스택이나 재귀를 사용하지 않고 Morris inorder tree traversal을 설명하십시오.


126

누군가 스택이나 재귀를 사용하지 않고 다음 Morris inorder tree traversal 알고리즘을 이해하도록 도와 줄 수 있습니까? 나는 그것이 어떻게 작동하는지 이해하려고 노력했지만 그저 나를 탈출했습니다.

 1. Initialize current as root
 2. While current is not NULL
  If current does not have left child     
   a. Print currents data
   b. Go to the right, i.e., current = current->right
  Else
   a. In current's left subtree, make current the right child of the rightmost node
   b. Go to this left child, i.e., current = current->left

나는 나무가있는 방식으로 수정 이해 current node는되어 right child의를 max noderight subtree와 중위 순회에 대해이 속성을 사용합니다. 하지만 그 이상으로 나는 길을 잃었습니다.

편집 :이 동반 C ++ 코드를 찾았습니다. 수정 후 트리가 어떻게 복원되는지 이해하기가 어려웠습니다. 마법은 else절에 있으며 오른쪽 잎이 수정되면 맞습니다. 자세한 내용은 코드를 참조하십시오.

/* Function to traverse binary tree without recursion and
   without stack */
void MorrisTraversal(struct tNode *root)
{
  struct tNode *current,*pre;

  if(root == NULL)
     return; 

  current = root;
  while(current != NULL)
  {
    if(current->left == NULL)
    {
      printf(" %d ", current->data);
      current = current->right;
    }
    else
    {
      /* Find the inorder predecessor of current */
      pre = current->left;
      while(pre->right != NULL && pre->right != current)
        pre = pre->right;

      /* Make current as right child of its inorder predecessor */
      if(pre->right == NULL)
      {
        pre->right = current;
        current = current->left;
      }

     // MAGIC OF RESTORING the Tree happens here: 
      /* Revert the changes made in if part to restore the original
        tree i.e., fix the right child of predecssor */
      else
      {
        pre->right = NULL;
        printf(" %d ",current->data);
        current = current->right;
      } /* End of if condition pre->right == NULL */
    } /* End of if condition current->left == NULL*/
  } /* End of while */
}

12
이 알고리즘에 대해 들어 본 적이 없습니다. 아주 우아합니다!
Fred Foo

5
의사 코드 + 코드 (아마도) 의 소스 를 나타내는 것이 유용 할 것이라고 생각했습니다 .
Bernhard Barker 2014 년


위 코드에서 다음 줄은 필요하지 않습니다. pre->right = NULL;
prashant.kr.mod dec

답변:


155

알고리즘을 올바르게 읽고 있다면 이것이 작동 방식의 예가되어야합니다.

     X
   /   \
  Y     Z
 / \   / \
A   B C   D

첫째, X루트이므로 current. X왼쪽 자식이 있으므로 의 왼쪽 하위 트리 X의 맨 오른쪽 자식이 X됩니다 X. 즉, 순서 순회에서 바로 전임자 입니다. 따라서 X의 오른쪽 자식이 B되며로 current설정됩니다 Y. 이제 트리는 다음과 같습니다.

    Y
   / \
  A   B
       \
        X
       / \
     (Y)  Z
         / \
        C   D

(Y)위의 Y모든 하위 항목은 재귀 문제로 생략됩니다. 어쨌든 중요한 부분이 나열됩니다. 이제 트리에 X에 대한 링크가 있으므로 순회가 계속됩니다.

 A
  \
   Y
  / \
(A)  B
      \
       X
      / \
    (Y)  Z
        / \
       C   D

그러면 A왼쪽 자식이 없기 때문에 출력 되고, 이전 반복에서의 오른쪽 자식 으로 만들어진에 current반환됩니다 . 다음 반복에서 Y에는 두 자식이 있습니다. 그러나 루프의 이중 조건으로 인해 루프가 자신에게 도달하면 중지됩니다. 이는 왼쪽 하위 트리가 이미 통과했음을 나타냅니다. 따라서 자체 인쇄하고 오른쪽 하위 트리 인 .YAB

B자신을 인쇄 한 다음 current되고 X같은 검사 과정을 통과하는, Y또한 계속 그 왼쪽 서브 트리가 통과 된 것을 실현했다 Z. 나머지 트리는 동일한 패턴을 따릅니다.

재귀가 필요하지 않습니다. 스택을 통한 역 추적에 의존하는 대신 (하위) 트리의 루트로 돌아가는 링크가 어쨌든 재귀 적 인 순서 트리 순회 알고리즘에서 액세스되는 지점으로 이동하기 때문입니다. 왼쪽 하위 트리가 완료되었습니다.


