关于二叉树的详解

一、树

树的概念:

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成的一个具有层次关系的集合,由于它看起来像是一颗倒挂着的树,也就是根朝上,叶朝下的。

注意1:有一个特殊的节点,称为根节点,根结点没有前驱节点。

           除了根节点外,其余结点被分成了M个互不相交的集合T1,T2,……,Tm,其中每个集合Ti(1<=i<=m)又是一颗结构与树类似的子树。每颗子树的根节点有且仅有一个前驱,可以有0个或者多个后继。(因此树是递归定义的)。

 注意2:在树形结构中,子树之间不能有交集即:对T0这个集合来说,T1,T2,T3互不相交。

              除了根节点外,每一个节点都必须有一个父节点。

              一颗N个节点的树有N-1条边。

树的相关概念:

节点的度:一个节点含有的子树的个数。如上图中A有三个子节点B、C、D。因此度为3。

叶节点或终端节点:度为0的节点。如上述的J、F、K、L、H、I。

非终端节点或分支节点:即不为终端的节点,度不为0。

双亲节点或父节点:一个节点中含有子节点,则称其为子节点的父节点,如:B、C、D。的父节点就是A。由此可知,父节点必为分支节点。

子节点或孩子节点:一个节点含有的子树的根节点。如E、F为B的子节点。(不一定是叶节点)

兄弟节点:具有相同父节点的节点。如:B、C、D的父节点都是A,因此他们互为兄弟节点。

树的度:一棵树中,(最大的(节点的度)),如上图的树的度为3。

节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。

树的高度或深度:树中节点的最大层次。如上图,树的高度为4。

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:F、G互为堂兄弟节点。

节点祖先:从根到该节点所经分支上的所有结点;如:A。

子孙,以某节点为根的子树中任一节点都为该节点子孙,如:上述所有节点都是A的子孙。

森林:有n(n>0)棵互不相交的树的集合。

二、二叉树的概念及结构:

概念:一颗二叉树是节点的一个有限集合,该集合为空,或者由一个根节点加上俩颗别称为左子树和右子树的二叉树组成。

 从上图可以知道:二叉树不存在度大于2的节点,而且二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。

特殊的二叉树:

满二叉树:一个二叉树,如果每一层的节点树都达到最大值,则这个二叉树就是满二插树。也就是说,如果一个二叉树层数为K,而且结点总数是2^K-1,则就是满二叉树。

完全二叉树:完全二叉树是一种效率很高的数据结构,他是由满二叉树引出来的,对于深度为K,有n个节点的二叉树,当且仅当其每一个节点都与深度为K的满二叉树中编号从1到n的节点一一对应时称为完全二叉树,要注意,满二叉树是一种特殊的完全二叉树。

 二叉树的性质:

1.若规定根节点层数为1,则一棵非空的二叉树第i层上最多有2^(i-1)个节点。

2.若规定根节点层数为1,则深度为h的二叉树最大节点树是2^h-1。

3.对任一棵二叉树,如果度为0的叶节点个数为n0,度为2的分支节点个数为n2,则有n0=n2+1;

4.若规定根节点的层数为1,具有n个节点的满二叉树深度,h=log(n+1)(以2为底数)。

5.对于具有n个节点的完全二叉树,如果按照从上到下从左到右的数组顺序对所有的节点从0开始编号,对于序号为i的节点有:

5.1、若i>0,i位置节点父节点序号为:(i-2)/2;i=0;若i为根节点编号,无父节点;

5.2、若2i+1<n,该分支节点的左子节点为2i+1(i为该分支节点编号);若2i+1>=n,无左子节点。

5.3、若2i+2<n,该分支节点的左子节点为2i+2(i为该分支节点编号);若2i+2>=n,无右子节点。

 二叉树的存储结构:

二叉树的结构存储一般为俩种,一种是顺序结构,一种是链式结构(这里不说链式结构)。

顺序存储:顺序存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间浪费,而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一棵二叉树。

 从上图就可以看出非完全二叉树的顺序存储当存储的量大时,会浪费大量的空间。而完全二叉树的顺序存储不会浪费空间,这里就推出一种二叉树顺序结构的使用——堆(一种二叉树)。

三、堆

 堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值,且堆总是一颗完全二叉树。

 堆的实现:

代码的实现(详细的步骤解说在代码后面):

#define _CRT_SECURE_NO_WARNINGS 1
//头文件引用
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdbool.h>

//定义新名字
#define  HPDataType int

