スタックや再帰を使用せずにモリスの順序木探索を説明する


125

誰かが、スタックや再帰を使用せずに、次のモリス順序木探索アルゴリズムを理解するのを手伝ってくれませんか?私はそれがどのように機能するかを理解しようとしていましたが、それはちょうど私をエスケープしました。

 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し、INORDERトラバーサルのために、このプロパティを使用します。しかし、それを超えて、私は迷っています。

編集:この付随する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


上記のコードでは、行を次する必要はありません: pre->right = NULL;
prashant.kr.mod

回答:


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行われた、A前回の反復での右の子を。次の反復では、Yには両方の子があります。ただし、ループの二重条件により、ループはそれ自体に到達したときに停止します。これは、左のサブツリーをすでに通過したことを示しています。したがって、それ自体が出力され、正しいサブツリーであるが続きBます。

Bはそれ自体を出力し、にcurrentなりますX。これは、実行されたのと同じチェックプロセスを経て、Yその左側のサブツリーがトラバースされ、を続行していることも認識しZます。ツリーの残りの部分も同じパターンに従います。

スタックを介したバックトラックに依存する代わりに、(サブ)ツリーのルートに戻るリンクは、とにかく再帰的な順序ツリートラバーサルアルゴリズムでアクセスされるポイントに移動するため、再帰は不要です。左のサブツリーが終了しました。


3
説明ありがとう。左の子は切断されず、代わりに、走査のために右端の葉に追加された新しい右の子を切断することにより、ツリーが復元されます。コードを含む私の更新された投稿を参照してください。
brainydexter '31年

1
いいスケッチですが、whileループの状態がまだわかりません。pre-> right!= currentのチェックが必要なのはなぜですか?
No_name 2013年

6
これが機能する理由がわかりません。Aを出力すると、Yがルートになり、Aは左の子になります。したがって、私たちは以前と同じ状況にあります。そして、Aを繰り返します。実際、これは無限ループのように見えます。
user678392 2013年

これはYとBの間の接続を切断しませんか?Xを現在に設定し、Yを事前に設定すると、現在(X)が見つかるまでpreの正しいサブツリーを調べ、次にpre => rightをNULLに設定します。上記のコードに従って
Achint 2014年

17

再帰的な順序トラバーサルは次のとおり(in-order(left)->key->in-order(right))です。(これはDFSに似ています)

DFSを実行するときは、どこにバックトラックするかを知る必要があります(そのため、通常はスタックを保持します)。

バックトラックする必要がある親ノードを通過するとき->バックトラックするノードを見つけ、親ノードへのリンクを更新します。

バックトラックするとき?それ以上行けないとき。これ以上行けないときは?左の子供のプレゼントがないとき。

どこに戻るか?通知:成功者へ!

したがって、左の子パスに沿ってノードをたどるときに、各ステップで先行ノードを現在のノードを指すように設定します。このようにして、前任者は後継者へのリンク(バックトラック用のリンク)を持ちます。

バックトラックが必要になるまで、できる限り左に進みます。バックトラックする必要がある場合は、現在のノードを出力し、後続への正しいリンクをたどります。

後戻りした場合->右の子を追跡する必要があります(左の子で完了です)。

バックトラックしたかどうかを確認するにはどうすればよいですか?現在のノードの先行ノードを取得し、(このノードへの)正しいリンクがあるかどうかを確認します。持っている場合-私たちがフォローしたよりも。リンクを削除してツリーを復元します。

左のリンクがない場合=>バックトラックしなかったので、左の子の後に進みます。

これが私の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 2018

6

ここでアルゴリズムのアニメーションを作成しました:https : //docs.google.com/presentation/d/11GWAeUN0ckP7yjHrQkIB0WT9ZUhDBSa-WR0VsPU38fg/edit?usp=sharing

これはうまくいけば理解に役立つはずです。青い円はカーソルで、各スライドは外側のwhileループの繰り返しです。

これは、モリストラバーサルのコードです(オタクのオタクからコピーして変更しました)。

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

優れたスプレッドシートとバイナリツリーライブラリの使用法。しかし、コードは正しくなく、ルートノードの印刷に失敗します。行の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
解決策は非常にすっきりしていますが、問題が1つあります。クヌースによると、ツリーは最終的に変更されるべきではありません。これによりtemp.left = null、ツリーを失われます。
Ankur 2015年

このメソッドは、バイナリツリーをリンクリストに変換するような場所で使用できます。
cyber_raj

@Shanが言ったように、アルゴリズムは元のツリーを変更するべきではありません。アルゴリズムはトラバースのために機能しますが、元のツリーを破壊します。したがって、これは実際には元のアルゴリズムとは異なり、誤解を招く可能性があります。
ChaoSXDemon 2017年


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)

優れたモリス秩序トラバーサルの説明

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

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.