3
설명 해주셔서 감사합니다. 왼쪽 자식은 절단되지 않고 대신 순회를 위해 맨 오른쪽 리프에 추가 된 새 오른쪽 자식을 절단하여 나중에 트리를 복원합니다. 코드와 함께 업데이트 된 게시물을 참조하십시오.
brainydexter

1
멋진 스케치이지만 여전히 while 루프 조건을 이해하지 못합니다. pre-> right! = 현재 확인이 필요한 이유는 무엇입니까?
NO_NAME

6
왜 이것이 작동하는지 모르겠습니다. A를 인쇄 한 후에 Y는 루트가되고 A는 여전히 왼쪽 자식입니다. 따라서 우리는 이전과 같은 상황에 있습니다. 그리고 우리는 A를 반복합니다. 사실, 그것은 무한 루프처럼 보입니다.
user678392

이것은 Y와 B 사이의 연결을 끊지 않습니까? X가 현재로 설정되고 Y가 pre로 설정되면 현재 (X)를 찾을 때까지 pre의 오른쪽 하위 트리를 살펴보고 pre => right를 NULL로 설정합니다. B가 되겠죠? 위에 게시 된 코드에 따라
Achint

17

재귀 순서 순회는 : (in-order(left)->key->in-order(right)). (이것은 DFS와 유사합니다)

DFS를 수행 할 때 역 추적 할 위치를 알아야합니다 (일반적으로 스택을 유지하는 이유입니다).

역 추적해야하는 상위 노드를 통과 할 때-> 역 추적해야 할 노드를 찾고 상위 노드에 대한 링크를 업데이트합니다.

우리가 역 추적 할 때? 더 이상 갈 수 없을 때. 더 이상 갈 수 없을 때? 남은 아이가 없을 때.

우리는 어디로 되돌아 갈까요? 고시 : SUCCESSOR에게!

따라서 왼쪽 자식 경로를 따라 노드를 따라갈 때 각 단계에서 선행 작업이 현재 노드를 가리 키도록 설정합니다. 이렇게하면 선행 작업이 후속 작업에 대한 링크 (역 추적 링크)를 갖게됩니다.

역 추적이 필요할 때까지 할 수있는 동안 왼쪽을 따라갑니다. 역 추적해야 할 때 현재 노드를 인쇄하고 후속 작업에 대한 올바른 링크를 따릅니다.

우리가 방금 역 추적했다면-> 오른쪽 자식을 따라야합니다 (왼쪽 자식으로 끝났습니다).

방금 역 추적했는지 확인하는 방법은 무엇입니까? 현재 노드의 선행자를 가져 와서이 노드에 대한 올바른 링크가 있는지 확인하십시오. 그랬다면-우리가 따라 간 것보다. 링크를 제거하여 트리를 복원하십시오.

왼쪽 링크가 없다면 => 우리는 역 추적하지 않았고 왼쪽 아이들을 따라 가야합니다.

다음은 내 Java 코드입니다 (죄송합니다. C ++가 아닙니다).

public static <T> List<T> traverse(Node<T> bstRoot) {
    Node<T> current = bstRoot;
    List<T> result = new ArrayList<>();
    Node<T> prev = null;
    while (current != null) {
        // 1. we backtracked here. follow the right link as we are done with left sub-tree (we do left, then right)
        if (weBacktrackedTo(current)) {
            assert prev != null;
            // 1.1 clean the backtracking link we created before
            prev.right = null;
            // 1.2 output this node's key (we backtrack from left -> we are finished with left sub-tree. we need to print this node and go to right sub-tree: inOrder(left)->key->inOrder(right)
            result.add(current.key);
            // 1.15 move to the right sub-tree (as we are done with left sub-tree).
            prev = current;
            current = current.right;
        }
        // 2. we are still tracking -> going deep in the left
        else {
            // 15. reached sink (the leftmost element in current subtree) and need to backtrack
            if (needToBacktrack(current)) {
                // 15.1 return the leftmost element as it's the current min
                result.add(current.key);
                // 15.2 backtrack:
                prev = current;
                current = current.right;
            }
            // 4. can go deeper -> go as deep as we can (this is like dfs!)
            else {
                // 4.1 set backtracking link for future use (this is one of parents)
                setBacktrackLinkTo(current);
                // 4.2 go deeper
                prev = current;
                current = current.left;
            }
        }
    }
    return result;
}

