友人の皆さん、また会いましょう。今回は、連鎖二分木に関する関連知識ポイントを説明します。読んだ後でインスピレーションがあれば、リンクを 3 つ残してください。ご多幸をお祈りします。完了しました。 !
データ構造とアルゴリズム コラム:データ構造とアルゴリズム
個人ホームページ: stackY、
C言語コラム: C言語:エントリーからマスターまで
目次
4. バイナリ ツリーが完全なバイナリ ツリーであるかどうかを判断します。
序文:
チェーンストレージ:
二分木の連結記憶構造とは、二分木を表現するために連結リストが使用されること、すなわち、要素の論理的関係を示すためにリンクが使用されることを意味する。通常の方法では、リンク リストの各ノードは、データ フィールドと左右のポインタ フィールドの 3 つのフィールドで構成され、左と右のポインタは、左の子と左の子がポイントするリンクのストレージ アドレスを与えるために使用されます。ノードの右の子が見つかります。チェーンの構造はさらに二股チェーンと三股チェーンに分かれており、徐々に難易度が上がっていきますので、まずは簡単なものから学んでいきます。
事前の指示:
バイナリ ツリーの基本操作を学習する前に、バイナリ ツリーを作成してから、関連する基本操作を学習する必要があります。そこで、まず最も単純なバイナリ ツリーを手動で作成し、次にバイナリ ツリーに関する操作をいくつか学び、後で実際のバイナリ ツリーを一緒に作成してみましょう。バイナリ ツリーには、最初のルートと、それぞれ左と右の子のノードへの 2 つのポインタがあるため、バイナリ ツリーをすばやく簡単に開始するには、構造を定義する必要があります。引き続きモジュールを分割してバイナリ ツリーを作成し、ヘッダー ファイル Tree.h と 2 つのソース ファイル Tree.c 、 Test.cを作成します。ヘッダー ファイル: Tree.htypedef int TreeDataType; typedef struct BinaryTreeNode { TreeDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; //快速创建一个二叉树 BTNode* CreatBinaryTree();
ソースファイル: Tree.c
//快速创建一个二叉树 BTNode* BuyBTNode(TreeDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { perror("malloc fail"); return NULL; } node->data = x; node->left = NULL; node->right = NULL; return node; } BTNode* CreatBinaryTree() { //创建节点 BTNode* node1 = BuyBTNode(1); BTNode* node2 = BuyBTNode(2); BTNode* node3 = BuyBTNode(3); BTNode* node4 = BuyBTNode(4); BTNode* node5 = BuyBTNode(5); BTNode* node6 = BuyBTNode(6); BTNode* node7 = BuyBTNode(7); //链接成树 node1->left = node2; node1->right = node3; node2->left = node4; node2->right = node5; node3->left = node6; node3->right = node7; return node1; }
注: 上記のコードはバイナリ ツリーを作成する方法ではありません。実際のバイナリ ツリーを作成する方法については、後で詳しく説明します。バイナリ ツリーの基本的な操作を説明する前に、バイナリ ツリーの概念を確認してください。バイナリ ツリーは次のとおりです。1.空の木2.空ではない: ルート ノード、ルート ノードの左側のサブツリー、およびルート ノードの右側のサブツリー。この概念からわかるように、二分木の定義は再帰的であるため、以降の基本操作は基本的にこの概念に従って実装されます。
1. バイナリツリートラバーサル
バイナリ ツリーをトラバースするにはいくつかの方法があります。
1.事前注文: 最初にルート ノードにアクセスし、次に左側のサブツリーにアクセスし、最後に右側のサブツリーにアクセスします。
2. Inorder : 最初に左側のサブツリーにアクセスし、次にルート ノードにアクセスし、最後に右側のサブツリーにアクセスします。
3. Postorder : 最初に左側のサブツリーにアクセスし、次に右側のサブツリーにアクセスし、最後にルート ノードにアクセスします。
4.シーケンス: レイヤーごとにアクセスします。(それについては後で詳しく説明します)
それでは、作成したバイナリ ツリーの前、中、後順のトラバーサル アクセスを使用すると何が得られるのでしょうか? 一緒に絵を描きましょう:
1. 前文:
最初に 1 のルートにアクセスし、次に 1 の左側のサブツリーのルート 2 にアクセスし、次に 2 の左側のサブツリーのルートにアクセスし、次に 4 の左側のサブツリーのルートにアクセスします。 は空であるため、アクセスする必要があります4 の右のサブツリーのルートはまだ 4 が空であるため、1 レベル上がる必要があります。2 の右のサブツリーのルートは 5 で、次に 5 の左のサブツリーにアクセスすると空です。 5 の右側のサブツリーを訪問します。まだ空です。その後、もう一度進みます。1 つ上のレベルに移動します。この時点では、1 の左側のブランチはすべて訪問されているため、右側のサブツリー 3 を訪問する必要があります。すべての訪問が完了するまで
最終的な走査結果は次のようになります: 1 2 4 NN 5 NN 3 6 NN 7 NN
2. 順番に:
インオーダートラバーサルでは最初に左側のサブツリーにアクセスするため、1 の左側のサブツリーにアクセスするまでずっと下に移動し、次に 1 の左側のサブツリーでその左側のサブツリーを見つけ、空のツリーが見つかるまでこれを繰り返す必要があります。 , この時点でわかるのは、4 の左側のサブツリーが空であるということです。このとき、最初に 4 にアクセスする必要があり、次に 4 の右側のサブツリーも空です。このとき、1 レベル上にアクセスして、 2の右側のサブツリーを訪問し、次に2の右側のサブツリーの左側のサブツリーを訪問します。この時点では、5の左側のサブツリーは空であるため、5を訪問してから、その右側のサブツリーを訪問する必要があります。 5. この時点で、1 つ上のレベルに移動する必要があります。この時点では、1 にアクセスし、1 の右側のサブツリーで前のアクションを繰り返し続ける必要があります。
したがって、最終的な走査結果は次のようになります: N 4 N 2 N 5 N 1 N 6 N 3 N 7 N
3.事後注文:
事後トラバーサルでは、最初に左側のサブツリーにアクセスし、次に右側のサブツリーにアクセスし、最後にルートにアクセスします。まず最初に 1 の左側のサブツリーにアクセスし、次に 1 の左側のサブツリーの左側のサブツリーにアクセスし、以下を訪問するまで続きます。が空の場合、これは上の層に戻り、次に右のサブツリーを訪問し、次にルートを訪問する、というように、4 の左のサブツリーを訪問すると空になり、その後 4 の右のサブツリーを訪問すると同様になります。空の場合は 4 にアクセスします。各ノードは同じです...
最終的な走査結果は次のようになります: NN 4 NN 5 2 NN 6 NN 7 3 1
コード:
ルールによれば、二分木の走査には以下が含まれます: preorder/inorder/postorder 再帰構造の走査:1. プレオーダー トラバーサル (プレオーダー トラバーサル、プレオーダー トラバーサルとも呼ばれます) - ルート ノードを訪問する操作は、左右のサブツリーをトラバースする前に発生します。2. インオーダー トラバーサル (Inorder Traversal) - ルート ノードを訪問する操作は、その左右のサブツリー (間) をトラバースする際に発生します。3. ポストオーダー トラバーサル — ルート ノードを訪問する操作は、その左右のサブツリーをトラバースした後に発生します。ヘッダー ファイル: Tree.h
//前序遍历 void PrevOrder(BTNode* root); //中序遍历 void InOrder(BTNode* root); //后序遍历 void PostOrder(BTNode* root);
ソースファイル: Tree.c
//前序遍历 void PrevOrder(BTNode* root) { if (root == NULL) { printf("N "); return; } //先访问根节点 printf("%d ", root->data); //再访问左子树 PrevOrder(root->left); //最后访问右子树 PrevOrder(root->right); } //中序遍历 void InOrder(BTNode* root) { if (root == NULL) { printf("N "); return; } //先访问左子树 InOrder(root->left); //再访问根 printf("%d ", root->data); //在访问右子树 InOrder(root->right); } //后序遍历 void PostOrder(BTNode* root) { if (root == NULL) { printf("N "); return; } //先访问左子树 PostOrder(root->left); //再访问右子树 PostOrder(root->right); //最后访问根 printf("%d ", root->data); }
ソースファイル: Test.c
#include "Tree.h" int main() { BTNode* root = CreatBinaryTree(); //前序遍历 PrevOrder(root); printf("\n"); //中序遍历 InOrder(root); printf("\n"); //后序遍历 PostOrder(root); printf("\n"); return 0; }
再帰的展開図:
プロローグ:
順番通りに:
後続:
ここではエディタはポストシーケンスの再帰展開図を描画しません メソッドのロジックはプレシーケンス、インシーケンスとまったく同じですが、アクセス順序が異なります。自分自身を練習のために。
1.2 二分木のノード数
バイナリ ツリー内のノードの数を見つけるには 2 つの方法があります。
1. 使用状況カウンター統計をトラバースします。(推奨されません)
2. 再帰を使用して直接計算します。
最初の方法が推奨されないのはなぜですか?
というのも、カウンタを使う場合はグローバル変数としてカウンタを定義する必要があり、一度使った後は手動でリセットする必要があり面倒です。
ここでは、再帰を使用して統計を直接実行する 2 番目の方法を使用します。
再帰を使用して直接カウントする場合に注意すべき点は、ルート ノードの左のサブツリーと右のサブツリーが両方とも空の場合、戻り値は 0 ではなく 1 になります。ノードは空ではないため、ルート ノード自体とその左右のサブツリー ノードの合計を返す必要があります。
コード:
ヘッダー ファイル: Tree.h
//二叉树的节点个数 int BTreeSize(BTNode* root);
ソースファイル: Tree.c
//二叉树的节点个数 int BTreeSize(BTNode* root) { //为空就返回0 if (root == NULL) { return 0; } //不为空就返回根节点和它的左右子树节点的和 return BTreeSize(root->left) + BTreeSize(root->right) + 1; }
再帰的展開図:
1.3 二分木の葉ノードの数
二分木の葉ノードは、その左右の部分木が空である場合に葉ノードと呼ばれます。したがって、ルートノードの左右の部分木が空であるという事実と、再帰的方法は引き続き使用されますが、ルート ノードには左右のサブツリーが 1 つしかないため、リーフ ノードではありません。
コードデモ:
ヘッダー ファイル: Tree.h
//求叶子节点的个数 int BTreeLeafSize(BTNode* root);
ソースファイル: Tree.c
//求叶子节点的个数 int BTreeLeafSize(BTNode* root) { //左右子树只存在一个或者为空树 if (root == NULL) { return 0; } //左右子树都不存在 if (root->left == NULL && root->right == NULL) { return 1; } //递归调用下面的子树 return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); }
再帰的展開図:
1.4 バイナリツリーの高さ(深さ)
二分木の高さを求めるには、再帰的走査の方法も使用します。つまり、左右のサブツリーの高さを見つけて、どちらの木の高さが大きいかを比較し、どちらの木を返します。
1 の高さが必要な場合は、その左右のサブツリー 2 と 3 の高さを計算する必要があり、2 と 3 の高さが必要な場合は、左右のサブツリーの高さを個別に計算する必要があります。 4 の左右のサブツリーの高さは 0 です。4 が 2 に戻ると、返される高さは 0+1 になります (0 はその左右のサブツリーの高さが 0 であることを意味し、1 はそれ自身のサブツリーの高さが 0 であることを意味します)高さは 1)、つまり、戻るときに独自の高さを追加して、2 の左側のサブツリーの高さが 1 になるようにします。次に、2 の右側のサブツリー 5 の高さを計算し、5 の高さと高さを計算します。 5の左右の部分木のうち、5の左側の部分木の高さは1、右側の部分木の高さは1です。木の高さは0で、大きい方を1として、5が戻ると2、それ自体の高さを 2 に追加します。これにより、2 の左右のサブツリーの高さは 2 になります。したがって、2 が 1 に戻ったら、独自の高さを追加します。高さは 3 になります。....というように、バイナリ全体が完成するまで続きます。木を横切っていきます。したがって、ツリー全体の最終的な高さは 4 になります。
コードデモ:ヘッダー ファイル: Tree.h
//二叉树的高度 int BTreeHight(BTNode* root);
ソースファイル: Tree.c
//二叉树的高度 int BTreeHight(BTNode* root) { //是否为空树 if (root == NULL) { return 0; } //保存左右子树的高度 BTNode* LeftTreeHight = BTreeHight(root->left); BTNode* RightTreeHight = BTreeHight(root->right); return LeftTreeHight > RightTreeHight ? LeftTreeHight + 1 : RightTreeHight + 1; //加上自己本身的高度1 }
注: ここで別の書き方もあります。左右のサブツリーの高さを保存せず、直接三眼操作を実行してから再帰することも可能ですが、ツリー内に多くのノードがある場合、多くのエラーが発生します。再帰の大幅な削減につながります。
コードデモ:
//不推荐的写法 //二叉树的高度 int BTreeHight(BTNode* root) { //是否为空树 if (root == NULL) { return 0; } //直接递归 return BTreeHight(root->left) > BTreeHight(root->right) ? BTreeHight(root->left) + 1 : BTreeHight(root->right) + 1; //加上自己本身的高度1 }
1.5 k番目の層のノード数
k 番目の層のノードの数が必要です。ここでは k 番目の層を見つけることが重要な問題なので、それを複数の下位問題に変換する必要があります。k 番目の層が必要な場合は、次のようになります。その左と右のサブツリーの k を求めます -1 層のノードの数、その終了マークは次のようになります: k==1 でノードが空でない場合、それはノードとみなされます。特殊なケースもあります、空のツリーはノードがないことを意味します。また、k を検出する必要があり、k は 0 より大きくなります。
このツリーを逆さまにして、元の最初の層を 3 番目の層と見なすことができます。次に、その左右のサブツリーの 2 番目の層のノードの数を見つける必要があり、その後、それを左右のサブツリーに変換できます。このとき得られるノード数は第1層、第3層のノード数は3です。
コードデモ:
ヘッダー ファイル: Tree.h
//第k层节点的个数 int BTreeLeafKSize(BTNode* root, int k);
ソースファイル: Tree.c
//第k层节点的个数 int BTreeLeafKSize(BTNode* root, int k) { //判断k的合理性 assert(k > 0); //树为空 if (root == NULL) { return 0; } //树不为空,且k==1 if (k == 1 ) { //算一个有效结点 return 1; } //树既不为空,k也不为1,继续向下走 return BTreeLeafKSize(root->left, k - 1) + BTreeLeafKSize(root->right, k - 1); }
再帰的展開図:
1.6 二分木で値 x を持つノードを見つける
ノードの位置を見つけることは、サブ問題に変換することもできます。左右のサブツリーで値が x であるノードを見つけるために、事前順序トラバーサル プロセスが行われます。まずルートを見つけ、次に左右のサブツリーを見つけます。サブツリー、ノードの場合 ポイント内のデータが x の場合、このノードのアドレスを返します。ノードが見つからない場合は、NULL を返します。
事前順序トラバーサル:
最初にルートを見つけ、次に左右のサブツリーを見つけます。
1 は 6 ではない、次に 1 の左右のサブツリーに移動して検索、左側のサブツリー 2 は 6 ではない、次に 2 の左側のサブツリーに移動して検索、4 は 6 ではない、そして左側と右側に移動4 のサブツリーを調べて、左右のサブツリーがないことを確認します。上のレベルに戻り、2 の右のツリーに行って、5 が 6 ではないことを確認します。次に、5 の左右のサブツリーに進みます。左右のサブツリーがない場合は、上のレベルに戻り、1 ツリー検索の右側の子に移動します。3 は 6 ではありません。次に、3 の左側のサブツリーに移動して検索し、6 が 6 と正確に等しいことを確認します。 6のノードのアドレスを返します。コードデモ:
ヘッダー ファイル: Tree.h
//查找值为x的结点 BTNode* BTreeFind(BTNode* root, TreeDataType x);
ソースファイル: Tree.c
//查找值为x的结点 BTNode* BTreeFind(BTNode* root, TreeDataType x) { //判断是否为空树 if (root == NULL) { return NULL; } //找到了就返回地址 if (root->data == x) { return root; } //先去左树找,找到了就返回 BTNode* left = BTreeFind(root->left, x); if (left) { return left; } //再去右树找,找到了就返回 BTNode* right = BTreeFind(root->right, x); if (right) { return right; } //如果左右树都没有找到就返回NULL return NULL; }
再帰的展開図:
2. 二分木のレベル順走査
レベル順序トラバーサル: 前順序トラバーサル、順序内トラバーサル、後順序トラバーサルに加えて、バイナリ ツリーのレベル順序トラバーサルも実行できます。バイナリ ツリーのルート ノードが 1 層にあると仮定すると、層順序のトラバーサルは、バイナリ ツリーが配置されているバイナリ ツリーのルート ノードから開始し、最初に最初の層のルート ノードを訪問し、次に最初の層のノードを訪問します。 2 番目のレイヤーを左から右に、次に 3 番目のレイヤーのノードというように、上から下、左から右にレイヤーごとにツリーのノードを訪問するプロセスがレイヤー順序のトラバーサルです。層の順序のトラバーサルに使用する必要があるツールは、前に作成したキューです。このキューには先入れ先出しの特性があるため、最初にバイナリ ツリーの最初の層をキューにチェックインし、次に最初の層をデキューし、最初の層を配置します。層の左右のサブツリーがキューに挿入されます。簡単に理解すると、このキューでは、ルート ノードが出力されると、その左右 2 つのサブツリーが取り込まれます。このようにして、レイヤー順序の横断が実現されます。コードデモ:
ヘッダー ファイル: Queue.h
#pragma once // 队列 #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> //链式结构:表示队列 typedef struct BinaryTreeNode* QDataType; typedef struct QueueNode { struct QueueNode* next; QDataType data; }QNode; //队列的结构 typedef struct Queue { //头指针 QNode* phead; //尾指针 QNode* ptail; //队列中有效元素个数 int size; }Queue; //初始化队列 void QueueInit(Queue* pq); //销毁队列 void QueueDestroy(Queue* pq); //队尾入队列 void QueuePush(Queue* pq, QDataType x); //检测队列是否为空 bool QueueEmpty(Queue* pq); //对头出队列 void QueuePop(Queue* pq); //获取队头的元素 QDataType QueueFront(Queue* pq); //获取队尾的元素 QDataType QueueBack(Queue* pq); //获取队列有效元素的个数 int QueueSize(Queue* pq);
ソースファイル: Queue.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" //初始化队列 void QueueInit(Queue* pq) { assert(pq); pq->phead = NULL; pq->ptail= NULL; pq->size = 0; } //销毁队列 void QueueDestroy(Queue* pq) { assert(pq); QNode* cur = pq->phead; while (cur) { QNode* next = cur->next; free(cur); cur = next; } pq->phead = pq->ptail = NULL; pq->size = 0; } //队尾入队列 void QueuePush(Queue* pq, QDataType x) { assert(pq); //创建新的结点 QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail"); return; } newnode->next = NULL; newnode->data = x; //第一次尾插 if (pq->ptail == NULL) { assert(pq->phead == NULL); pq->phead = pq->ptail = newnode; } else { pq->ptail->next = newnode; pq->ptail = newnode; } //有效元素++ pq->size++; } //检测队列是否为空 bool QueueEmpty(Queue* pq) { assert(pq); //1.判断头、尾指针 /* return pq->phead == NULL && pq->ptail == NULL; */ //2.判断有效元素个数 return pq->size == 0; } //队头出队列 void QueuePop(Queue* pq) { assert(pq); //判空 assert(!QueueEmpty(pq)); //一个结点 if (pq->phead->next == NULL) { //直接释放 free(pq->phead); pq->phead = pq->ptail = NULL; } //多个结点 else { //记录头的下一个 QNode* Next = pq->phead->next; //释放 free(pq->phead); //更新头结点 pq->phead = Next; } pq->size--; } //获取队头的元素 QDataType QueueFront(Queue* pq) { assert(pq); //先判空 assert(!QueueEmpty(pq)); return pq->phead->data; } //获取队尾的元素 QDataType QueueBack(Queue* pq) { assert(pq); //先判空 assert(!QueueEmpty(pq)); return pq->ptail->data; } //获取队列有效元素的个数 int QueueSize(Queue* pq) { assert(pq); return pq->size; }
ヘッダー ファイル: Tree.h
//层序遍历 void LevelOrder(BTNode* root);
ソースファイル: Tree.c
#include "Queue.h" #include "Tree.h" //层序遍历 void LevelOrder(BTNode* root) { //创建队列 Queue q; QueueInit(&q); //先将第一层的插入队列 if (root) QueuePush(&q, root); while (!QueueEmpty(&q)) { //保存队头的数据 BTNode* front = QueueFront(&q); //删除队头 QueuePop(&q); printf("%d ", front->data); //将第二层的数据带入队列 if (front->left) QueuePush(&q, front->left); if (front->right) QueuePush(&q, front->right); } printf("\n"); QueueDestroy(&q); }
3. バイナリツリーの作成と破棄
これまでに使用したバイナリ ツリーはすべてすぐに作成されますが、これは実際のバイナリ ツリーの作成方法ではないため、実際にバイナリ ツリーを作成する方法を学びましょう。
3.1 二分木の作成
バイナリ ツリーの作成に関して、まず OJ の質問を見てみましょう。
バイナリツリーの作成: https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef?tpId=60&&tqId=29483&rp=1&ru=/activity/oj&qru=/ta/tsing-kaoyan/question-ranking
この質問の説明の意味は、配列を preorder で走査してバイナリ ツリーを作成し、バイナリ ツリーのインオーダー トラバーサルを使用して印刷することです。配列を preorder で走査することでバイナリ ツリーを作成する効果を分析できます。テストを使用します。例を使用して説明します: ABC##DE#G##F###
バイナリ ツリーの事前順序トラバーサルでは、まずルートにアクセスし、次に左側のサブツリーにアクセスし、次に右側のサブツリーにアクセスします。その後、ツリーの左右のサブツリーが空でない限り、分解を続けることができます。左のサブツリーと右のサブツリー Tree に変換すると、事前順序トラバーサルによる ABC##DE#G##F### は次のようになります。
問題解決のアイデア:
配列を走査し、それが '#' である場合、ノードを作成する必要はありません。配列の次の要素にアクセスし続けるだけです。 '#' でない場合は、作成後にノードを作成できます。完了、配列の次の要素へのアクセスを継続します はい、ノードを作成した後、その左側のサブツリーを作成し続け、左側のサブツリーが作成された後に右側のサブツリーを作成する必要があります。このとき、再帰的に完了する必要があります。ノードの左右のサブツリー ノードを作成し、順序を使用したトラバース (左のサブツリー、ルート、右のサブツリー) が行われます。
コードデモ:
#include <stdio.h> #include <stdlib.h> typedef int TreeDataType; typedef struct BinaryTreeNode { TreeDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; //节点的创建 BTNode* BuyBTNode(TreeDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { perror("malloc fail"); return NULL; } node->data = x; node->left = NULL; node->right = NULL; return node; } //创建二叉树 BTNode* CreatBinaryTree(char* a, int* pi) { //判断是否为'#' if(a[*pi] == '#') { (*pi)++; return NULL; } //不为'#'就创建 BTNode* root = BuyBTNode(a[*pi]); //继续访问下一个元素 (*pi)++; //递归创建它的左子树 root->left = CreatBinaryTree(a,pi); //递归创建它的右子树 root->right = CreatBinaryTree(a,pi); return root; } //前序遍历 void InOrder(BTNode* root) { if(root == NULL) { return; } InOrder(root->left); printf("%c ",root->data); InOrder(root->right); } int main() { char a[100]; scanf("%s",a); int i = 0; //传递下标 BTNode* root = CreatBinaryTree(a, &i); InOrder(root); return 0; }
通常のバイナリ ツリー作成プロセスは、この OJ の質問と似ており、プリオーダー トラバーサルを使用してバイナリ ツリーを作成するため、ここで多くの人が疑問を持つでしょう。なぜ添え字を渡すのですか?
添字を渡さず、関数内で直接添字を設定した場合、各関数呼び出しの添字は初期値になります。これは、関数再帰が関数スタック フレームを作成するときに各スタック フレームが独立しているため、各スタック フレームに添字が存在するためです。関数内でこの添字をスタック フレーム内で操作します。他のスタック フレーム内の添字は影響を受けません。その後、関数の外に添字を設定し、この添字のアドレスを関数に渡します。その後、各関数呼び出しは次のようになります。このアドレスに移動して添字を見つけます。逆参照することで添字を変更できるため、複数のスタック フレームで 1 つの添字を使用できます。
バイナリ ツリーを作成します。
ヘッダー ファイル: Tree.h
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* CreatBinaryTree(char* a, int* pi);
ソースファイル: Tree.c
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 //#代表空树 //创建二叉树 BTNode* CreatBinaryTree(char* a, int* pi) { //判断是否为'#' if (a[*pi] == '#') { (*pi)++; return NULL; } //不为'#'就创建 BTNode* root = BuyBTNode(a[*pi]); //继续访问下一个元素 (*pi)++; //递归创建它的左子树 root->left = CreatBinaryTree(a, pi); //递归创建它的右子树 root->right = CreatBinaryTree(a, pi); return root; }
3.2 バイナリツリーの破壊
バイナリ ツリーを破棄する最も適切な方法は、バイナリ ツリーを破棄するポストオーダー トラバーサルです。ポストオーダー トラバーサルでは、最初に左側のサブツリーにアクセスし、次に右側のサブツリーにアクセスし、最後にルートにアクセスします。これにより、バイナリ ツリー全体が非常によく破壊できます。
コードデモ:
ヘッダー ファイル: Tree.h
//销毁二叉树 void BTDestroy(BTNode* root);
ソースファイル: Tree.c
//销毁二叉树 void BTDestroy(BTNode* root) { //后序遍历销毁 //为空就返回 if (root == NULL) return; //先走左树再走右树 BTDestroy(root->left); BTDestroy(root->right); //释放 free(root); }
4. バイナリ ツリーが完全なバイナリ ツリーであるかどうかを判断します。
完全な二分木の特徴: 木の高さが h であると仮定すると、最初の h-1 層は満たされている必要があり、最後の層は満たされていても構いませんが、連続していなければなりません。では、ここのベテランの多くは二分木のノード数で判断してみようと考えると思いますが、そういう考え方もあり得るのですが、どうでしょうか?最後の層が不連続な場合はピットになるため、その連続性から始める必要があります。完全な二分木は連続構造であり、完全な二分木は層順序のトラバーサルを使用した連続構造であるため、完全な二分木であると判断しますツリーにはレベル順序のトラバースが必要です。
層の順序を使用してバイナリ ツリーを走査します。キューの先頭のデータが空であることが判明した場合は、判定を行う必要があります。後続のデータがすべて空であれば、バイナリ ツリーが完全なバイナリであることが証明されます。空でない場合は、完全なバイナリ ツリーではありません。次の図を作成して確認できます。
次に、最初にレイヤーを走査することから始めることができます。デキューするとき、空のスペースに遭遇すると最初に飛び出し、次にキュー内の残りの値を判断します。走査した後も空ではない値がある場合は、キュー全体の場合、不完全なバイナリ ツリーとして表現されます。トラバース後にキュー全体が空の場合、証明は完全なバイナリ ツリーです。
コードデモ:
ヘッダー ファイル: Tree.h
//判断二叉树是否为完全二叉树 bool BinaryTreeComplete(BTNode* root);
ソースファイル: Tree.c
//判断二叉树是否为完全二叉树 bool BinaryTreeComplete(BTNode* root) { //创建队列进行层序遍历 Queue q; QueueInit(&q); if (root) QueuePush(&q, root); //层序遍历 while (!QueueEmpty(&q)) { //取对头的数据 BTNode* front = QueueFront(&q); QueuePop(&q); //队头数据为空就跳出 if (front == NULL) break; //不为空就继续遍历下一个 QueuePush(&q, front->left); QueuePush(&q, front->right); } //跳出之后在这里判断队列中剩下的部分是否存在非空的数据 while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); QueuePop(&q); //非空则表示不是完全二叉树 if (front) { QueueDestroy(&q); return false; } } QueueDestroy(&q); return true; }
5. 連鎖二分木の全テストコード
キューのコードはここには示されていません。必要な方は、 スタックとキューのデータ構造を自分で取得する記事を参照してください。
ヘッダー ファイル: Tree.h
#pragma once // 二叉树 #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h> typedef int TreeDataType; typedef struct BinaryTreeNode { TreeDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; //快速创建一个二叉树 BTNode* BinaryTreeCreat(); //前序遍历 void PrevOrder(BTNode* root); //中序遍历 void InOrder(BTNode* root); //后序遍历 void PostOrder(BTNode* root); //二叉树的节点个数 int BTreeSize(BTNode* root); //求叶子节点的个数 int BTreeLeafSize(BTNode* root); //二叉树的高度 int BTreeHight(BTNode* root); //第k层节点的个数 int BTreeLeafKSize(BTNode* root, int k); //查找值为x的结点 BTNode* BTreeFind(BTNode* root, TreeDataType x); //层序遍历 void LevelOrder(BTNode* root); // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* CreatBinaryTree(char* a, int* pi); //销毁二叉树 void BTDestroy(BTNode* root); //判断二叉树是否为完全二叉树 bool BinaryTreeComplete(BTNode* root);
ソースファイル: Tree.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "Tree.h" #include "Queue.h" //快速创建一个二叉树 BTNode* BuyBTNode(TreeDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { perror("malloc fail"); return NULL; } node->data = x; node->left = NULL; node->right = NULL; return node; } BTNode* BinaryTreeCreat() { //创建节点 BTNode* node1 = BuyBTNode(1); BTNode* node2 = BuyBTNode(2); BTNode* node3 = BuyBTNode(3); BTNode* node4 = BuyBTNode(4); BTNode* node5 = BuyBTNode(5); BTNode* node6 = BuyBTNode(6); BTNode* node7 = BuyBTNode(7); //链接成树 node1->left = node2; node1->right = node3; node2->left = node4; node2->right = node5; node3->left = node6; node3->right = node7; return node1; } //前序遍历 void PrevOrder(BTNode* root) { if (root == NULL) { printf("N "); return; } //先访问根节点 printf("%d ", root->data); //再访问左子树 PrevOrder(root->left); //最后访问右子树 PrevOrder(root->right); } //中序遍历 void InOrder(BTNode* root) { if (root == NULL) { printf("N "); return; } //先访问左子树 InOrder(root->left); //再访问根 printf("%d ", root->data); //在访问右子树 InOrder(root->right); } //后序遍历 void PostOrder(BTNode* root) { if (root == NULL) { printf("N "); return; } //先访问左子树 PostOrder(root->left); //再访问右子树 PostOrder(root->right); //最后访问根 printf("%d ", root->data); } //二叉树的节点个数 int BTreeSize(BTNode* root) { //为空就返回0 if (root == NULL) { return 0; } //不为空就返回根节点和它的左右子树节点的和 return BTreeSize(root->left) + BTreeSize(root->right) + 1; } //求叶子节点的个数 int BTreeLeafSize(BTNode* root) { //左右子树只存在一个或者为空树 if (root == NULL) { return 0; } //左右子树都不存在 if (root->left == NULL && root->right == NULL) { return 1; } //递归调用下面的子树 return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); } //二叉树的高度 int BTreeHight(BTNode* root) { //是否为空树 if (root == NULL) { return 0; } //保存左右子树的高度 int LeftTreeHight = BTreeHight(root->left); int RightTreeHight = BTreeHight(root->right); return LeftTreeHight > RightTreeHight ? LeftTreeHight + 1 : RightTreeHight + 1; //加上自己本身的高度1 } //不推荐的写法 //二叉树的高度 //int BTreeHight(BTNode* root) //{ // //是否为空树 // if (root == NULL) // { // return 0; // } // // //直接递归 // return BTreeHight(root->left) > BTreeHight(root->right) ? // BTreeHight(root->left) + 1 : BTreeHight(root->right) + 1; //加上自己本身的高度1 //} //第k层节点的个数 int BTreeLeafKSize(BTNode* root, int k) { //判断k的合理性 assert(k > 0); //树为空 if (root == NULL) { return 0; } //树不为空,且k==1 if (k == 1 ) { //算一个有效结点 return 1; } //树既不为空,k也不为1,继续向下走 return BTreeLeafKSize(root->left, k - 1) + BTreeLeafKSize(root->right, k - 1); } //查找值为x的结点 BTNode* BTreeFind(BTNode* root, TreeDataType x) { //判断是否为空树 if (root == NULL) { return NULL; } //找到了就返回地址 if (root->data == x) { return root; } //先去左树找,找到了就返回 BTNode* left = BTreeFind(root->left, x); if (left) { return left; } //再去右树找,找到了就返回 BTNode* right = BTreeFind(root->right, x); if (right) { return right; } //如果左右树都没有找到就返回NULL return NULL; } //层序遍历 void LevelOrder(BTNode* root) { //创建队列 Queue q; QueueInit(&q); //先将第一层的插入队列 if (root) QueuePush(&q, root); while (!QueueEmpty(&q)) { //保存队头的数据 BTNode* front = QueueFront(&q); //删除队头 QueuePop(&q); printf("%d ", front->data); //将第二层的数据带入队列 if (front->left) QueuePush(&q, front->left); if (front->right) QueuePush(&q, front->right); } printf("\n"); QueueDestroy(&q); } // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 //#代表空树 //创建二叉树 BTNode* CreatBinaryTree(char* a, int* pi) { //判断是否为'#' if (a[*pi] == '#') { (*pi)++; return NULL; } //不为'#'就创建 BTNode* root = BuyBTNode(a[*pi]); //继续访问下一个元素 (*pi)++; //递归创建它的左子树 root->left = CreatBinaryTree(a, pi); //递归创建它的右子树 root->right = CreatBinaryTree(a, pi); return root; } //销毁二叉树 void BTDestroy(BTNode* root) { //后序遍历销毁 //为空就返回 if (root == NULL) return; //先走左树再走右树 BTDestroy(root->left); BTDestroy(root->right); //释放 free(root); } //判断二叉树是否为完全二叉树 bool BinaryTreeComplete(BTNode* root) { //创建队列进行层序遍历 Queue q; QueueInit(&q); if (root) QueuePush(&q, root); //层序遍历 while (!QueueEmpty(&q)) { //取对头的数据 BTNode* front = QueueFront(&q); QueuePop(&q); //队头数据为空就跳出 if (front == NULL) break; //不为空就继续遍历下一个 QueuePush(&q, front->left); QueuePush(&q, front->right); } //跳出之后在这里判断队列中剩下的部分是否存在非空的数据 while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); QueuePop(&q); //非空则表示不是完全二叉树 if (front) { QueueDestroy(&q); return false; } } QueueDestroy(&q); return true; }
ソースファイル: Test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "Tree.h" #include "Queue.h" void TestTree1() { BTNode* root = BinaryTreeCreat(); //前序遍历 PrevOrder(root); printf("\n"); //中序遍历 InOrder(root); printf("\n"); //后序遍历 PostOrder(root); printf("\n"); // printf("BTreeSize:%d\n", BTreeSize(root)); printf("BTreeLeafSize:%d\n", BTreeLeafSize(root)); printf("BTreeHight:%d\n", BTreeHight(root)); LevelOrder(root); printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root)); BTDestroy(root); root = NULL; } void InOrder1(BTNode* root) { if (root == NULL) { return; } InOrder1(root->left); printf("%c ", root->data); InOrder1(root->right); } void TestTree2() { char a[] = { "abc##de#g##f###" }; int i = 0; BTNode* root = CreatBinaryTree(a, &i); //中序遍历 InOrder1(root); printf("\n"); printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root)); BTDestroy(root); root = NULL; } int main() { TestTree1(); // //TestTree2(); return 0; }
今日のブログの共有はこれで終わりです。気に入っていただけましたら、3 つのリンクを残してください。ありがとうございます。次回お会いしましょう!!