初阶数据结构4 二叉树

1. 树

1.1 树的概念与结构

树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。

  • 有⼀个特殊的结点,称为根结点,根结点没有前驱结点。
  • 除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、……、Tm ,其中每⼀个集合Ti(1 <= i <= m) ⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有 0 个或多个后继。因此,树是递归定义的。

在这里插入图片描述

树形结构中,⼦树之间不能有交集,否则就不是树形结构
在这里插入图片描述

非树形结构:
• ⼦树是不相交的(如果存在相交就是图了)
• 除了根结点外,每个结点有且仅有⼀个⽗结点
• ⼀棵N个结点的树有N-1条边

注意子树是指找一个结点为根结点和它的所有的子孙结点的总和

1.2 树相关术语

在这里插入图片描述

⽗结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点; 如上图:A是B的⽗结点
⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点; 如上图:B是A的孩⼦结点
结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如A的度为6,F的度为2,K的度为0
树的度:⼀棵树中,最⼤的结点的度称为树的度; 如上图:树的度为 6
叶⼦结点/终端结点:度为 0 的结点称为叶结点; 如上图: B、C、H、I… 等结点为叶结点
分⽀结点/⾮终端结点:度不为 0 的结点; 如上图: D、E、F、G… 等结点为分⽀结点
兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟); 如上图: B、C 是兄弟结点
结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推;
树的⾼度或深度:树中结点的最⼤层次; 如上图:树的⾼度为 4
结点的祖先:从根到该结点所经分⽀上的所有结点;如上图: A 是所有结点的祖先
路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如A到Q的路径为:
A-E-J-Q;H到Q的路径H-D-A-E-J-Q
⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙
森林:由 m(m>0) 棵互不相交的树的集合称为森林;

1.3 树的表⽰
孩⼦兄弟表⽰法:
树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法,孩⼦表⽰法、孩⼦双亲表⽰法以及孩⼦兄弟表⽰法等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法

在这里插入图片描述

struct TreeNode
{
struct Node* child; // 左边开始的第⼀个孩⼦结点
struct Node* brother; // 指向其右边的下⼀个兄弟结点
int data; // 结点中的数据域
};

1.4 树形结构实际运⽤场景

⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间的关联。
在这里插入图片描述

2. ⼆叉树

2.1 概念与结构

在树形结构中,我们最常⽤的就是⼆叉树,⼀棵⼆叉树是结点的⼀个有限集合,该集合由⼀个根结点加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空。
从上图可以看出⼆叉树具备以下特点:

  1. ⼆叉树不存在度⼤于 2 的结点
  2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树
    注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的

在这里插入图片描述
在这里插入图片描述

2.2 特殊的⼆叉树

2.2.1 满⼆叉树

⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀个⼆叉树的层数为 K ,且结点总数是2k-1 ,则它就是满⼆叉树。
在这里插入图片描述
2.2.2 完全⼆叉树
完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度为 K 的,有 n 个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从 1 ⾄ n 的结点⼀⼀对应时称之为完全⼆叉树。要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。
在这里插入图片描述

⼆叉树性质
根据满⼆叉树的特点可知:
1)若规定根结点的层数为 1 ,则⼀棵⾮空⼆叉树的第i层上最多有 2i-1个结点
2)若规定根结点的层数为 1 ,则深度为 h 的⼆叉树的最⼤结点数是 2h -1

2.3 ⼆叉树存储结构

⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构(底层是数组),⼀种链式结构(底层是链表)。

2.3.1 顺序结构

顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。
在这里插入图片描述
现实中我们通常把堆(⼀种⼆叉树)使⽤顺序结构的数组来存储,需要注意的是这⾥的堆和操作系统虚拟进程地址空间中的堆是两回事,⼀个是数据结构,⼀个是操作系统中管理内存的⼀块区域分段。

二叉树底层可以是数组也可是链表,用数组实现的二叉树就是堆

2.3.2 链式结构

⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 。链式结构⼜分为⼆叉链和三叉链,当前我们学习中⼀般都是⼆叉链。后⾯课程学到⾼阶数据结构如红⿊树等会⽤到三叉链。
在这里插入图片描述

3. 实现顺序结构⼆叉树

⼀般堆使⽤顺序结构的数组来存储数据,堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备
其他的特性。

3.1 堆的概念与结构

如果有⼀个关键码的集合 ,把它的所有元素按完全⼆叉树的顺序存储⽅
式存储,在⼀个⼀维数组中,并满⾜:Ki <=K2*i+1(或Ki >=K2*i+1)则称为⼩堆(或⼤堆)。将根结点最⼤的堆叫做最⼤堆或⼤根堆,根结点最⼩的堆叫做最⼩堆或⼩根堆。
在这里插入图片描述