private static <T> void setBacktrackLinkTo(Node<T> current) {
    Node<T> predecessor = getPredecessor(current);
    if (predecessor == null) return;
    predecessor.right = current;
}

private static boolean needToBacktrack(Node current) {
    return current.left == null;
}

private static <T> boolean weBacktrackedTo(Node<T> current) {
    Node<T> predecessor = getPredecessor(current);
    if (predecessor == null) return false;
    return predecessor.right == current;
}

private static <T> Node<T> getPredecessor(Node<T> current) {
    // predecessor of current is the rightmost element in left sub-tree
    Node<T> result = current.left;
    if (result == null) return null;
    while(result.right != null
            // this check is for the case when we have already found the predecessor and set the successor of it to point to current (through right link)
            && result.right != current) {
        result = result.right;
    }
    return result;
}

4
나는 당신의 대답 이이 솔루션을 생각해내는 것에 대한 높은 수준의 추론을 제공하기 때문에 많이 좋아합니다!
KFL

6

여기 알고리즘에 대한 애니메이션을 만들었습니다. https://docs.google.com/presentation/d/11GWAeUN0ckP7yjHrQkIB0WT9ZUhDBSa-WR0VsPU38fg/edit?usp=sharing

이것은 이해하는 데 도움이 될 것입니다. 파란색 원은 커서이고 각 슬라이드는 외부 while 루프의 반복입니다.

다음은 morris traversal에 대한 코드입니다 (저는 괴짜를 위해 괴짜에서 복사하고 수정했습니다).

def MorrisTraversal(root):
    # Set cursor to root of binary tree
    cursor = root
    while cursor is not None:
        if cursor.left is None:
            print(cursor.value)
            cursor = cursor.right
        else:
            # Find the inorder predecessor of cursor
            pre = cursor.left
            while True:
                if pre.right is None:
                    pre.right = cursor
                    cursor = cursor.left
                    break
                if pre.right is cursor:
                    pre.right = None
                    cursor = cursor.right
                    break
                pre = pre.right
#And now for some tests. Try "pip3 install binarytree" to get the needed package which will visually display random binary trees
import binarytree as b
for _ in range(10):
    print()
    print("Example #",_)
    tree=b.tree()
    print(tree)
    MorrisTraversal(tree)

당신의 애니메이션은 꽤 흥미 롭습니다. 시간이 지나면 외부 링크가 사라지는 경우가 많으므로 게시물에 포함 할 이미지로 만드는 것을 고려하십시오.
laancelot

1
애니메이션이 도움이됩니다!
yyFred

훌륭한 스프레드 시트와 binarytree 라이브러리의 사용. 그러나 코드가 올바르지 않아 루트 노드를 인쇄하지 못합니다. 줄 print(cursor.value)뒤에 추가해야합니다pre.right = None
satnam

4
public static void morrisInOrder(Node root) {
        Node cur = root;
        Node pre;
        while (cur!=null){
            if (cur.left==null){
                System.out.println(cur.value);      
                cur = cur.right; // move to next right node
            }
            else {  // has a left subtree
                pre = cur.left;
                while (pre.right!=null){  // find rightmost
                    pre = pre.right;
                }
                pre.right = cur;  // put cur after the pre node
                Node temp = cur;  // store cur node
                cur = cur.left;  // move cur to the top of the new tree
                temp.left = null;   // original cur left be null, avoid infinite loops
            }        
        }
    }

나는이 코드가 이해하는 것이 더 좋을 것이라고 생각합니다. 무한 루프를 피하기 위해 null을 사용하고 다른 마법을 사용할 필요는 없습니다. 선주문으로 쉽게 수정할 수 있습니다.


1
해결책은 매우 깔끔하지만 한 가지 문제가 있습니다. Knuth에 따르면 나무는 결국 수정해서는 안됩니다. 이를 통해 temp.left = null나무를 잃게됩니다.
Ankur

이 방법은 이진 트리를 연결 목록으로 변환하는 것과 같은 장소에서 사용할 수 있습니다.
cyber_raj

@Shan이 말한 것처럼 알고리즘은 원래 트리를 변경해서는 안됩니다. 알고리즘이 순회를 위해 작동하는 동안 원래 트리를 파괴합니다. 따라서 이것은 실제로 원래 알고리즘과 다르므로 오해의 소지가 있습니다.
ChaoSXDemon


1

아래의 의사 코드가 더 드러나기를 바랍니다.

