【データ構造】二分木チェーン構造の実装(3)

目次

1. 二分木のチェーン構造

2. バイナリチェーンのインターフェース実装

        1. バイナリチェーンの作成

        2. インターフェース機能

        3. 新しいノードを動的に作成する

        4. バイナリツリーを作成する

        5. 事前注文トラバーサル

        6. 順序どおりの走査

        7. ポストオーダートラバーサル

第三に、ノードの数と高さなどです。

        1. インターフェース機能

        2. ノード数

        3. リーフノードの数

        4. 二分木の高さ

        5. 二分木の k 番目のレベルのノードの数

        6. 二分木は値 x を持つノードを検索します。


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 番目の段階はここです。この段階では、バイナリ ツリー (再帰ツリー) の再帰的な概念を理解していただきます。

後ほどブロガーが順次更新していきますので、よろしくお願いします。

不足がある場合は、お気軽に補足して連絡してください。

終わり。


おすすめ

転載: blog.csdn.net/m0_71676870/article/details/132839828