私は最近、自分の人生でBSTを十分に使用していたが、順序トラバーサル以外のものを使用することすら考えていなかったことに気づきました(プログラムを順/後順序トラバーサルを使用するのがどれだけ簡単かを認識し、知っていますが)。
これに気づいたとき、私は古いデータ構造の教科書のいくつかを引き出し、事前注文と事後注文のトラバースの有用性の背後にある理由を探しました-彼らはあまり言っていませんでした。
実際に事前注文/事後注文を使用するタイミングの例は何ですか?順序よりも意味があるのはいつですか?
どのような状況でバイナリツリーの予約順序、順序、および順序を使用するかを理解する前に、各トラバーサル戦略がどのように機能するかを正確に理解する必要があります。例として次のツリーを使用します。
ツリーのルートは7、左端のノードは、右端のノードは1です。
先行予約のトラバーサル:
要約:ルートで始まり(7)、右端のノードで終わる(1)
走査順序:7、1、0、3、2、5、4、6、9、8、10
順番通りの走査:
要約:左端のノードで始まり()、右端のノードで終わる(1)
トラバーサルシーケンス:0、1、2、3、4、5、6、7、8、9、10
後順走査:
要約:左端のノード()で始まり、ルート(7)で終わります
トラバーサルシーケンス:0、2、4、6、5、3、1、8、10、9、7
プログラマーが選択するトラバーサル戦略は、設計されているアルゴリズムの特定のニーズに依存します。目標は速度であるため、必要なノードを最速にする戦略を選択してください。
葉を検査する前に根を探索する必要があることがわかっている場合は、pre-orderを選択します。これは、すべての葉の前にすべての根に出会うからです。
ノードの前にすべての葉を探索する必要があることがわかっている場合は、post-orderを選択します。葉の検索でルートを検査する時間を無駄にしないためです。
ツリーのノードに固有のシーケンスがあることがわかっており、ツリーを元のシーケンスにフラット化する場合は、in-orderトラバーサルを使用する必要があります。ツリーは、作成されたのと同じ方法で平坦化されます。先行順序または後続順序のトラバーサルは、ツリーを作成に使用されたシーケンスに戻しません。
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
}
ツリーの階層形式を単純に線形形式で印刷したい場合は、おそらく前順序走査を使用します。例えば:
- ROOT
- A
- B
- C
- D
- E
- F
- G
予約注文/注文/注文後の使用法:簡単な単語
事前注文:ツリーのコピーを作成するために使用します新しいツリー、レプリカを取得します
In-order::増加しない順序でノードの値を取得するには
Post-order::葉から根にツリーを削除する場合
プレオーダーとポストオーダーは、それぞれトップダウンとボトムアップの再帰アルゴリズムに関連しています。バイナリツリーに特定の再帰アルゴリズムを反復的な方法で記述したい場合、これは基本的に行うことです。
さらに、事前順序シーケンスと事後順序シーケンスが手元のツリーを完全に指定し、コンパクトなエンコーディングを生成することに注意してください(少なくともスパースツリーの場合)。
この違いが本当の役割を果たしていると思われる場所はたくさんあります。
私が指摘するすばらしいものの1つは、コンパイラーのコード生成です。次のステートメントを検討してください。
x := y + 32
そのためのコードを生成する方法は、(もちろん、もちろん)レジスタにyをロードし、32をレジスタにロードし、次に2つを追加する命令を生成するためのコードを生成することです。それを操作する前に何かがレジスタ内になければならないので(仮定しましょう、常に定数オペランドを実行できますが、何でも)、この方法で実行する必要があります。
一般に、この質問に答えることができるのは基本的にこれになります。データ構造の異なる部分を処理する間に依存関係がある場合、違いは本当に重要です。これは、要素を印刷するとき、コードを生成するとき(外部状態が違います。もちろん、これを単項的に表示することもできます)、または最初に処理される子に応じて計算を伴う構造に対して他のタイプの計算を行うときに表示されます。