//创建结构体
typedef struct Heap
{
	HPDataType* HeapNode;
	int size;
	int capacity;
}HP;
void HeapInit(HP* php)
{
	assert(php);
	HPDataType* tmp = malloc(sizeof(HPDataType) * 4);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	php->HeapNode = tmp;
	php->capacity = 4;
	php->size = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void HeapAdjustUp(HPDataType* pHeapNode, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (pHeapNode[parent] < pHeapNode[child])
		{
			Swap(&pHeapNode[parent], &pHeapNode[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType data)
{
	assert(php);
	if (php->capacity == php->size)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->HeapNode, sizeof(HPDataType) * (php->capacity) * 2);
		if (tmp == NULL)
		{
			perror("malloc fail");
			return;
		}
		php->HeapNode = tmp;
	}
	php->HeapNode[php->size] = data;
	HeapAdjustUp(php->HeapNode, php->size);
    php->size++;
}
void HeapAdjustDown(HPDataType* pHeapNode, int number)
{
	int parent = 0;
	int child = parent * 2 + 1;
	while (child < number)
	{
		if (pHeapNode[child + 1] > pHeapNode[child])
		{
			child += 1;
		}

		if (pHeapNode[parent] < pHeapNode[child])
		{
			Swap(&(pHeapNode[parent]), &(pHeapNode[child]));
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php)
{
	assert(php);
	Swap(&(php->HeapNode[0]), &(php->HeapNode[php->size - 1]));
	php->size--;
	HeapAdjustDown(php->HeapNode, php->size);
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->HeapNode[0];
}

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
int main()
{
	HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 4);
	HeapPush(&hp, 18);
	HeapPush(&hp, 42);
	HeapPush(&hp, 12);
	HeapPush(&hp, 45);
	HeapPush(&hp, 3);
	HeapPush(&hp, 5);
	HeapPush(&hp, 5);
	HeapPush(&hp, 50);
	HeapPush(&hp, 5);
	HeapPush(&hp, 5);
	HeapPush(&hp, 15);
	HeapPush(&hp, 5);
	HeapPush(&hp, 48);
	HeapPush(&hp, 5);

	int k = 0;
	scanf("%d", &k);
	while (!HeapEmpty(&hp) && k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;
}

堆实现的详细分析与图解:

对堆的创建:

//创建结构体
typedef struct Heap
{
	HPDataType* HeapNode;
	int size;
	int capacity;
}HP;

首先,创建结构体,在该结构体中,含有三个元素,一个是指向在操作系统中内存管理的堆区的指针HeapNode可以指向开辟的那块空间。一个是记录当前在堆区中已经插入的数据的个数size,还有一个是当前开辟的空间的大小capacity。

对堆进行初始化:

HeapInit(&hp);
void HeapInit(HP* php)
{
	assert(php);//判断栈区是否有它空间
	HPDataType* tmp = malloc(sizeof(HPDataType) * 4);//在堆区申请空间,来存储数据
	if (tmp == NULL)//判断空间申请是否成功
	{
		perror("malloc fail");
		return;
	}

	php->HeapNode = tmp;
	php->capacity = 4;
	php->size = 0;
}

开始在堆中插入数据:

HeapPush(&hp, 4);
void HeapPush(HP* php, HPDataType data)
{
	assert(php);
	if (php->capacity == php->size)//判断堆区中申请的空间是否满了,如果满了,申请加大空间
	{
		HPDataType* tmp = (HPDataType*)realloc(php->HeapNode, sizeof(HPDataType) * (php->capacity) * 2);
		if (tmp == NULL)
		{
			perror("malloc fail");
			return;
		}
		php->HeapNode = tmp;
        php->capacity *= 2;
	}
	php->HeapNode[php->size] = data;//存入数据
	HeapAdjustUp(php->HeapNode, php->size);//对数据进行向上调整,使得堆可以满足大根堆或小根堆
    php->size++;
}
void HeapAdjustUp(HPDataType* pHeapNode, int child)
{
	int parent = (child - 1) / 2;//child是自己当前的编号,计算出该节点的父节点的编号
	while (child > 0)
	{
		if (pHeapNode[parent] < pHeapNode[child])
		{
			Swap(&pHeapNode[parent], &pHeapNode[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

第一次插入的数据是4:

开始插入,4在逻辑结构中编号为0,用0*2+1=1,0*2+2=2知,下标/编号为1,2的是他的子节点。

下面的左边都为物理结构,右边为逻辑结构! 

第二次插入数据:

HeapPush(&hp, 18);

 插入18时,4的下标为0,18下标为1(1/2-1=0,因此他的父节点编号/下标为0,)

 第三次插入数据:

HeapPush(&hp, 42);

 插入42时,42下标为2(2/2-1=0,他的父节点编号为0)

 第四次插入数据:

HeapPush(&hp, 12);

插入12时,12下标为3((3-1)/2=1,他的父节点编号/下标为1)

 第五次插入数据:

HeapPush(&hp, 45);

 以此类推!

堆的删除:

提及堆的创建与数据的插入,那么,堆的数据删除与销毁也必不可少。而要想在数据删除的过程中有直接删除和间接删除的方式;

直接删除:

所谓直接删除,就是对当前根节点直接删除,然后挪动其他数据。

 不难发现,如果当我们通过挪动数据的方式删除的话,那么我们就会打乱原本的父子兄弟关系,并且它的时间复杂度为O(N)。

间接删除:

如果我们通过间接删除的方式去删除:先将根节点那个代表大根堆那个最大的数与当前在堆上最末的编号的叶节点那个交换,再对该堆向下调整,那么这样的话就不会打乱父子兄弟关系,并且他的时间复杂度是O(logN)。

        首先先将根节点那个代表大根堆那个最大的数与当前在堆上最末的编号的叶节点那个交换,然后开始对该堆向下调整,找出左右子节点中较大的那个进行交换,然后再以当前节点看作父节点,对其的左右子节点比较看看是否需要交换,这样递归,可以发现,它最多需要交换logN次,最少交换1次!

void HeapAdjustDown(HPDataType* pHeapNode, int number)
{
	int parent = 0;//向下开始调整的位置
	int child = parent * 2 + 1;//算出左右子节点
	while (child < number)
	{
		if (pHeapNode[child + 1] > pHeapNode[child])//选出大的那个子节点
		{
			child += 1;
		}

		if (pHeapNode[parent] < pHeapNode[child])//比较交换
		{
			Swap(&(pHeapNode[parent]), &(pHeapNode[child]));
			parent = child;//递归
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php)
{
	assert(php);
	Swap(&(php->HeapNode[0]), &(php->HeapNode[php->size - 1]));//首先交换根节点与末叶节点
	php->size--;//默认删除了要删的那个数据
	HeapAdjustDown(php->HeapNode, php->size);//向下调整数据,size不仅是当前的节点的编号,还是
//在该编号节点前面存储的数据的数量。
}

上述说这么多,那堆有什么用?

其实堆排序所运用的思想就是如此!

堆的运用->堆排序:

堆排序,先建堆,后通过在堆上删除数据的思想,不断的把当前堆上最大值往后移!

在建堆上有俩种方法,一种是向上调整建堆,一种是向下调整建堆!

#include<stdio.h>
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void HeapSort(int* a, int number)
{
	//开始建堆(向上调整)
	for (int i = 0; i < number; i++)//完成全部数插入堆中
	{
		int child = i;
		int parent = (i - 1) / 2;
		while (child > 0)
		{
			if (a[child] > a[parent])
			{
				Swap(&a[child], &a[parent]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
    //开始建堆(向下调整建堆)
	for (int i = (number - 1 - 1) / 2; i >= 0; i--)
	{
		int parent = i;
		int child = (2 * parent) + 1;
		while (child < number)
		{
			if (child + 1 < number && a[child] < a[child + 1])
			{
				child++;
			}
			if (a[child] > a[parent])
			{
				Swap(&a[child], &a[parent]);
				parent = child;
				child = 2 * parent + 1;
			}
			else
			{
				break;
			}
		}
	}
	//这里运用删除堆中数据的思想,开始不断抽出当前堆中最大数
	for (int i = number - 1; i >= 0; i--)
	{
		Swap(&a[i], &a[0]);
		int parent = 0;
		int child = 2 * parent + 1;
		while (child < i)
		{
			if (child+1<i && a[child] < a[child + 1])
			{
				child++;
			}
			if (a[child] > a[parent])
			{
				Swap(&a[child], &a[parent]);
				parent = child;
				child = 2 * parent + 1;
			}
			else
			{
				break;
			}
		}
	}
}
int main()
{
	int arr[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	HeapSort(arr, 10);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

由于向上调整建堆在数据结构堆结构中已经将讲过,因此,这里只分析向下调整建堆!

    //开始建堆(向下调整建堆)
	for (int i = (number - 1 - 1) / 2; i >= 0; i--)
	{
		int parent = i;
		int child = (2 * parent) + 1;
		while (child < number)
		{
			if (child + 1 < number && a[child] < a[child + 1])
			{
				child++;
			}
			if (a[child] > a[parent])
			{
				Swap(&a[child], &a[parent]);
				parent = child;
				child = 2 * parent + 1;
			}
			else
			{
				break;
			}
		}
	}

向下调整建堆的思想是什么呢?---向下调整建堆的思想是先分支后主干(都是以逻辑结构来说的,因为物理结构都是数组)

通过上面的流程图可以得知:向下调整建堆是先把小枝干做完和调整,再慢慢和大枝干结合调整,最后连接根节点再整体调整,而向上调整建堆是以根节点为起始点,不断向四面八方递归构建小的枝干。

堆的运用->topK问题:

所谓topK问题就是指在N个数中找出最大或者最小的前k个数(k<=n)。

void AdjustUpLittle(int* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void AdjustDownLittle(int* a, int len, int parent)
{
	int child = parent * 2 + 1;
	while (child < len)
	{
		if (child + 1 < len && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//TopK问题
void HeapTopK(int* a, int k,int len)
{
	for (int i = 0; i < k; i++)
	{
		AdjustUpLittle(a, i);
	}
	int i = k;
	while (i < len)
	{
		if (a[i] > a[0])
		{
			a[0] = a[i];
			AdjustDownLittle(a, k, 0);
		}
		i++;
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
}

分析:要想解决topK问题,首先先用n个数中的前k个数建小根堆。然后从第(k+1)个数开始遍历,发现存在有一个数比根节点要大时,进行交换或者覆盖,然后向下调整,保持小根堆,直到把数全部遍历完。

四、二叉树的链式存储结构:

前序:

二叉树:在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。此处手动快速创建一棵简单的二叉树,而又因为一般的二叉树的增删查改的功能并不良好,因此并不推荐直接使用,却又是学习红黑树,AVL树的基础。

二叉树的遍历:

二叉树分为前序(或先序)遍历,中序遍历,后序遍历。简单概括而言,前序遍历是:根节点->左子节点->右子节点。中序遍历是:左子节点->根节点->右子节点。后序遍历是:左子节点->右子节点->根节点。

举例:

二叉树的用法的代码:

#include <stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreNode* right;
}BTNODE;
BTNODE* BuyNode(BTDataType 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* CreatTree()
{
	BTNODE* node1 = BuyNode(1);
	BTNODE* node2 = BuyNode(2);
	BTNODE* node3 = BuyNode(3);
	BTNODE* node4 = BuyNode(4);
	BTNODE* node5 = BuyNode(5);
	BTNODE* node6 = BuyNode(6);
	BTNODE* node7 = BuyNode(7);

	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	node3->right = node7;
	return node1;
}
//前序遍历(根->左节点->右节点)
void PreOrder(BTNODE* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
//中序遍历(左节点->根->右节点)
void InOrder(BTNODE* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}
//后序遍历(左节点->右节点->根)
void PostOrder(BTNODE* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}
//二叉树的节点个数
int TreeSize(BTNODE* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//二叉树第k层的节点个数
int TreeKLevel(BTNODE* root,int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
//二叉树的高度
int TreeHeight(BTNODE* root)
{
	if (root == NULL)
	{
		return 0;
	}

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//二叉树查找一个数
BTNODE* TreeFind(BTNODE* root, BTDataType FindData)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == FindData)
	{
		return root;
	}
	BTNODE* lreplace = TreeFind(root->left,FindData);
	if (lreplace)
	{
		return lreplace;
	}
	BTNODE* rreplace = TreeFind(root->right, FindData);
	if (rreplace)
	{
		return rreplace;
	}
	return NULL;
}
int main()
{
	BTNODE* root = CreatTree();
	PreOrder(root);
	printf("\n");

	InOrder(root);
	printf("\n");

	PostOrder(root);
	printf("\n");

	int k = TreeHeight(root);
	printf("%d\n", TreeKLevel(root, k));

	printf("%p", TreeFind(root, 7));
	return 0;
}

举例对前序遍历,二叉树节点个数计算,二叉树第k层的节点个数计算的函数的递归图:

对前序遍历的递归图:

对二叉树节点的个数计算的递归图:

二叉树的层序遍历:

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。所谓层序遍历,就是一层一层的遍历。
typedef struct QueueNode
{
	struct Queue* next;
	BTNode* val;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Que;

void QueuePush(Que* pq, BTNode* x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}

	newnode->val = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
		newnode = NULL;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

bool QueueEmpty(Que* pq)
{
	assert(pq);

	return pq->size == NULL;
}

void QueuePop(Que* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	QNode* pop = pq->head;
	pq->head = pq->head->next;
	free(pop);
	pop = NULL;
	pq->size--;
}

void QueueDestory(Que* pq)
{
	assert(pq);

	while (!QueueEmpty(pq))
	{
		QueuePop(pq);
	}
}

void QueueInit(Que* pq)
{
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	Que pq;
	QueueInit(&pq);

	QueuePush(&pq, root);
	while(!QueueEmpty(&pq))
	{
		BTNode* front = pq.head->val;
		QueuePop(&pq);

		if (front->_left)
		{
			QueuePush(&pq, front->_left);
		}

		if (front->_right)
		{
			QueuePush(&pq, front->_right);
		}
		printf("%d ", front->_data);
	}
	QueueDestory(&pq);
}

层序遍历需要用到队列来实现,因为层序遍历需要满足一个就是先遍历先出的特点,和队列特点一致。

typedef struct QueueNode
{
	struct Queue* next;
	BTNode* val;
}QNode;

与普通的队列不同的是,该队列中的每一个Node存储的都不是一个数,而是一个地址,指向二叉树的节点。因此需要用到指针val;

其次,队列的头指针需要不断的更新,因此需要·定义一个新的指针front指向二叉树的节点,并且需要该节点在下一次循环时进行更新为新头节点。

层序遍历的应用->判断是否是二叉树:

bool BinaryTreeComplete(BTNode* root)
{
	if (root == NULL)
	{
		return false;
	}

	Que pq;
	QueueInit(&pq);
	QueuePush(&pq, root);
	BTNode* front = root;
	while (!QueueEmpty(&pq))
	{
		front = (pq).head->val;
		QueuePop(&pq);
		if (front == NULL)
		{
			break;
		}
		QueuePush(&pq, front->_left);
		QueuePush(&pq, front->_right);
	}
	while (!QueueEmpty(&pq))
	{
		front = (pq).head->val;
		QueuePop(&pq);
		if (front != NULL)
		{
			QueueDestory(&pq);
			return false;
		}
	}
	QueueDestory(&pq);
	return true;
}

分析:判断该二叉树是否是完全二叉树,先进行层序遍历,直到在队列中出队列开始出现NULL,的时候跳出循环,开始不断的对队列中剩余的元素抽出,如果其中有一个元素是非空,那么表示这个二叉树不是完全二叉树,反之就是。

先序遍历数组建堆:

#include <stdio.h>
#include<stdlib.h>
typedef char TDataType;

typedef struct TreeNode
{
    struct TreeNode* left;
    struct TreeNode* right;
    TDataType val;
}TNode;

void InOrderTree(TNode* root)
{
    if(root==NULL)
    {
        return;
    }

    InOrderTree(root->left);
    printf("%c ",root->val);
    InOrderTree(root->right);
}

void prevOrderTree(TDataType** a,TNode** root)
{
    if(**a=='#')
    {
        (*a)++;
        free(*root);
        *root=NULL;
        return;
    }
    (*root)->val=**a;
    (*a)++;
    (*root)->left=(TNode*)malloc(sizeof(TNode));
    (*root)->right=(TNode*)malloc(sizeof(TNode));
    prevOrderTree(a,&(*root)->left);
    prevOrderTree(a,&(*root)->right);
}

int main()
{
    TDataType a[100]={0};
    scanf("%s",a);
    TNode* root=(TNode*)malloc(sizeof(TNode));
    if(root==NULL)
    {
        perror("malloc fail");
        return 0;
    }
    root->val=0;
    root->left=root->right=NULL;
    TDataType* p=a;
    prevOrderTree(&p,&root);
    InOrderTree(root);
    return 0;
}

先序遍历数组建堆极度需要注意的一点是:

第一、在 main 函数中定义 p 是为了在构建二叉树时,能够通过指针 p 来遍历输入的字符数组 a。在 prevOrderTree 函数中,我们需要递归地构建二叉树。每次递归时,我们需要将 p 的值作为参数传递给下一层递归。通过在 main 函数中定义 p,我们可以在整个程序中共享这个指针,并且可以在递归调用时正确地更新 p 的值。如果不在 main 函数中定义 p,而是将 a 直接传递 给 prevOrderTree 函数,那么在递归调用时无法正确地更新 a 的值,会导致构建二叉树时出错。

其次、对 root 取地址是因为在 prevOrderTree 函数中,我们需要修改 root 指针所指向的节点的左子节点和右子节点。在 C 语言中,函数的参数是按值传递的,即在函数内部对参数的修改不会影响到函数外部的变量。因此,我们需要将 root 指针的地址传递给 prevOrderTree 函数,使得函数内部可以修改 root 指针所指向的节点的左子节点和右子节点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昨日青空。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值