堆具有以下性质
• 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;
• 堆总是⼀棵完全⼆叉树。

⼆叉树性质
• 对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从0 开始编号,则对于序号为 i 的结点有:

  1. 若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
  2. 若 2i+1<n ,左孩⼦序号: 2i+1 , 若2i+1>=n ⽆左孩⼦
  3. 若 2i+2<n ,右孩⼦序号: 2i+2 , 若2i+2>=n ⽆右孩⼦

3.2 堆的实现

堆底层结构为数组,因此定义堆的结构为:

堆的删除
HPDataType HPTop(HP* php);
删除堆顶的数据
void HPPop(HP* php);
/判空
bool HPEmpty(HP* php);
求size
int HPSize(HP* php);
向上调整算法
void AdjustUp(HPDataType* a, int child);
向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);
堆的删除
HPDataType HPTop(HP* php);
删除堆顶的数据
void HPPop(HP* php);
判空
bool HPEmpty(HP* php);
求size
int HPSize(HP* php);
向上调整算法
void AdjustUp(HPDataType* a, int child);
向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);

编写代码

typedef int HPDataType;
//定义堆的结构--数组
typedef struct Heap {
	HPDataType* arr;
	int size;
	int capacity;
}HP;

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HPInit(HP* php)
{
	assert(php);
	php->arr = NULL;
	php->size = php->capacity = 0;
}

void HPDestrory(HP* php)
{
	assert(php);
	if(php->arr)
	free(php->arr);
	php->arr = NULL;
	php->size = php->capacity = 0;
}

void swap(HPDataType* x1, HPDataType* x2)
{
	HPDataType tmp = *x1;
	*x1 = *x2;
	*x2 = tmp;
}

