Die C-Sprache implementiert einen Binärbaum (Kettenstruktur).


Vorwort

Wenn wir die sequentielle Struktur verwenden, um die Speicherung des Binärbaums zu realisieren, besteht der nächste Schritt darin, die Kettenstruktur zu verwenden, um die Speicherung des Binärbaums zu realisieren.

Erstens die Durchquerung des Binärbaums

Der einfachste Weg, die Binärbaumstruktur zu erlernen, besteht darin, sie zu durchlaufen. Bei der sogenannten Binärbaumdurchquerung (Traversal) werden nacheinander entsprechende Operationen an den Knoten im Binärbaum gemäß bestimmten spezifischen Regeln ausgeführt, wobei jeder Knoten nur einmal bedient wird. Die von den Zugangsknoten ausgeführten Operationen hängen vom spezifischen Anwendungsproblem ab. Das Durchlaufen ist eine der wichtigsten Operationen an einem Binärbaum und bildet auch die Grundlage für andere Operationen an einem Binärbaum.

1. Durchqueren der Ebenenreihenfolge des Binärbaums

Die Schichtreihenfolge des Binärbaums wurde verwendet, als die sequentielle Strukturspeicherung des Binärbaums früher eingeführt wurde, dh die Speicherreihenfolge des gesamten Binärbaums im Array ist die Schichtreihenfolge-Durchlaufreihenfolge des Binärbaums.
Fügen Sie hier eine Bildbeschreibung ein

2. Durchqueren des Binärbaums vorbestellen

Vorbestellungsdurchquerung (Vorbestellungsdurchquerung, auch als Vorbestellungsdurchquerung bekannt) – Der Vorgang des Besuchs des Wurzelknotens erfolgt vor dem Durchlaufen seiner linken und rechten Teilbäume. Das #-Zeichen gibt den Standort eines leeren Baums an.
Fügen Sie hier eine Bildbeschreibung ein

3. Inorder-Traversierung eines Binärbaums

Inorder Traversal (Inorder Traversal) – Der Besuch des Wurzelknotens erfolgt beim Durchlaufen seiner linken und rechten Teilbäume (in der Mitte). Das #-Zeichen gibt den Standort eines leeren Baums an.

Fügen Sie hier eine Bildbeschreibung ein

4. Postorder-Durchquerung des Binärbaums

Postorder Traversal – Der Besuch des Wurzelknotens erfolgt nach dem Durchlaufen seiner linken und rechten Teilbäume. Das #-Zeichen gibt den Standort eines leeren Baums an.
Fügen Sie hier eine Bildbeschreibung ein

5. Code-Implementierung

Nachdem wir die Regeln für die Durchquerung eines Binärbaums vor, in der Mitte und nach der Ordnung kennen, können wir einen Binärbaum implementieren und dann diese Durchquerungsmethoden implementieren.
Binärbaumdefinition

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>

typedef int BTDataType;

typedef struct BinaryTreeNode
{
    
    
	//存储该结点的左子树的根结点地址
	struct BinaryTreeNode* left;
	//存储该结点的右子树的根结点地址
	struct BinaryTreeNode* right;
	//存储该结点的数据
	BTDataType data;
}BTNode;

Erstellen Sie einen binären Baumknoten

//创建一个新的结点,并且将该结点返回
BTNode* BuyNode(BTDataType x)
{
    
    
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL)
	{
    
    
		perror("malloc fail");
		exit(-1);
	}
	newNode->left = NULL;
	newNode->right = NULL;
	newNode->data = x;
	return newNode;
}

Erstellen Sie einen Binärbaum

//创建一棵二叉树,并且返回该二叉树的根结点
BTNode* CreateBinaryTree()
{
    
    
	//建立二叉树的结点
	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;
}

Implementieren Sie die Durchquerung der Vorbestellung

//先序遍历
void PreOrder(BTNode* root)
{
    
    
	//如果访问的结点为NULL,就打印#
	if (root == NULL)
	{
    
    
		printf("# ");
		return;
	}
	//如果访问结点不为NULL,就将该结点的数据打印出来
	printf("%d ", root->data);
	//因为为先序遍历,所以先访问根节点,然后再访问左子树
	PreOrder(root->left);
	//当左子树访问完再访问右子树
	PreOrder(root->right);
}

Das heißt, die Reihenfolge der rekursiven Funktionsaufrufe ist in der Abbildung dargestellt.
Fügen Sie hier eine Bildbeschreibung ein

Implementieren Sie Inorder-Traversal

