이진 트리 요약의 알고리즘 비 재귀 1 차 순회 (3 가지 방법)
@ 저자 : Jingdai
@date : 2020.12.03
문
이진 트리의 재귀 적 1 차 순회는 매우 간단하지만 인터뷰 중에 면접관은 종종 비재 귀적 방법을 작성하도록 요청합니다. 여기에 요약이 있습니다.
방법 1
가장 간단한 방법을 먼저 작성하십시오. 먼저 큐를 사용하여 트리 순회를 완료하여 이진 트리를 계층 적으로 순회하는 코드를 기억하십시오. 다음 코드.
public static void levelOrderTraverse(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (queue.size() != 0) {
TreeNode cur = queue.poll();
System.out.println(cur.val);
if (cur.left != null)
queue.offer(cur.left);
if (cur.right != null)
queue.offer(cur.right);
}
}
위 코드의 대기열이 스택으로 바뀌면 어떻게됩니까? 트리의 순회는 여전히 완료되지만 순회 순서는 약간 이상합니다. 노드의 경우 먼저 자신을 순회 한 다음 오른쪽 하위 트리를 순회 한 다음 왼쪽 하위 트리를 순회합니다. 선주문 순회 순서는 무엇입니까? 먼저 자신을 탐색 한 다음 왼쪽 하위 트리를 탐색 한 다음 오른쪽 하위 트리를 탐색하여 왼쪽 및 오른쪽 하위 트리의 스택 순서를 변경할 수 있으며 결과는 다음 코드가됩니다. 본질적으로 큐를 스택으로 바꾸는 것은 bfs에서 dfs로 변경됩니다.
public static void preOrderTraverse(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> stack = new LinkedList<>();
stack.push(root);
while (stack.size() != 0) {
TreeNode cur = stack.pop();
System.out.println(cur.val);
if (cur.right != null)
stack.push(cur.right);
if (cur.left != null)
stack.push(cur.left);
}
}
또한 왼쪽 및 오른쪽 하위 트리의 경우 왼쪽 하위 트리를 먼저 통과 한 다음 오른쪽 하위 트리를 통과해야합니다. 스택은 후입 선출입니다. 스택에서 벗어날 때 통과합니다. , 그래서 우리는 먼저 횡단 한 다음 스택에 들어갑니다. 왼쪽 하위 트리.
방법 2
다음은 일반 데이터 구조 책에서 소개 한 방법 인 약간 더 복잡한 방법을 소개합니다.
첫 번째 순서는 첫 번째 만남의 순회이고, 중간 순서는 두 번째 만남의 순회이며, 사후 순서는 세 번째 만남의 순회입니다. 코드를 살펴보십시오.
public static void preOrderTraverse(TreeNode root) {
TreeNode p = root;
LinkedList<TreeNode> stack = new LinkedList<>();
while (p != null || stack.size() != 0) {
while (p != null) {
System.out.println(p.val);
stack.push(p);
p = p.left;
}
p = stack.pop();
p = p.right;
}
}
각 노드에 대해 만날 때 순회 된 다음 왼쪽 하위 트리가 순회됩니다. 노드에 왼쪽 하위 트리가 없으면 p가 오른쪽 하위 트리를 가리 키도록 요소를 팝한 다음 동일한 방식으로 오른쪽 하위 트리를 탐색합니다.
3 가지 방법 중 3 : Morris 방법
위 방법의 시간 복잡도는 모두 O(n)
이고 공간 복잡도는 O(h)
재귀 방법은 동일하지만 시스템의 스택 공간을 사용한다는 것입니다. Morris는 복잡한 O(1)
접근을 위한 공간 인이 문제에 대응합니다 .
먼저 Morris 순회 과정을 살펴 보겠습니다. (주오 쉔의 생각 참조) 이것은 선주문이 아니고,이 순회를 기반으로 선주문이 조금 변경된 후 선주문이됩니다.
현재 순회중인 노드의 cur
경우 다음 두 가지 상황이 있습니다.
-
경우
cur
에는 왼쪽 하위 트리가없는, 바로cur
오른쪽 서브 트리로 이동 :cur = cur.right
. -
때
cur
왼쪽 하위 트리가 왼쪽 하위 트리의 오른쪽 노드를 찾을 때rightmost
:-
경우
rightmost.right == null
, 여기에 대한 설명입니다rightmost.right
점cur
, 다음cur
의 왼쪽 서브 트리로 이동은 :rightmost.right = cur; cur = cur.left;
-
경우
rightmost.right == cur
본원에 기재된 두 번째rightmost.right
포인트 복구는 널 (NULL)이다cur
오른쪽 서브 트리로 이동 :rightmost.right = null; cur = cur.right;
-
를 cur
가리키면 null
루프가 종료됩니다.
최종 코드는 다음과 같습니다.
public static void morrisOrderTraverse(TreeNode root) {
TreeNode cur = root;
TreeNode rightmost = null;
while (cur != null) {
if (cur.left != null) {
rightmost = cur.left;
while (rightmost.right != null && rightmost.right != cur) {
rightmost = rightmost.right;
}
// the first time to come here
if (rightmost.right == null) {
rightmost.right = cur;
System.out.println(cur.val);
cur = cur.left;
} else {
// seconde time to come here
rightmost.right = null;
System.out.println(cur.val);
cur = cur.right;
}
} else {
System.out.println(cur.val);
cur = cur.right;
}
}
}
모리스 주문의 순서인데, 선주문으로 변경하는 방법은? 선주문은 처음 만날 때 순회하는 것입니다. Morris 순회에서 노드에 왼쪽 자식이 있으면 두 번 순회합니다 (처음에는 왼쪽 자식의 맨 오른쪽 노드가 null을 가리키고 두 번째는 맨 오른쪽 노드를 가리 킵니다). 왼쪽 자식의이 노드를 가리 킵니다. 자식에 왼쪽 자식이 없으면 한 번만 순회됩니다 (null은 순회하지 않기 때문에 null을 앞뒤로 계산하는 것도 두 번입니다). 그래서 우리가 처음으로 노드를 만나는 한 순회 할 수 있습니다. 즉, 노드에 왼쪽 자식이 있으면 첫 번째 만남에서 순회되고, 노드에 왼쪽 자식이 없으면 직접 순회됩니다. 마지막으로 Morris의 선주문 순회 코드는 다음과 같습니다.
public static void preOrderTraverse(TreeNode root) {
TreeNode cur = root;
TreeNode rightmost = null;
while (cur != null) {
if (cur.left != null) {
rightmost = cur.left;
while (rightmost.right != null && rightmost.right != cur) {
rightmost = rightmost.right;
}
// the first time to come here
if (rightmost.right == null) {
rightmost.right = cur;
System.out.println(cur.val);
cur = cur.left;
} else {
// the second time
rightmost.right = null;
cur = cur.right;
}
} else {
System.out.println(cur.val);
cur = cur.right;
}
}
}
같은 방식으로 Morris 메서드를 기반으로 한 순차 순회도 작성하기 쉽습니다. 즉, 노드가 두 번째로 만날 때 순회됩니다. 노드에 왼쪽 하위 트리가 없으면 직접 순회되고, 노드에 왼쪽 하위 트리가 있으면 두 번째로 발견 될 때 다시 순회됩니다.
테스트 코드
테스트를 용이하게하기 위해 여기에서 코드를 사용하여 트리를 만들고 테스트 할 수 있습니다.
public static void main(String[] args) {
// create tree
TreeNode root = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
TreeNode node7 = new TreeNode(7);
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
node3.right = node7;
preOrderTraverse(root);
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x; }
}