二分木
バイナリ ツリーは、各ノードが最大 2 つの子ノードを持つ特別なツリー状のデータ構造です。1 つのノードを親ノードと呼び、2 つの子ノードをそれぞれ左子ノードおよび右子ノードと呼びます。
1.二分木とは何ですか
バイナリ ツリーは、各ノードが最大 2 つの子ノードを持つ特別なツリー状のデータ構造です。各ノードにはデータ要素とその左右の子ノードへのポインタが含まれています。左側の子ノードの値は親ノードの値以下であり、右側の子ノードの値は親ノードの値より大きいです。このプロパティにより、バイナリ ツリーの検索、挿入、削除操作が非常に効率的になります。
A
/ \
B C
/ \ \
D E F
バイナリ ツリーは、階層構造を持つデータをモデル化するためによく使用されます。その一般的なアプリケーションには、検索アルゴリズム (二分探索ツリーなど)、式ツリー、ハフマン コーディング ツリーなどが含まれます。バイナリ ツリーでは、事前順序トラバーサル、順序トラバーサル、事後順序トラバーサルなど、さまざまなトラバーサル方法を使用してノードにアクセスできます。
第二に、二分木トラバーサル法
1. 事前注文トラバーサル
ルート ノードから開始して、最初にルート ノードにアクセスし、次に左右のサブツリーで事前順序トラバーサルを再帰的に実行します。この走査を使用して、ツリー構造全体を複製できます。
次のバイナリ ツリーがあるとします。
1
/ \
2 3
/ \ \
4 5 6
まず、ルート ノード 1 にアクセスし、次に左側のサブツリーをたどります。左側のサブツリーのルート ノードは 2 です。引き続きそこにアクセスし、左側のサブツリーをトラバースします。左側のサブツリーのルート ノードは 4 です。引き続きアクセスしますが、左側と右側のサブツリーがないことがわかります。そのため、ノード 2 に戻り、右側のサブツリーをたどります。
右のサブツリーのルート ノードは 5 です。引き続きアクセスしますが、左右のサブツリーがないことがわかります。そのため、ノード 2 に戻り、次にルート ノード 1 に戻り、右のサブツリーをたどります。ルートノード。
右のサブツリーのルート ノードは 3 です。引き続きそこにアクセスし、次にその左のサブツリーをトラバースします。左側のサブツリーは空であるため、ノード 3 に戻り、その右側のサブツリーをたどります。
右のサブツリーのルート ノードは 6 です。引き続きアクセスしますが、左右のサブツリーがないことがわかります。そのため、ノード 3 に戻り、次にルート ノード 1 に戻り、トラバーサルが終了します。
最终的先序遍历结果是:1 2 4 5 3 6。
以下は、事前注文トラバーサルの具体的な手順を示す図です。
1
/ \
2 3
/ \ \
4 5 6
- ルートノード1にアクセスします
- 左側のサブツリーをトラバースします
- ルートノード2にアクセスします
- 左側のサブツリーをトラバースします
- ルートノード4にアクセスします
- 左側のサブツリーが空の場合は、戻り値を返します。
- 右側のサブツリーをトラバースする
- ルートノード5にアクセスします
- 左側のサブツリーが空の場合は、戻り値を返します。
- 右側のサブツリーをトラバースする
- ルートノード3にアクセスします
- 左側のサブツリーをトラバースします (空、リターン)
- 右側のサブツリーをトラバースする
- ルートノード6にアクセスします
- 左側のサブツリーが空の場合は、戻り値を返します。
最终的先序遍历结果是:1 2 4 5 3 6。
2. インオーダートラバーサル
インオーダー トラバーサルは、バイナリ ツリーをトラバースする方法です。トラバーサルの順序は、ルート ノードから開始して、最初に左側のサブツリーでインオーダー トラバーサルを再帰的に実行し、次にルート ノードにアクセスし、最後に右側のサブツリーでインオーダー トラバーサルを再帰的に実行します。二分探索ツリーの場合、順番に走査することで昇順の出力を実現できます。
バイナリ ツリーの例を次に示します。
1
/ \
2 3
/ \ / \
4 5 6 7
中序遍历的结果为:4, 2, 5, 1, 6, 3, 7。
順序トラバーサルのプロセスは、再帰またはスタックを使用して実装できます。2 つの方法については、以下で詳しく説明します。
(1) 再帰的手法
再帰が最も簡単な方法です。具体的な手順は次のとおりです。
- 現在のノードが空かどうかを返します。
- 現在のノードの左側のサブツリーを再帰的に走査します。
- 現在のノードにアクセスします。
- 現在のノードの右のサブツリーを再帰的に走査します。
以下は、再帰を使用した順序トラバーサルのサンプル コードです。
#include <stdio.h>
#include <stdlib.h>
struct Node {
int val;
struct Node* left;
struct Node* right;
};
void inorderTraversal(struct Node* root) {
if (root == NULL) {
return;
}
inorderTraversal(root->left);
printf("%d ", root->val);
inorderTraversal(root->right);
}
(2)スタック方式
stack メソッドは、スタックを使用してトラバーサル プロセスを支援します。具体的な手順は次のとおりです。
- 空のスタックとルート ノードへのポインターを初期化します。
- スタックが空でない場合、またはポインターが空でない場合は、次の操作を実行します。
- ポインタが null でない場合は、ポインタをスタックにプッシュし、ポインタがその左のサブツリーを指すようにします。
- ポインタが空の場合は、スタックの最上位要素をポップしてアクセスし、ポインタをその右のサブツリーを指します。
- スタックが空でポインタが空の場合、トラバーサルは終了します。
以下は、スタック メソッドを使用した順序トラバーサルのサンプル コードです。
#include <stdio.h>
#include <stdlib.h>
struct Node {
int val;
struct Node* left;
struct Node* right;
};
void inorderTraversal(struct Node* root) {
struct Node* stack[1000];
int top = -1;
struct Node* curr = root;
while (top != -1 || curr != NULL) {
if (curr != NULL) {
stack[++top] = curr;
curr = curr->left;
} else {
curr = stack[top--];
printf("%d ", curr->val);
curr = curr->right;
}
}
}
再帰的手法であってもスタック手法であっても、その時間計算量は O(n) です。ここで、n はバイナリ ツリー内のノードの数です。
3. ポストオーダートラバーサル
ポストオーダー トラバーサル (Postorder Traversal) は、バイナリ ツリーのトラバーサル手法であり、そのトラバーサル順序は、最初に左側のサブツリーをトラバースし、次に右側のサブツリーをトラバースし、最後にルート ノードにアクセスします。このトラバーサル手法は、空きメモリの回復やデストラクタ呼び出しによく使用されます。
バイナリ ツリーの例を次に示します。
1
/ \
2 3
/ \ \
4 5 6
ポストオーダートラバーサルの順序に従って、最初に左側のサブツリーをトラバースし、次に右側のサブツリーをトラバースし、最後にルート ノードにアクセスする必要があります。したがって、バイナリ ツリーのポストオーダー トラバーサルの結果は次のようになります。
4 -> 5 -> 2 -> 6 -> 3 -> 1
次に、C 言語を使用して、バイナリ ツリーのポストオーダー トラバーサルを実装します。
まず、ノードの値と左右の子ノードへのポインタを含むバイナリ ツリーの構造を定義する必要があります。
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树的结构
struct TreeNode {
int val;
struct TreeNode* left;
struct TreeNode* right;
};
次に、新しいバイナリ ツリー ノードを作成する関数が必要です。
// 创建一个新的二叉树节点
struct TreeNode* createNode(int val) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->val = val;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
次に、二分木のポストオーダートラバーサル関数を実装してみましょう。
// 后序遍历二叉树
void postorderTraversal(struct TreeNode* root) {
if (root == NULL) {
return;
}
postorderTraversal(root->left); // 遍历左子树
postorderTraversal(root->right); // 遍历右子树
printf("%d ", root->val); // 访问根节点
}
ポストオーダートラバーサル関数では、まず現在のノードが空かどうかを判定し、空であればそれを返します。次に、左のサブツリーと右のサブツリーを順番に再帰的にたどり、最後にルート ノードにアクセスします。
最後に、main 関数でサンプルのバイナリ ツリーを作成し、ポストオーダー トラバーサル関数を呼び出してそれを走査します。
int main() {
// 创建示例二叉树
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->right = createNode(6);
// 后序遍历二叉树
printf("后序遍历结果:");
postorderTraversal(root);
printf("\n");
return 0;
}
上記のコードを実行すると、出力は次のようになります。
后序遍历结果:4 5 2 6 3 1。
3. 二分木の特徴
トラバーサル手法に加えて、バイナリ ツリーには他にもいくつかの特性があります。
- 完全な二分木: 完全な二分木では、最後の層を除いて、各層のノードが埋められ、最後の層のノードが左から右の順に埋められます。このツリー構造は配列に効率的に格納できます。
- 完全なバイナリ ツリー: 完全なバイナリ ツリーは、すべてのノードが 0 または 2 つの子ノードを持つバイナリ ツリーです。完全なバイナリ ツリーでは、すべてのリーフ ノードが同じレベルにあります。
- バランスのとれたバイナリ ツリー: バランスのとれたバイナリ ツリーでは、任意のノードの左のサブツリーと右のサブツリーの高さの差は最大 1 です。これにより、ツリーのバランスが保たれ、検索、挿入、削除の操作の効率が向上します。
- 二分探索木: 二分探索木 (BST) は、任意のノードについて、その左側のサブツリーのすべてのノードがそれより小さい値を持ち、右側のサブツリーのすべてのノードがそれより小さい値を持つという特性を持つ二分木です。それよりも大きいです。このプロパティにより、二分探索ツリーでの検索、挿入、および削除操作が非常に効率的に行われます。
全体として、バイナリ ツリーは幅広い用途を持つ重要なデータ構造です。そのノードは最大 2 つの子ノードを持つことができ、さまざまな走査方法を使用してノードにアクセスして処理できます。
4番目、バイナリツリーの実装
1. 構造を定義する
まず、二分木のノードを表す構造を定義します。各ノードは 1 つのデータ部分と 2 つのポインター部分で構成され、それぞれ左の子ノードと右の子ノードを指します。
typedef struct Node {
int data;
struct Node *left;
struct Node *right;
} Node;
2. 新しいノードを作成する
次に、新しいノードを作成する関数を実装しますcreateNode
。この関数は、メモリの割り当てとノードのデータ部分の初期化を担当します。メモリの割り当てに失敗すると、エラー メッセージが出力され、プログラムが終了します。
Node *createNode(int data) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
3. ノードを挿入する
次に、ノードを挿入する関数を実装しますinsertNode
。この関数は、渡されたデータ値に従って、新しいノードを適切な位置に挿入します。ツリーが空の場合、関数は新しいノードを作成し、それをルート ノードにします。それ以外の場合は、データ値のサイズに応じて、左または右のサブツリーにノードを再帰的に挿入します。
Node *insertNode(Node *root, int data) {
if (root == NULL) {
return createNode(data);
} else if (data < root->data) {
root->left = insertNode(root->left, data);
} else if (data > root->data) {
root->right = insertNode(root->right, data);
}
return root;
}
その後、pre-order トラバーサル、in-order トラバーサル、post-order トラバーサルの 3 つのトラバーサル メソッドを実装しました。これらの走査方法は、バイナリ ツリー内のノードを特定の順序で訪問します。
4. 事前注文トラバーサル
プレオーダートラバーサル関数はpreOrder
、ルートノード、左サブツリー、右サブツリーの順にツリーをトラバースし、ノードのデータ値を出力します。
void preOrder(Node *root) {
if (root != NULL) {
printf("%d ", root->data);
preOrder(root->left);
preOrder(root->right);
}
}
5. インオーダートラバーサル
inorder traversal 関数は、inOrder
左サブツリー、ルート ノード、右サブツリーの順序でツリーを走査し、ノードのデータ値を出力します。
void inOrder(Node *root) {
if (root != NULL) {
inOrder(root->left);
printf("%d ", root->data);
inOrder(root->right);
}
}
6. ポストオーダートラバーサル
事後走査関数は、postOrder
左サブツリー、右サブツリー、ルート ノードの順にツリーを走査し、ノードのデータ値を出力します。
void postOrder(Node *root) {
if (root != NULL) {
postOrder(root->left);
postOrder(root->right);
printf("%d ", root->data);
}
}
7.メイン機能
最後に、main
関数内で空のツリーのルート ノードを作成し、insertNode
関数を通じてデータを挿入します。次に、3 つのトラバーサル関数をそれぞれ呼び出し、トラバーサル結果を出力します。
int main() {
Node *root = NULL;
root = insertNode(root, 50);
insertNode(root, 30);
insertNode(root, 20);
insertNode(root, 40);
insertNode(root, 70);
insertNode(root, 60);
insertNode(root, 80);
printf("先序遍历结果:");
preOrder(root);
printf("\n中序遍历结果:");
inOrder(root);
printf("\n后序遍历结果:");
postOrder(root);
return 0;
}
これは、単純なバイナリ ツリー実装の詳細なコードです。バイナリ ツリーがどのように機能し、どのように実装されるかをよりよく理解するのに役立つことを願っています。
5、完全なコード
#include <stdio.h>
#include <stdlib.h>
// 节点结构
typedef struct Node {
int data;
struct Node *left;
struct Node *right;
} Node;
// 创建一个新节点
Node *createNode(int data) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 插入节点
Node *insertNode(Node *root, int data) {
if (root == NULL) {
return createNode(data);
} else if (data < root->data) {
root->left = insertNode(root->left, data);
} else if (data > root->data) {
root->right = insertNode(root->right, data);
}
return root;
}
// 先序遍历
void preOrder(Node *root) {
if (root != NULL) {
printf("%d ", root->data);
preOrder(root->left);
preOrder(root->right);
}
}
// 中序遍历
void inOrder(Node *root) {
if (root != NULL) {
inOrder(root->left);
printf("%d ", root->data);
inOrder(root->right);
}
}
// 后序遍历
void postOrder(Node *root) {
if (root != NULL) {
postOrder(root->left);
postOrder(root->right);
printf("%d ", root->data);
}
}
int main() {
Node *root = NULL;
root = insertNode(root, 50);
insertNode(root, 30);
insertNode(root, 20);
insertNode(root, 40);
insertNode(root, 70);
insertNode(root, 60);
insertNode(root, 80);
printf("先序遍历结果:");
preOrder(root);
printf("\n中序遍历结果:");
inOrder(root);
printf("\n后序遍历结果:");
postOrder(root);
return 0;
}