//中序遍历
void PreOrder(BTNode* root)
{
    
    
	//如果访问的结点为NULL,就打印#
	if (root == NULL)
	{
    
    
		printf("# ");
		return;
	}
	//因为为中序遍历,所以先访问左子树,然后再访问根节点
	PreOrder(root->left);
	//左子树访问完后再打印根结点数据
	printf("%d ", root->data);
	//当根结点访问完再访问右子树
	PreOrder(root->right);
}

Implementieren Sie Post-Order-Traversal

//后序遍历
void PostOrder(BTNode* root)
{
    
    
	//如果访问的结点为NULL,就打印#
	if (root == NULL)
	{
    
    
		printf("# ");
		return;
	}
	//因为为后序遍历,所以先访问左子树,然后再访问右子树
	PostOrder(root->left);
	//当左子树结点访问完毕后,访问右子树结点
	PostOrder(root->right);
	//当左右子树结点都访问完后,再访问根节点数据
	printf("%d ", root->data);
}

Zweitens die Implementierung einiger Operationen des Binärbaums

Wenn wir die Durchquerung des Binärbaums implementiert haben, können wir einige andere Operationen des Binärbaums implementieren

1. Ermitteln Sie die Anzahl der Knoten im Binärbaum

Wenn wir nach Binärbaumknoten fragen, denken wir zunächst daran, eine globale Variable count zu definieren und dann nacheinander die Knoten des Binärbaums zu besuchen. Wenn der Knoten nicht leer ist, dann count++, und der Endwert von count ist der Knoten im Binärbaum Nummer. Es ist zu beachten, dass beim zweiten Aufruf dieser Methode die Anzahl zu diesem Zeitpunkt nicht mehr 0 ist, da count eine globale Variable ist. Daher muss der Wert von count auf 0 zurückgesetzt werden.

int count = 0;
void BinaryTreeSize(BTNode* root)
{
    
    
	//如果访问的结点为NULL,则count不进行+1
	if (root == NULL)
	{
    
    
		return;
	}
	//每访问到一个不为NULL结点就让count+1
	++count;
	//然后再去访问该结点的左子树和右子树
	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);
}

Es ist nicht sicher, globale Variablen zu verwenden, um die Anzahl der Baumknoten in der obigen Methode zu ermitteln. Die globalen Variablen können geändert werden und jedes Mal, wenn die Funktion aufgerufen wird, müssen die globalen Variablen zurückgesetzt werden. Wir können also die zweite Methode verwenden, um die Anzahl der Binärbaumknoten zu ermitteln.

int TreeSize2(BTNode* root)
{
    
    
	//当root结点为NULL时,就返回0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//当root结点不为NULL,就返回root结点的数量,然后加上root结点左子树和右子树的结点的数量。
	return 1 + TreeSize2(root->left) + TreeSize2(root->right);
}

Rekursions- und Fallback-Graphen für Funktionen.
Fügen Sie hier eine Bildbeschreibung ein

2. Ermitteln Sie die Anzahl der Blattknoten im Binärbaum

//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
    
    
	//如果该结点为NULL,则返回0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//如果该结点为叶子结点,则返回1
	if (root->left == NULL && root->right == NULL)
	{
    
    
		return 1;
	}
	//如果该结点不为NULL,也不为叶子结点,就返回该节点的左子树和右子树中的叶子结点数
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3. Ermitteln Sie die Anzahl der Knoten in der k-ten Ebene des Binärbaums

//二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    
    
	assert(k >= 1);
	//如果该结点为NULL,就返回0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//k==1,说明要求的就是这一层的结点数,返回1
	if (k == 1)
	{
    
    
		return 1;
	}
	//如果该结点不为NULL,且k!=1,说明求的不是该层的结点数,让k-1,然后求该结点的左子树和右子树
	return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);
}

4. Ermitteln Sie die Tiefe des Binärbaums

//求二叉树深度
int BinaryTreeDepth(BTNode* root)
{
    
    
	//如果该结点为NULL,则深度为0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//然后求出该结点的左子树和右子树的深度
	int leftDepth = BinaryTreeDepth(root->left);
	int rightDepth = BinaryTreeDepth(root->right);
	//如果该结点的左子树深度大于右子树深度
	if (leftDepth > rightDepth)
	{
    
    
		//就返回该结点左子树的深度加这一层的深度
		return leftDepth + 1;
	}
	//如果该结点的左子树深度小于等于右子树深度
	else
	{
    
    
		//就返回右子树的深度加这一层的深度
		return rightDepth + 1;
	}
}

