[データ構造とアルゴリズム分析] ツリー ウォーキングでは、事前順序、順序内、事後順序、幅優先のトラバーサル アルゴリズムの実装と最適化を検討します。

序文

  バイナリ ツリーは最も基本的なデータ構造の 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その右側の子ノードを更新します。スタックが空になり、現在のノードがpNULL になるまでループします。

  • 最後に、動的に割り当てられたスタック領域が解放されます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とキュー テール ポインタも定義しrearpルート ノードへのポインタを初期化します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 つの走査方法 (事前順序トラバーサル、順序トラバーサル、事後トラバーサル、階層トラバーサル) を紹介します。このうち、前順トラバーサル、順トラバーサル、後順トラバーサルを総称して深さ優先トラバーサルと呼び、階層型トラバーサルを幅優先トラバーサルと呼びます。

  深さ優先トラバーサルと幅優先トラバーサルの両方に独自の特性があり、さまざまな問題を解決するためによく使用されます。深さトラバーサルは、ツリーの深さ、パスの問題、ノードの最長直径の解決など、グローバル情報を取得するためにツリー全体を走査する必要がある状況に適しています。幅走査は、バイナリ ツリーを層ごとに走査したり、バイナリ ツリーの最小深さを解決したりするなど、ツリーの同じ層内のノード間でターゲット ノードを見つける場合により適しています。

おすすめ

転載: blog.csdn.net/qq_53960242/article/details/131152056