node = root
while node != null
    if node.left == null
        visit the node
        node = node.right
    else
        let pred_node be the inorder predecessor of node
        if pred_node.right == null /* create threading in the binary tree */
            pred_node.right = node
            node = node.left
        else         /* remove threading from the binary tree */
            pred_node.right = null 
            visit the node
            node = node.right

질문의 C ++ 코드를 참조하면 내부 while 루프는 현재 노드의 순서가있는 선행자를 찾습니다. 표준 이진 트리에서 선행 작업의 오른쪽 자식은 null이어야하며 스레드 버전에서는 오른쪽 자식이 현재 노드를 가리켜 야합니다. 오른쪽 자식이 null이면 현재 노드로 설정되어 효과적으로 threading을 생성하며 , 그렇지 않으면 일반적으로 스택에 저장되어야하는 반환 지점으로 사용됩니다. 오른쪽 자식이 null 이 아니면 알고리즘은 원래 트리가 복원되었는지 확인한 다음 오른쪽 하위 트리에서 순회를 계속합니다 (이 경우 왼쪽 하위 트리를 방문한 것으로 알려져 있음).


0

Python 솔루션 시간 복잡성 : O (n) 공간 복잡성 : O (1)

우수한 Morris Inorder Traversal 설명

class Solution(object):
def inorderTraversal(self, current):
    soln = []
    while(current is not None):    #This Means we have reached Right Most Node i.e end of LDR traversal

        if(current.left is not None):  #If Left Exists traverse Left First
            pre = current.left   #Goal is to find the node which will be just before the current node i.e predecessor of current node, let's say current is D in LDR goal is to find L here
            while(pre.right is not None and pre.right != current ): #Find predecesor here
                pre = pre.right
            if(pre.right is None):  #In this case predecessor is found , now link this predecessor to current so that there is a path and current is not lost
                pre.right = current
                current = current.left
            else:                   #This means we have traverse all nodes left to current so in LDR traversal of L is done
                soln.append(current.val) 
                pre.right = None       #Remove the link tree restored to original here 
                current = current.right
        else:               #In LDR  LD traversal is done move to R  
            soln.append(current.val)
            current = current.right

    return soln

죄송 합니다만이 질문에 대한 직접적인 답변은 아닙니다. OP는 알고리즘 자체를 구현하기를 원하기 때문에 구현이 아닌 작동 방식에 대한 설명을 요청했습니다. 귀하의 의견은 이미 알고리즘을 이해하고 있지만 OP는 아직 이해하지 못하는 사람에게 유용합니다. 또한 정책으로서, 링크가 시간이 지남에 따라 변경되거나 끊어 질 수 있기 때문에 일부 외부 리소스에 단순히 연결하는 대신 자체적으로 답변을 포함해야합니다. 링크를 포함하는 것은 괜찮지 만 그렇게한다면 적어도 링크가 제공하는 것의 요점도 포함해야합니다.
Anonymous1847

0

Morris In-order Traversal의 PFB 설명.

  public class TreeNode
    {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null)
        {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }

    class MorrisTraversal
    {
        public static IList<int> InOrderTraversal(TreeNode root)
        {
            IList<int> list = new List<int>();
            var current = root;
            while (current != null)
            {
                //When there exist no left subtree
                if (current.left == null)
                {
                    list.Add(current.val);
                    current = current.right;
                }
                else
                {
                    //Get Inorder Predecessor
                    //In Order Predecessor is the node which will be printed before
                    //the current node when the tree is printed in inorder.
                    //Example:- {1,2,3,4} is inorder of the tree so inorder predecessor of 2 is node having value 1
                    var inOrderPredecessorNode = GetInorderPredecessor(current);
                    //If the current Predeccessor right is the current node it means is already printed.
                    //So we need to break the thread.
                    if (inOrderPredecessorNode.right != current)
                    {
                        inOrderPredecessorNode.right = null;
                        list.Add(current.val);
                        current = current.right;
                    }//Creating thread of the current node with in order predecessor.
                    else
                    {
                        inOrderPredecessorNode.right = current;
                        current = current.left;
                    }
                }
            }

            return list;
        }

        private static TreeNode GetInorderPredecessor(TreeNode current)
        {
            var inOrderPredecessorNode = current.left;
            //Finding Extreme right node of the left subtree
            //inOrderPredecessorNode.right != current check is added to detect loop
            while (inOrderPredecessorNode.right != null && inOrderPredecessorNode.right != current)
            {
                inOrderPredecessorNode = inOrderPredecessorNode.right;
            }

            return inOrderPredecessorNode;
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.