5. Suchen Sie im Binärbaum den Knoten, dessen Wert x ist

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    
    
	//当结点为NULL时,返回NULL
	if (root == NULL)
	{
    
    
		return NULL;
	}
	//当该结点数据为x时,返回该结点
	if (root->data == x)
	{
    
    
		return root;
	}
	//当该结点数据不为x时,先遍历该结点的左子树
	BTNode* left = BinaryTreeFind(root->left, x);
	//如果该结点的左子树返回的结点不为NULL,则说明在左子树中找到了存储x的结点,则此时left就存储该结点的地址。直接将left返回即可
	if (left!=NULL)
	{
    
    
		return left;
	}

	//如果该结点的左子树也没有查到就去遍历该结点的右子树,
	BTNode* right = BinaryTreeFind(root->right, x);
	//当该结点的右子树返回的结点不为NULL,则说明在右子树中找到了存储x的结点,此时right就存储该结点的地址。直接将right返回即可
	if (right!=NULL)
	{
    
    
		return right;
	}
	
	//当该结点的数据和该结点的左子树和右子树的结点中都没有该数据,则二叉树中没有该数据,此时返回NULL
	return NULL;
}

6. Zerstörung des Binärbaums

Die Zerstörung des Binärbaums besteht darin, den gesamten von jedem Knoten des Binärbaums beanspruchten Speicherplatz nacheinander freizugeben, und es ist erforderlich, den Binärbaum einmal zu durchlaufen. Hier müssen wir die Durchquerung nach der Bestellung verwenden, dh die erste Freigabe Die Knoten des linken Teilbaums des Wurzelknotens und dann den Wurzelknoten freigeben. Der Knoten des rechten Teilbaums des Knotens und schließlich den Platz des Wurzelknotens freigeben. Denn wenn der Wurzelknotenraum zuerst durch Vorbestellungsdurchquerung freigegeben wird, werden die linken und rechten Teilbäume des Wurzelknotens nicht gefunden.

//二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return;
	}
	//采用后序遍历释放二叉树结点
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

3. Implementierung der Reihenfolgendurchquerung auf binärer Baumebene

1. Durchquerung der Schichtfolge

Die Realisierung der Durchquerung der Ebenenreihenfolge des Binärbaums erfordert die Unterstützung einer Warteschlange. Beispielsweise gibt es den folgenden Binärbaum. Stellen Sie zunächst den Wurzelknoten des Binärbaums in die Warteschlange.
Fügen Sie hier eine Bildbeschreibung ein
Dann wird der Kopf der Warteschlange aus der Warteschlange entfernt und die linken und rechten Kinder des Kopfknotens werden in die Warteschlange gestellt.
Fügen Sie hier eine Bildbeschreibung ein
Wiederholen Sie dann den obigen Vorgang. Stellen Sie die verbleibenden Knoten nacheinander in die Warteschlange.
Fügen Sie hier eine Bildbeschreibung ein
bis die Warteschlange leer ist. Die Reihenfolge, in der die Knoten aus der Warteschlange entfernt werden, ist die Reihenfolge, in der der Binärbaum durchlaufen wird.
Fügen Sie hier eine Bildbeschreibung ein

2. Implementierung des Layer-Sequenz-Traversal-Codes

Für die hierarchische Durchquerung ist eine Hilfswarteschlange erforderlich. Stellen Sie zuerst den Wurzelknoten des Binärbaums in die Warteschlange, entfernen Sie dann den Wurzelknoten aus der Warteschlange, stellen Sie die linken und rechten untergeordneten Elemente des Wurzelknotens in die Warteschlange, entfernen Sie dann die linken und rechten untergeordneten Elemente aus der Warteschlange und stellen Sie die linken und rechten untergeordneten Elemente in die Warteschlange die linken und rechten Kinder. Die Schleife wird fortgesetzt, bis die Warteschlange leer ist.

//二叉树的层序遍历
void LevelOrder(BTNode* root)
{
    
    
	//创建一个辅助队列
	Queue qt;
	QueueInit(&qt);
	//如果二叉树为空,就退出函数
	if (root == NULL)
	{
    
    
		return;
	}
	//将根节点的地址入队
	QueuePush(&qt, root);
	//如果队列中不为空,就进入循环
	while (!QueueEmpty(&qt))
	{
    
    
		//创建一个BTNode*类型的指针接收队列中队头结点存的地址
		BTNode* head = QueueFront(&qt);
		//将队头结点出队。
		QueuePop(&qt);
		//打印出队结点的数据
		printf("%d ", head->data);
		//如果出队结点的左孩子不为空,就将结点左孩子入队
		if (head->left != NULL)
		{
    
    
			QueuePush(&qt, head->left);
		}
		//如果出队结点的右孩子不为空,就将结点右孩子入队
		if (head->right != NULL)
		{
    
    
			QueuePush(&qt, head->right);
		}
	}
	printf("\n");
	QueueDestroy(&qt);

}