void AdjustUp(HPDataType* arr, int child)
{
	int parent=(child - 1) / 2;
	while (parent>=0)//也可以用child,自己尝试一下
	{
		if (arr[child] < arr[parent])
		{
			swap(&arr[parent], &arr[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

向上调整算法我们往堆中插入数据时在堆尾,插入的数据会导致堆不符合小堆或大堆,此时要向上调整让新插入元素调整到正确为止
在这里插入图片描述


void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int NewCapacity = php->capacity == 0 ? 4 : php->capacity;
		HPDataType* tmp = realloc(php->arr, sizeof(HPDataType) * NewCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		php->arr = tmp;
	}
	php->arr[php->size] = x;
	AdjustUp(php->arr, php->size);
	php->size++;
}

插入数据程序跟顺序表大差不差,只是要进行向上调整


void AdjustDown(HPDataType* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child<n)
	{
		//找左右孩子中最小的
		if (child+1<n && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向下调整算法是针对删除元素
在这里插入图片描述


void HPPop(HP* php)
{
	assert(php && php->size);
	swap(&php->arr[0], &php->arr[php->size-1]);
	php->size--;
	AdjustDown(php->arr, 0, php->size);
}

删除堆顶元素,直接交换堆顶和堆尾元素。进行向上调整,注意size–的先后顺序不会影响调整结果,因为不管向上调整还是向下调整算法只是针对让当前元素调整到正确的位置,如果删除的元素也参加到调整的过程中由于交换前堆顶元素是最小值,调整过程中不会影响到删除的元素(它依然为处于堆尾)。


HPDataType HPTop(HP* php)
{
	assert(php && php->size);
	return php->arr[0];
}

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

3.3 堆的应⽤

3.3.1 堆排序

版本⼀:基于已有数组建堆、取堆顶元素完成排序版本

空间复杂度为O(N),时间复杂度为O(N)

HP hp;
HPInit(&hp);

int arr[] = { 17,20,10,13,19,15 };
for (int i = 0; i < 6; i++)
{
	HPPush(&hp, arr[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
	arr[i++] = HPTop(&hp);
	HPPop(&hp);
}

额外开辟了栈

版本二:数组建堆,⾸尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次⼤的数据

建堆
升序—大堆
降序----小堆
向上算法建堆


时间复杂度为O(nlogn)
向下调整算法建堆
时间复杂度为O(n)

void HeapSort(int* arr, int n)
{
    //向上调整算法建堆
	//for (int i = 0; i < n; i++)
	//{
	//	AdjustUp(arr, i);
	//}

	//向下调整算法建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, i, n);//保证最后以一棵子树是堆,对于一个二叉树(最多)
	}

	//循环将堆顶数据跟最后位置(会变化,每次减少一个数据)的数据进行交换
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, 0, end);
		end--;
	}
}

向上调整之前要保证上面的数组都是堆,向下调整算法之前要保证下面的数组都是堆。数组本身按顺序就可以向上建堆,而向下建堆要保证以最后一个度不为0的根结点的子树为堆


3.4时间复杂度

3.4.1 向下调整算法复杂度

在这里插入图片描述

T(N)=20 *0+21 *1+22 *2+23 *3+…+2h-1 *(h-1)
2T(N) = 21 *0+22 *1+23 *2+…+2h-1 *(h-2)+2h *(h-1)
错位相减,注意
2h =2log2(N+1) =(N+1)
h=log2(n+1)
T(N)=n-log2(n+1) ≈n
O(N)=n

3.4.2 向上调整算法复杂度

在这里插入图片描述

过程同上 O(N)=nlog2n

3.4.3 堆排序算法复杂度

对数组进行向下调整建堆时间复杂度为O(N)

在这里插入图片描述

排序从少结点到多结点,递归式去排序,先排20 个结点,此时层数为1层。21 个结点,此时层数为2层…
最后排到2h-1 哥结点,此时层数为h层。

4. 实现链式结构⼆叉树

⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组
成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 ,
其结构如下:

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

⼆叉树的创建⽅式⽐较复杂,我们往往手动创建一棵二叉树

#include"Tree.h"

BTNode* buyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->left = newnode->right = NULL;

	return newnode;
}

void test01()
{
	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 = node3;
	node2->left = node4;
	node2->right = node5;
	node3->left = node6;
}

回顾⼆叉树的概念,⼆叉树分为空树和⾮空⼆叉树,⾮空⼆叉树由根结点、根结点的左⼦树、根结点的右⼦树组成的
在这里插入图片描述

根结点的左⼦树和右⼦树分别⼜是由⼦树结点、⼦树结点的左⼦树、⼦树结点的右⼦树组成的,因此⼆叉树定义是递归式的,后序链式⼆叉树的操作中基本都是按照该概念实现的。

4.1 前中后序遍历

⼆叉树的操作通过树的遍历来实现

4.1.1 遍历规则

按照规则,⼆叉树的遍历有:前序/中序/后序的递归结构遍历:

1)前序遍历(Preorder Traversal 亦称先序遍历):访问根结点的操作发⽣在遍历其左右⼦树之前
访问顺序为:根结点、左⼦树、右⼦树
2)中序遍历(Inorder Traversal):访问根结点的操作发⽣在遍历其左右⼦树之中(间)
访问顺序为:左⼦树、根结点、右⼦树
3)后序遍历(Postorder Traversal):访问根结点的操作发⽣在遍历其左右⼦树之后
访问顺序为:左⼦树、右⼦树、根结点

编写代码

void PreOrder(BTNode*root)
{
	if (root = NULL)
	{
		return;
	}
	printf("%d", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

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

void PostOrder(BTNode* root)
{
	if (root = NULL)
	{
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d", root->data);
}

以前序遍历为例
在这里插入图片描述
关于函数递归的内容前面的博客有写,关于函数栈帧的创建与销毁的内容今天会更新

4.2 结点个数以及⾼度等

⼆叉树结点个数
int BinaryTreeSize(BTNode* root);
⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root);
⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root);
⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
⼆叉树销毁
void BinaryTreeDestory(BTNode** root);

编写代码

int BinaryTreesize(BTNode* root)
{
	if (root = NULL)
	{
		return 0;
	}
	return 1+BinaryTreesize (root->left)+BinaryTreesize(root->right);
}

int BinaryTreeLeafsize(BTNode* root)
{
	if (root = NULL)
	{
		return 0;
	}
	if (root->left ==NULL && root->right==NULL)
	{
		return 1;
	}
	return BinaryTreeLeafsize(root->left) + BinaryTreeLeafsize(root->right);
}

int BinaryTreeLevelKsize(BTNode* root,int k)
{
	if (root = NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLeefsize(root->left, k - 1) + BinaryTreeLeefsize(root->right, k - 1);
}

k=k时遍历根节点(第一层),k=k-1遍历第二层结点,k=1遍历第k层结点


int BinaryTreeDepth(BTNode* root)
{
	if (root = NULL)
	{
		return 0;
	}
	int leftDep = BinaryTreeDepth(root->left);
	int rightDep = BinaryTreeDepth(root->right);
	return leftDep > rightDep ? leftDep + 1 : rightDep + 1;
}

树的高度为子树最大的高度+1


BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root = NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* leftFind = BinaryTreeFind(root->left, x);
	if (leftFind)
	{
		return leftFind;
	}
	BTNode* rightFind = BinaryTreeFind(root->right, x);
	if(rightFind)
	{
		return rightFind;
	}
	return NULL;
}

如果在左子树找到了就不需要遍历右子树


void BinaryTreeDestrory(BTNode** root)
{
	if (*root==NULL)
	{
		return;
	}
	BinaryTreeDestrory(&(*root)->left);
	BinaryTreeDestrory(&(*root)->right);
	free(*root);
	*root = NULL;
}

要将传过来的指针置空,传地址


4.3 层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对⼆叉树进⾏层序遍历。
设⼆叉树的根结点所在层数为1,层序遍历就是从所在⼆叉树的根结点出发,⾸先访问第⼀层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,⾃上⽽下,⾃左⾄右逐层访问树的结点的过程就是层序遍历
实现层序遍历需要额外借助数据结构:队列
在这里插入图片描述

typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInitialise(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->phead == NULL && pq->ptail == NULL)
	{
		return false;
	}
	return true;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NewNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	NewNode->data = x;
	NewNode->next = NULL;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = NewNode;
	}
	else
	{
		pq->ptail->next = NewNode;
		pq->ptail = NewNode;
	}
	pq->size++;
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	QueueNode* Next = pq->phead->next;
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	free(pq->phead);
	pq->phead = NULL;
	pq->phead = Next;
	pq->size--;
}

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	return pq->phead->data;
}

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	return pq->ptail->data;
}

