目次
1. 二分木のチェーン構造
バイナリ ツリーのリンクされたストレージ構造とは、バイナリ ツリーを表すためにリンク リストを使用すること、つまり要素の論理関係を示すためにチェーンを使用することを指します。
通常の方法では、リンク リストの各ノードは、データ フィールドと左右のポインタ フィールドの 3 つのフィールドで構成されます。左ポインタと右ポインタは、左の子と右の子がポイントするリンクのストレージ アドレスを与えるために使用されます。ノードのそれぞれが配置されます。
鎖の構造は二分岐鎖と三分岐鎖に分けられますが、ここでは二分岐鎖について勉強します。
二分木は次のとおりです。
1. 空の木
2. 空ではない: ルート ノード、ルート ノードの左側のサブツリー、およびルート ノードの右側のサブツリー。
図からわかるように、バイナリ ツリーの定義は再帰的であり、再帰ツリーとも呼ばれます。そのため、基本的なポストオーダー操作は基本的にこの概念に従って実装されます。
バイナリチェーンの構造の図。
2. バイナリチェーンのインターフェース実装
1. バイナリチェーンの作成
typedef int BTDataType;
//二叉链
typedef struct BinaryTreeNode
{
BTDataType data; // 当前结点值域
struct BinaryTreeNode* left; // 指向当前结点左孩子
struct BinaryTreeNode* right; // 指向当前结点右孩子
}BTNode;
まず、バイナリ チェーンを表す構造体を作成します。dataは現在のノードの値の範囲、BTDataType は格納された値のデータ型です。
left は現在のノードの左の子を指し、right は現在のノードの右の子を指します。
ここでのBTDataType はintの名前変更であり、データ型の名前変更とも言えます。そのため、後続の変更を容易にするために統一できます。
2. インターフェース機能
//动态创立新结点
BTNode* BuyNode(BTDataType x);
//创建二叉树
BTNode* GreatBTree();
//前序遍历
void PrevOrder(BTNode* root);
//中序遍历
void InOrder(BTNode* root);
//后序遍历
void PostOrder(BTNode* root);
これは上記で実装されるインターフェイス関数です。
3. 新しいノードを動的に作成する
//动态创立新结点
BTNode* BuyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
assert(newnode);
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
後で新しいノードを作成するときは、この関数を直接呼び出して、必ずヒープ領域にスペースを適用してください。これにより、スペースは保持され、関数の終了後にリサイクルされなくなります。
新しい値をdataに割り当て、左と右の両方がnull を指し、ノード ポインタを返します。
4. バイナリツリーを作成する
//创建二叉树
BTNode* GreatBTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
次に、バイナリ ツリーを構築するノードを申請し、リンクを介して新しいノードをリンクします。
作成されたバイナリ ツリー構造は次のとおりです。
5. 事前注文トラバーサル
//前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
バイナリ ツリーの事前順序、順序、およびその後の走査はすべて同じ考え方を使用します。
1. プレオーダー トラバーサル (プレオーダー トラバーサルとも呼ばれます) - ルート ノード ---> 左サブツリー ---> 右サブツリー
2. インオーダートラバーサル - 左サブツリー ---> ルートノード ---> 右サブツリー
3. ポストオーダートラバーサル - 左のサブツリー ---> 右のサブツリー ---> ルート ノード
ここでは再帰的思考が使用されています: NULLはここではNで表されます。それを理解するために絵を描き、それを層ごとにたどることをお勧めします。
事前注文トラバーサル:
最初にルート ノード(1)にアクセスし、次にその左側のサブツリー(2)にアクセスします。print 1
このとき、ルート ノードは(2)であり、その左側のサブツリーにアクセスします(3) ; print 1 2
このとき、ルートノードは(3)であり、その左側のサブツリー(NULL)にアクセスします; print 1 2 3
このとき、ルートノードは(NULL) で (3) に NULL を返し、(3 ) の右側のサブツリー(NULL)にアクセスします; print 1 2 3 N
このときルートノードは(3) に (NULL) return NULL となっており、このとき(3) 、つまり(2)の左側のサブツリーへのアクセスが終了し、次に(2)の右側のサブツリー(NULL)へのアクセスが終了します。 (2)にアクセス; 印刷1 2 3 NN
このとき、ルートノードは(2) に (NULL) return NULL となり、 (2) 、つまり(1)の左側のサブツリーへのアクセスが終了し、次に(1)の右側のサブツリー(4)へのアクセスが終了します。 (1)にアクセス; print 1 2 3 NNN
このとき、ルートノードは(4)であり、その左側のサブツリー(5)にアクセスします; print 1 2 3 NNN 4
このとき、ルートノードは(5)であり、その左側のサブツリー(NULL)にアクセスします; print 1 2 3 NNN 4 5
このときルートノードは(NULL) なので、(5) に NULL を返し、(5) の右側のサブツリー(NULL ) にアクセスします; print 1 2 3 NNN 4 5 N
このとき、ルートノードは(NULL)であり、(5)にNULLを返すと、(5)、つまり(4)の左側のサブツリーへのアクセスが終了し、次に右側のサブツリー(6)へアクセスすることになる。 (4)のアクセスが行われる; print 1 2 3 NNN 4 5 NN
このとき、ルートノードは(6)であり、その左側のサブツリー(NULL)にアクセスします; print 1 2 3 NNN 4 5 NN 6
このときルートノードは(NULL)なので、(6)にNULLを返し、 (6)の右側のサブツリー(NULL )にアクセスし、print 1 2 3 NNN 4 5 NN 6 N
このとき、ルートノードは(NULL)ですので、(6)にNULLを返します。この時点で(6)、つまり(4)の右側のサブツリーへのアクセスは終了です。(4) 、つまり、(1) の右側のサブツリー右サブツリーへのアクセスが終了し、この時点で(1)へのアクセスも終了し、プレオーダー トラバーサルも終了; print 1 2 3 NNN 4 5 NN 6 NN
グラフィックアイデアの例:
BTNode* root = GreatBTree();
//前序遍历
PrevOrder(root);
これは事前注文トラバーサルです。
6. 順序どおりの走査
//中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
順序トラバーサル: 左のサブツリー ---> ルート ノード ---> 右のサブツリー
考え方はpre-order traversalと同じで、アクセス順序を変更して pre-order traversal の考え方に従うだけで完了です。
//中序遍历
InOrder(root);
printf("\n");
7. ポストオーダートラバーサル
//后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
事後トラバーサル: 左サブツリー ---> 右サブツリー ---> ルート ノード
考え方は同じですが、アクセス順序が変更されています。前方、中間、後順のトラバースの考え方はすべて同じです。そのうちの 1 つを理解していれば、すべてを理解できます。
//后续遍历
PostOrder(root);
printf("\n");
バイナリ チェーンの基本的なトラバーサルはここで完全に実装されています。レイヤー順序のトラバーサル もあると言う人もいます。このトラバーサルにはキューの使用が必要です。現在の C 言語のステージ実装は面倒すぎるため、ポストオーダー ブロガーはそれを補います;
第三に、ノードの数と高さなどです。
このような問題は再帰的な問題でもあるため、関数スタック フレームの理解により注意を払います。
1. インターフェース機能
//结点个数
int SumNode(BTNode* root);
//叶子结点个数
int LeafNode(BTNode* root);
//二叉树高度
int HeightTree(BTNode* root);
//二叉树第k层结点个数
int BTreeLeveSize(BTNode* root, int k);
//二叉树查找值为x的结点
BTNode* BTreeFine(BTNode* root, int x);
実装する機能は上記の通りです。
2. ノード数
//结点个数
int SumNode(BTNode* root)
{
return root == NULL ? 0 : SumNode(root->left) + SumNode(root->right) + 1;
}
実際、再帰は難しいとは言えませんし、難しくないと言っても過言ではありません。
1.大したことは小さくする: ルート ノードを含むバイナリ ツリーのノードの合計(1) ==> 左のサブツリーのノードの合計(2)と右のサブツリーのノードの合計( 4)独自のノードを加えたもの ポイントの数は 1で、ルート ノードとのノードの合計(2) ==>左のサブツリーの合計(3) + NULL + 1、これがルールです。(1) = (2) + (4) +1】
2.終了条件。ノードがNULLの場合は0を返します。
//结点个数
printf("%d\n", SumNode(root));
3. リーフノードの数
//叶子结点个数
int LeafNode(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left==NULL && root->right==NULL)
{
return 1;
}
else
{
return LeafNode(root->left) + LeafNode(root->right);
}
}
大きなことを小さくする: ルート ノードを持つバイナリ ツリーのリーフ ノードの数を見つけます(1) ==>その左のサブツリーのリーフ ノードの数(2)とその右のサブツリー(4) を加えたもの ; [ (1) =(2)+(4)】
終了条件:ノードがNULLの場合は0を返し、ノードの左右のサブツリーが両方ともNULLの場合は1 を返します。
4. 二分木の高さ
//二叉树高度
int HeightTree(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int left = HeightTree(root->left);
int right = HeightTree(root->right);
return left > right ? left + 1 : right + 1;
}
大したことは小さくしましょう: ルート ノード(1)を持つバイナリ ツリーの高さを求めます==>左のサブツリー(2)と右のサブツリー(4)の高い方の高さに自身の高さ1 を加えたもの; [( 1) =(2)>(4)? (2)+1: (4)+1]
終了条件:ノードがNULLの場合は 0を返します。
//二叉树高度
printf("%d\n", HeightTree(root));
5. 二分木の k 番目のレベルのノードの数
//二叉树第k层结点个数
int BTreeLeveSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BTreeLeveSize(root->left, k - 1) + BTreeLeveSize(root->right, k - 1);
}
大きなことを小さくする: ルート ノードを持つバイナリ ツリーの K 番目のレベルのノードの数を見つけます(1) ==>左側のサブツリーのK-1 番目のレベルのノードの数(2)に右側のサブツリーを加えたもの(4)数値; [ (1) = (2) + (4) ]
終了条件:ノードがNULLの場合は 0を返し、K が1に等しい場合は1を返します。
//二叉树第k层结点个数
printf("%d\n", BTreeLeveSize(root,3));
6. 二分木は値 x を持つノードを検索します。
//二叉树查找值为x的结点
BTNode* BTreeFine(BTNode* root, int x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
if (BTreeFine(root->left, x) == NULL)
{
return BTreeFine(root->right, x);
}
else
{
return BTreeFine(root->left, x);
}
}
大事なことを小さくする:ルート ノード(1)を持つバイナリ ツリーで値xを持つノードを見つけます。 ==>左のサブツリー(2)と右のサブツリー(4)で値xを持つノードを見つけます。
終了条件:ノードがNULLの場合はNULL を返し、ノードの値がxの場合はノードを返します。
アイデア:したがって、サブツリーの 1 つがNULLでない場合、それが要求されたノードです。左側のサブツリーが空でない場合は、左側のサブツリーのノードが返されます。それ以外の場合は、右側のサブツリーのノードが返されます。左側のサブツリーが両方とも空でない場合は、右側のサブツリーのノードが返されます。右側のサブツリーが空の場合は、右側のサブツリーのノードも返します。
//二叉树查找值为x的结点
BTNode* ret = BTreeFine(root, 6);
printf("%d\n", ret->data);
ret = BTreeFine(root, 3);
printf("%d\n", ret->data);
ここまでです。これらの質問を通じて、二分木 (再帰木) について完全に理解しました。これが再帰アルゴリズムです。理解するには、まだ絵を描く必要があります。再帰の基礎知識は、関数スタック フレームの作成と破壊です。 ;
3 番目の段階はここです。この段階では、バイナリ ツリー (再帰ツリー) の再帰的な概念を理解していただきます。
後ほどブロガーが順次更新していきますので、よろしくお願いします。
不足がある場合は、お気軽に補足して連絡してください。
終わり。。