Preorder、Postorder、Inorder Binary Search Tree Traversal戦略を使用する場合


97

私は最近、BSTを十分に活用している一方で、インオーダートラバーサル以外を使用することさえ考えたことがないことに気付きました(プログラムを前後のトラバーサルに使用するのがいかに簡単であるかを知っており、知っています)。

これに気づいたとき、私は私の古いデータ構造の教科書の一部を引き出して、予約注文と注文注文の走査の有用性の背後にある理由を探しました-彼らは多くのことを言いませんでした。

プレオーダー/ポストオーダーを実際に使用する場合の例は何ですか?順序より意味があるのはいつですか?

回答:


135

プレオーダー、インオーダー、ポストオーダーのトラバーサル戦略を使用する場合

バイナリツリーで事前順序、順序順、事後順序をどのような状況で使用するかを理解する前に、各走査戦略がどのように機能するかを正確に理解する必要があります。例として次のツリーを使用します。

ツリーのルートは7、左端のノードは0、右端のノードは10です。

ここに画像の説明を入力してください

予約注文トラバーサル

概要:ルート(7)で始まり、右端のノード(10

走査シーケンス:7、1、0、3、2、5、4、6、9、8、10

順序トラバーサル

概要:左端のノード(0)で始まり、右端のノード(10)で終わります

走査シーケンス:0、1、2、3、4、5、6、7、8、9、10

注文後のトラバーサル

概要:左端のノード(0)で始まり、ルート(7)で終わります

走査シーケンス:0、2、4、6、5、3、1、8、10、9、7

プレオーダー、インオーダー、ポストオーダーを使用するのはいつですか?

プログラマーが選択する走査戦略は、設計されているアルゴリズムの特定のニーズによって異なります。目標は速度です。そのため、必要なノードを最速で実現する戦略を選択してください。

  1. 葉を検査する前に根を探索する必要があることがわかっている場合は、すべての葉の前にすべての根に出会うため事前注文を選択します。

  2. ノードの前にすべての葉を探索する必要があることがわかっている場合は、葉の検索でルートを検査する時間を無駄にしないためポストオーダーを選択します。

  3. ツリーがノードに固有のシーケンスを持っていることがわかっていて、ツリーを元のシーケンスに平坦化したい場合は、順序トラバーサルを使用する必要があります。ツリーは、作成されたのと同じ方法で平坦化されます。事前注文または事後注文のトラバーサルでは、ツリーの作成に使用されたシーケンスにツリーを巻き戻さない場合があります。

予約注文、注文注文、注文後の再帰アルゴリズム(C ++):

struct Node{
    int data;
    Node *left, *right;
};
void preOrderPrint(Node *root)
{
  print(root->name);                                  //record root
  if (root->left != NULL) preOrderPrint(root->left);  //traverse left if exists
  if (root->right != NULL) preOrderPrint(root->right);//traverse right if exists
}

void inOrderPrint(Node *root)
{
  if (root.left != NULL) inOrderPrint(root->left);   //traverse left if exists
  print(root->name);                                 //record root
  if (root.right != NULL) inOrderPrint(root->right); //traverse right if exists
}

void postOrderPrint(Node *root)
{
  if (root->left != NULL) postOrderPrint(root->left);  //traverse left if exists
  if (root->right != NULL) postOrderPrint(root->right);//traverse right if exists
  print(root->name);                                   //record root
}

3
非再帰的走査についてはどうですか?前のノードに戻る必要がないため、順序順/事後順序に比べて、事前順序で非再帰的にツリーをトラバースする方がはるかに簡単です。
bluenote10

@ bluenote10どういう意味ですか?プレオーダーでは、ノードに戻り、左の子を処理した後、右の子を処理します。もちろん、「まだ訪問されていないノード」のキューを使用することもできますが、これは実際には、暗黙的な(スタック)ストレージを明示的なキューと交換するだけです。すべてのトラバーサルメソッドでは、左と右の両方の子を処理する必要があります。つまり、それらのいずれかを実行した後、親に「戻る」必要があります。
ジョシュアテイラー

@JoshuaTaylor:はい、それらはすべて同じ複雑さのクラスですが、典​​型的な実装を見ると、ポストオーダーはおそらくもう少しトリッキーです。
bluenote10 2016年

2
プレオーダートラバースは、挿入のシーケンスでノード値を提供します。ツリーのコピーを作成する場合は、この方法でソースツリーをトラバースする必要があります。順序トラバースは、ソートされたノード値を提供します。ポストオーダートラバースについては、このメソッドを使用してツリー全体を削除し、最初にリーフノードにアクセスすることができます。
アルビン2017

ツリーが正しく順序付けられていなくても、それは本当だと思います。つまり、シーケンスが最初に並べ替えられていないと、並べ替えられたシーケンスが順番に表示されません。
CodeYogi 2017

29

予約購入:木のコピーを作成するために使用されます。たとえば、ツリーのレプリカを作成する場合は、ノードを配列に配置し、前順走査を行います。次に、配列の各値に対して新しいツリーで挿入操作を実行します。元のツリーのコピーが作成されます。

In-order:: BSTでノードの値を降順に取得するために使用されます。

ポストオーダー::リーフからルートへのツリーの削除に使用されます


2
これはすばらしい簡潔な回答であり、注文前と注文後のユースケースを理解するのに役立ちました。ただし、質問がこれに直接言及していることは明らかですが、これはバイナリ検索ツリーの場合であり、必ずしも一般的なバイナリツリーでは機能しないことに注意してください。たとえば、コピー処理中の挿入ロジックが機能しないため、一般的なバイナリツリーをコピーするために、事前注文トラバーサルを使用する必要はありません。
markckim 2018

7
In-order::ノードの値を「減少しない」順序で取得する-「増加しない」ではない
rahil008

26

単純にツリーの階層形式を線形形式で出力したい場合は、おそらくプレオーダートラバーサルを使用します。例えば:

- ROOT
    - A
         - B
         - C
    - D
         - E
         - F
             - G

4
またはTreeView、GUIアプリケーションのコンポーネント内。
12

4

プレオーダーとポストオーダーは、それぞれトップダウンとボトムアップの再帰アルゴリズムに関連しています。与えられた再帰的アルゴリズムをバイナリツリーで反復的に記述したい場合、これは基本的に行うことです。

さらに、前後順のシーケンスが一緒に手元のツリーを完全に指定し、コンパクトなエンコーディング(少なくともスパースツリーの場合)が生成されることを確認します。


1
重要なことを言っていると思いますが、前半について説明していただけますか?
CodeYogi

@CodeYogi具体的に何を説明する必要がありますか?
ラファエル

1
「プリオーダーとポストオーダーは、トップダウンとボトムアップの再帰アルゴリズムに関連しています」最初のケースでは、ノードは再帰メソッドのいずれかを呼び出す前に処理され、その逆も同様です。 ?
CodeYogi 2016

@CodeYogiはい、基本的に。
ラファエル

2

この違いが本当の役割を果たすのを目にする場所はたくさんあります。

私が指摘する素晴らしいものの1つは、コンパイラーのコード生成です。次のステートメントについて考えてみます。

x := y + 32

そのためのコードを生成する方法は、(当然ながら)最初にyをレジスターにロードするコードを生成し、32をレジスターにロードしてから、2つを追加する命令を生成することです。それを操作する前に何かがレジスターになければならないので(仮定しましょう。常に定数のオペランドを実行できますが、何でも)、この方法で実行する必要があります。

一般に、この質問に対する答えは基本的にこれに削減されます。データ構造の異なる部分の処理間に依存関係がある場合、違いは本当に重要です。これは、要素を印刷するとき、コードを生成するとき(外部状態によって違い、もちろんこれをモナドで表示することもできます)、または最初に処理される子に応じて計算を伴う構造に対して他のタイプの計算を行うときに表示されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.