int QueueSize(Queue* pq)
{
	assert(pq);
	/*size_t size = 0;
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		size++;
		pcur = pcur->next;
	}*/
	return pq->size;
}

void QueueDestroy(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));

	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}


typedef int QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;
void QueueInitialise(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->phead == NULL && pq->ptail == NULL)
	{
		return false;
	}
	return true;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NewNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	NewNode->data = x;
	NewNode->next = NULL;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = NewNode;
	}
	else
	{
		pq->ptail->next = NewNode;
		pq->ptail = NewNode;
	}
	pq->size++;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	QueueNode* Next = pq->phead->next;
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	free(pq->phead);
	pq->phead = NULL;
	pq->phead = Next;
	pq->size--;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	return pq->phead->data;
}

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInitialise(&q);
	QueuePush(&q, root);
	while (QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d", front->data);
		QueuePop(&q);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		QueuePush(&q, front->right);
	}
	QueueDestroy(&q);
}

在这里插入图片描述

4.4判断是是否为完全二叉树

typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInitialise(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->phead == NULL && pq->ptail == NULL)
	{
		return false;
	}
	return true;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NewNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	NewNode->data = x;
	NewNode->next = NULL;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = NewNode;
	}
	else
	{
		pq->ptail->next = NewNode;
		pq->ptail = NewNode;
	}
	pq->size++;
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	QueueNode* Next = pq->phead->next;
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	free(pq->phead);
	pq->phead = NULL;
	pq->phead = Next;
	pq->size--;
}

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	return pq->phead->data;
}

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	return pq->ptail->data;
}

int QueueSize(Queue* pq)
{
	assert(pq);
	/*size_t size = 0;
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		size++;
		pcur = pcur->next;
	}*/
	return pq->size;
}

void QueueDestroy(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));

	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}


typedef int QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;
void QueueInitialise(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->phead == NULL && pq->ptail == NULL)
	{
		return false;
	}
	return true;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NewNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	NewNode->data = x;
	NewNode->next = NULL;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = NewNode;
	}
	else
	{
		pq->ptail->next = NewNode;
		pq->ptail = NewNode;
	}
	pq->size++;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	QueueNode* Next = pq->phead->next;
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	free(pq->phead);
	pq->phead = NULL;
	pq->phead = Next;
	pq->size--;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(QueueEmpty(pq));
	return pq->phead->data;
}


bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInitialise(&q);
	QueuePush(&q, root);
	while (QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	//队列不一定为空
	while (QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

基本思路与层序遍历大差不差,只是要遍历两次,第一次遍历二叉树直到遇到空结点,若二叉树为非完全二叉树此时二叉树剩余的结点至少有一个为非空结点

5.二叉树的性质

对任何⼀棵⼆叉树, 如果度为 0的叶结点个数为 n0 , 度为 2 的分⽀结点个数为 n2 ,则有n0 = n2 + 1

在这里插入图片描述

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值