3. Anwendung der Schichtreihenfolge-Traversierung – Beurteilung, ob ein Binärbaum ein vollständiger Binärbaum ist

Wenn es einen Binärbaum gibt, bei dem beurteilt werden muss, ob es sich um einen vollständigen Binärbaum handelt, kann zu diesem Zeitpunkt die Durchquerung der Ebenenreihenfolge des Binärbaums ausgeliehen werden.
Beispielsweise gibt es einen vollständigen Binärbaum wie folgt .
Fügen Sie hier eine Bildbeschreibung ein
Der Binärbaum wird in der Reihenfolge der Ebenen durchlaufen. Zuerst tritt der Wurzelknoten in die Warteschlange ein, dann verlässt der Kopfknoten die Warteschlange und die linken und rechten untergeordneten Knoten des Knotens werden darin gespeichert Der Hauptknoten tritt in die Warteschlange ein. Wenn das linke und das rechte Kind leer sind, werden sie ebenfalls dem Team beitreten.
Fügen Sie hier eine Bildbeschreibung ein
Wenn die im Kopfknoten der Warteschlange gespeicherten Daten NULL sind, stoppen Sie den obigen Vorgang. Zu diesem Zeitpunkt sind alle in den Knoten in der Warteschlange gespeicherten Daten NULL, sodass der Schluss gezogen werden kann, dass der Binärbaum ein vollständiger Binärbaum ist
Fügen Sie hier eine Bildbeschreibung ein

Beispielsweise gibt es einen unvollständigen Binärbaum wie folgt .
Fügen Sie hier eine Bildbeschreibung ein
Der Binärbaum folgt den Schritten des Durchlaufens der Schichtreihenfolge: Zuerst tritt der Wurzelknoten in die Warteschlange ein, dann verlässt der Warteschlangenkopfknoten die Warteschlange und die linken und rechten untergeordneten Knoten des Knotens Im Kopfknoten der Warteschlange gespeichert, gelangen Sie in die Warteschlange. Wenn das linke und das rechte Kind leer sind, werden sie ebenfalls dem Team beitreten.
Fügen Sie hier eine Bildbeschreibung ein
Wenn die im Kopfknoten der Warteschlange gespeicherten Daten NULL sind, stoppen Sie den obigen Vorgang. Zu diesem Zeitpunkt sind einige der in den Knoten in der Warteschlange gespeicherten Daten NULL und andere nicht NULL, sodass der Schluss gezogen werden kann, dass der Binärbaum kein vollständiger Binärbaum ist.
Fügen Sie hier eine Bildbeschreibung ein
Basierend auf den oben genannten Bedingungen können wir einige Logiken zum Durchlaufen der Ebenenreihenfolge ändern, sodass beurteilt werden kann, ob der Binärbaum ein vollständiger Binärbaum ist.

int BinaryTreeComplete(BTNode* root)
{
    
    
	//创建一个辅助队列
	Queue qt;
	QueueInit(&qt);
	
	//如果二叉树根节点不为空,就将根节点的地址入队列
	if (root != NULL)
	{
    
    
		QueuePush(&qt, root);
	}
	
	//如果队列中不为空,就进入循环
	while (!QueueEmpty(&qt))
	{
    
    
		//将队列的队头结点存的数据返回
		BTNode* head = QueueFront(&qt);
		//将队头结点出队
		QueuePop(&qt);
		//如果该队头结点存的数据不为NULL,就将该结点的左右孩子入队,左右孩子为NULL也入队
		if (head != NULL)
		{
    
    
			QueuePush(&qt, head->left);
			QueuePush(&qt, head->right);
		}
		//如果该队头结点存的数据为空,说明已经到达队列中第一个NULL,此时跳出循环
		else
		{
    
    
			break;
		}
	}
	//此时如果队列不为空,就继续遍历队列中的元素
	while (!QueueEmpty(&qt))
	{
    
    
		//将队头结点存的数据返回
		BTNode* head = QueueFront(&qt);
		//将队头结点出队
		QueuePop(&qt);
		//如果队列中第一个NULL后还有队列结点存的数据不为NULL,则说明该二叉树不为完全二叉树
		if (head != NULL)
		{
    
    
			//将队列销毁
			QueueDestroy(&qt);
			//返回0则代表该二叉树不是完全二叉树
			return 0;
		}
	}
	//如果当队列中结点全部遍历完,并且存的数据都为NULL,说明该二叉树为完全二叉树
	//销毁队列
	QueueDestroy(&qt);
	//返回1代表为完全二叉树
	return 1;
}

Guess you like

Origin blog.csdn.net/dong132697/article/details/132562478