記事ディレクトリ
序文
バイナリ ツリーは最も基本的なデータ構造の 1 つであり、コンピュータ サイエンスにおいて非常に重要な用途があります。バイナリ ツリー トラバーサルとは、バイナリ ツリー内のすべてのノードを特定の順序でトラバースすることを指します。これは、バイナリ ツリーの最も基本的な操作の 1 つです。
バイナリツリーをトラバースする方法
バイナリツリーを構築する
この関数は、createNode
新しいバイナリ ツリー ノードを作成し、そのノードへのポインタを返します。この関数は、新しいノードの値 (ノード内の両方のポインター) を表すために使用される整数型パラメーター val を受け取りますNULL
。
// 创建新节点的函数
struct TreeNode *createNode(int val) {
struct TreeNode *node = (struct TreeNode*) malloc(sizeof(struct TreeNode));
node->val = val;
node->left = NULL;
node->right = NULL;
return node;
}
このbuildTree
機能は、固定構造のバイナリ ツリーを構築し、ルート ノードのポインタを返すことです。関数内では、まず値 1 のルート ノード root を作成し、次にcreateNode
関数を通じてバイナリ ツリーのすべてのノードを作成し、それらの値と対応するサブツリー ポインターを設定します。
// 构建一棵二叉树
struct TreeNode *buildTree()
{
struct TreeNode *root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);
root->right->left = createNode(6);
root->right->right = createNode(7);
root->left->left->left = createNode(8);
root->left->left->right = createNode(9);
return root;
}
buildTree
関数 を渡すと、バイナリ ツリーが得られます。
バイナリ ツリーを再帰的に走査する
再帰的走査の具体的な手順は次のとおりです。
- まず、現在のノードノードが空かどうかを判断し、空の場合は直接返します。
- 現在のノードの左側のサブツリーを再帰的に走査します。つまり、inOrder(node->left) を呼び出します。【1】
- 現在のノードを走査します。つまり、ノードノードの値node->valを出力します。【2】
- 現在のノードの右のサブツリーを再帰的に走査します。つまり、inOrder(node->right) を呼び出します。【3】
次に、順序トラバーサルの詳細なコードは次のとおりです。
// 递归中序遍历
void inOrder(struct TreeNode*node)
{
// 判断节点是否为空
if (node == NULL) return;
// 先访问左孩子
inOrder(node->left);
// 访问自己
printf(" %d ",node->val);
// 访问右孩子
inOrder(node->right);
}
注:アイデア ステップ [2] を [1] の前に移動することは、事前順序の走査です。 アイデアステップ[2]を[3]に移動した後、プリオーダートラバーサルです。
バイナリ ツリーの非再帰的走査
このinOrder2
機能はバイナリ ツリーの非再帰的な順序トラバーサルを実行することであり、ここではスタックを使用して再帰的な順序トラバーサル操作をシミュレートします。関数のアイデアは次のとおりです。
この関数は 2 つのパラメーター
root
(バイナリ ツリーのルート ノード) とnodeCount
(ノードの総数) を受け取り、nodeCount
トラバーサル スタックのサイズを初期化するために使用されます。まず、
malloc
関数を使用して、ノード ポインターを格納するサイズのnodeCount
ポインター配列data
を割り当てます (スタック構造をシミュレートします)。次に、スタック トップ ポインタを定義し
dataLen
て 0 に初期化し、p
現在のノードのポインタを格納するポインタを初期化します。その後、トラバーサル ループに入ります。現在のノードが
p
空かどうかを確認し、空でない場合はスタックにプッシュし、p
その左側の子ノードを更新します。それ以外の場合は、スタックからノードをポップしp
、その値を出力し、p
その右側の子ノードを更新します。スタックが空になり、現在のノードがp
NULL になるまでループします。最後に、動的に割り当てられたスタック領域が解放されます
data
。
// 非递归中序遍历
void inOrder2(struct TreeNode*root,int nodeCount)
{
// 初始化顺序栈
struct TreeNode* *data = (struct TreeNode**)malloc(sizeof(struct TreeNode*)*nodeCount);
// 栈顶指针
char dataLen = 0;
struct TreeNode*p = root;
// 遍历
while (p || dataLen)
{
// 节点不为空
if (p)
{
// 先入栈再访问下一个左孩子
data[dataLen++] = p;
p = p->left;
}
else
{
// 到达叶子节点后 应该先访问叶子节点再回溯到父节点最后访问兄弟
p = data[--dataLen];
printf(" %d ",p->val);
p = p->right;
}
}
// 注销栈空间
free(data);
}
この関数の考え方は、スタックを使用して再帰的操作をシミュレートすることであるため、再帰的メソッドよりも多くのメモリを節約し、関数の実行順序をより適切に制御できます。
レベルトラバーサル
この機能levelOrder
は、バイナリ ツリーの階層トラバースを実行することです。つまり、各層で左から右に順番にノードをトラバースし、ノードの値を出力します。この関数は、バイナリ ツリーのルート ノードroot
とノードの総数という2 つのパラメータを受け取りますnodeCount
。このうち、nodeCount
シーケンシャルキューのサイズを初期化するために使用されます。
関数内では、まず
malloc
関数を使用して、ノード ポインターを格納するためのサイズのnodeCount
ポインター配列を動的に割り当てますqueue
(キュー構造をシミュレートします)。この関数は、キュー ヘッド ポインタfront
とキュー テール ポインタも定義しrear
、p
ルート ノードへのポインタを初期化しますroot
。まず、ルート ノード (ポインター) をキューに追加します
p
。次に、キューの末尾ポインタが先頭ポインタより前にあるかどうか (キューが空かどうか) を判断します。条件が true の場合、トラバーサル ループに入り、キューの先頭からノードをポップし
p
、現在のノードがp
空かどうかを判断します。ループ内では、ノードの値、
p
つまりアクセスされたノードの値が最初に出力されます。その後、p
ノードの左の子が存在する場合、その左の子がキューに入れられます。p
ノードの右の子ノードが存在する場合、その右の子ノードがキューに入れられます。最後に、次のサイクルに進み、上記の手順を繰り返します。
// 层次遍历二叉树
void levelOrder(struct TreeNode*root,int nodeCount)
{
// 定义一个顺序队列用于辅助层序遍历
struct TreeNode* *queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*)*nodeCount);
// 队头 队尾
int front = 0,rear = 0;
struct TreeNode*p = root;
// 将根节点加入队列
queue[rear++] = p;
// 遍历
while ((rear != 0 && rear > front))
{
// [1] 出队列
p = queue[front++];
// [2] 访问节点
if (p)
printf(" %d ",p->val);
// [3] 将左节点入队列
if (p->left)
queue[rear++] = p->left;
// [4] 将右节点入队列
if (p->right)
queue[rear++] = p->right;
}
}
注:ここで指定されているアルゴリズム設計は、トップダウン、左から右の階層トラバーサルです。これをトップダウン、右から左の階層トラバーサルに変更する必要がある場合は、ループを変更するだけで済みますwhile
。手順[3]と[4]を入れ替えます。
この関数は、キューの先入れ先出し機能を使用してバイナリ ツリーを階層順に移動します。比較的シンプルで理解しやすく、あらゆる種類のバイナリ ツリーに適用できます。
二分木の結果の例
理論的結果:
コードの実行結果:
要約する
この記事では、バイナリ ツリーの 4 つの走査方法 (事前順序トラバーサル、順序トラバーサル、事後トラバーサル、階層トラバーサル) を紹介します。このうち、前順トラバーサル、順トラバーサル、後順トラバーサルを総称して深さ優先トラバーサルと呼び、階層型トラバーサルを幅優先トラバーサルと呼びます。
深さ優先トラバーサルと幅優先トラバーサルの両方に独自の特性があり、さまざまな問題を解決するためによく使用されます。深さトラバーサルは、ツリーの深さ、パスの問題、ノードの最長直径の解決など、グローバル情報を取得するためにツリー全体を走査する必要がある状況に適しています。幅走査は、バイナリ ツリーを層ごとに走査したり、バイナリ ツリーの最小深さを解決したりするなど、ツリーの同じ層内のノード間でターゲット ノードを見つける場合により適しています。