初阶数据结构4 二叉树

news2024/10/2 3:33:32

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1979386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flink实时数仓(六)【DWD 层搭建(四)交易域、互动域、用户域实现】

前言 今天的任务是完成 DWD 层剩余的事实表&#xff1b;今年的秋招开得比往年早&#xff0c;所以要抓紧时间了&#xff0c;据了解&#xff0c;今年的 hc 还是不多&#xff0c;要是晚点投铁定寄中寄了&#xff1b; 今天还是个周末&#xff0c;不过记忆里我好像整个大学都没有好好…

如何实现ElementUI表单项label的文字提示?

在Vue和ElementUI的丰富组件库中,定制化表单是常见的需求之一。那么如何在表单项label后添加文字提示,以提升用户体验呢? 首先我们来看一下效果图: 这里我们鼠标移动到❓图标上就会出现提示 在 ElementUI 中,el-form-item 组件允许使用 slot 自定义 label。通过在 el-fo…

Boost:asio网络编程从同步到异步

文章目录 同步服务器客户端服务端 异步服务器(有问题)异步服务器优化 在学TCP的时候&#xff0c;写的第一个服务器就是一个echo服务器&#xff0c;那在Boost网络编程中&#xff0c;自然也先写一个echo服务器练练手 同步服务器 客户端 #include <iostream> #include &l…

<数据集>BDD100K人车识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;15807张 标注数量(xml文件个数)&#xff1a;15807 标注数量(txt文件个数)&#xff1a;15807 标注类别数&#xff1a;7 标注类别名称&#xff1a; [pedestrian, car, bus, rider, motorcycle, truck, bicycle] 序号…

Java-变量,运算符,输入与输出

目录 一&#xff0c;语法基础 1.基本Java程序 2.语法基础 2.1 变量 2.2 常量限制(fiinal)类比C中的const 2.3 类型转化 2.4 运算符 2.5 表达式 2.5 输入与输出 2.5.1 输入 2.5.2 输出 一&#xff0c;语法基础 1.基本Java程序 public class Main{public static void…

差分放大电路

目录 引出 复合管 直接耦合放大电路 问题: 怎么抑制 初代电路(已引入负反馈之后) 分析 怎么解决 镜像电路 两个概念 分析直流通路: 分析交流电路: 差分放大电路的分析 交流通路 简化 H参数等效 可以得到 其他接法 引出 复合管 目的:获得更大的放大倍数 多只…

3个二创文案生成器,让文案创作变简单

在当今数字时代&#xff0c;内容创作已经成为了一项非常重要的工作。无论是为了推广产品、营销服务&#xff0c;还是仅仅为了吸引读者&#xff0c;优质的文案都是至关重要的。然而&#xff0c;对于许多人来说&#xff0c;写出令人印象深刻的文案并不容易。这就是为什么二创文案…

基于Django框架的挂号诊疗系统(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍&#xff1a;我是程序员阿龙&#xff…

【优秀python系统案例】基于python Flask的电影票房数据爬取与可视化系统的设计与实现

第1章 绪论 1.1 研究背景及意义 进入21世纪&#xff0c;特别是第二个十年&#xff0c;世界互联网取得了惊人的快速发展。根据分析师玛丽米克尔 (Mary Mikel) 2016年发布的一份互联网趋势报告&#xff0c;到2016年年中&#xff0c;全球互联网用户超过30亿&#xff0c;约占全球…

职场上的人情世故,你知多少?

对于职场新人来说&#xff0c;在学习人情世故时&#xff0c;不仅要学会哪些事情该做&#xff0c;还需要知道哪些事情千万不能做&#xff0c;这样才能让自己起码不会得罪别人&#xff0c;甚至得到更多的晋升机会。学会下面的四大职场规则&#xff0c;能让你的职场生涯更顺利。 …

MUSE Multi-View Contrastive Learningfor Heterophilic Graphs

发表于:CIKM 推荐指数: #paper/⭐ 一句话总结:融合了GCN(A,X)和GCN(A,I),创新性不足,因此只能B会 流程: 融合部分: h i f h i s λ i h i c h_i^fh_i^s\lambda_ih_i^c hif​his​λi​hic​ 由于有n个 λ \lambda λ.因此作者加了如下优化: L ϕ ∑ i 1 N λ i s ( h i …

贪心算法之货仓选址问题

#include<stdio.h> #include<stdlib.h> #include<math.h>//贪心算法之货仓选址问题/*** void* p是万能指针&#xff0c;可以和其它任意类型的指针进行转换&#xff0c;前提是确保转换是合法的*/ //写好用于qsort的比较函数&#xff0c;这里写的函数一般用于…

【K8S】为什么需要Kubernetes?

文章目录 1 什么是Kubernetes&#xff1f;2 三种常见的应用部署方式2.1 传统部署2.2 虚拟化部署2.3 容器化部署 3 Kubernetes的特点写在最后 1 什么是Kubernetes&#xff1f; Kubernetes是 一个开源的&#xff0c;用于管理云平台中多个主机上的容器化应用&#xff0c;Kubernet…

分享6款有助于写论文能用到的软件app!

在学术写作中&#xff0c;选择合适的软件和工具可以大大提高效率和质量。以下是六款有助于写论文的软件app推荐&#xff0c;其中特别重点介绍千笔-AIPassPaPer这款AI原创论文写作平台。 1. 千笔-AIPassPaPer 千笔-AIPassPaPer是一款功能全面且高效的AI原创论文写作平台。它能…

RabbitMQ高级特性 - 非持久化 / 持久化(交换机、队列、消息)

文章目录 RabbitMQ 持久化机制概述实现非持久化&#xff08;交换机、队列、消息&#xff09;实现持久化&#xff08;交换机、队列、消息&#xff09; RabbitMQ 持久化机制 概述 前面讲到了 生产者消息确认机制 和 消费者消息确认机制&#xff0c;保证了消息传输的可靠性&#…

断电引起redo和数据文件不一致故障恢复---惜分飞

有些时候故障总是来的让人非常意外,这个在准备停机迁移数据库之前的几分钟由于某种原因直接导致主机掉电,再次开机数据库无法启动 Sat Aug 03 23:10:37 2024 Successful mount of redo thread 1, with mount id 3696805928 Database mounted in Exclusive Mode Lost write prot…

【考研高数】tan(arcsin x)、tan(arccos x)、sin(arccos x)、cos(arcsin x) 等于多少?

在做题的时候&#xff0c;我们可能会用到下面这几个式子的值&#xff0c;在这里&#xff0c;「荒原之梦考研数学」先给出结论&#xff0c;在这些结论的后面&#xff0c;也给同学们放上了具体的证明过程&#xff1a; tan ⁡ ( arcsin ⁡ x ) x 1 − x 2 \tan(\arcsin x) \frac…

深度分析AI大模型赋能后,内容生产行业的发展现状

国内首部由AI全流程参与制作的微短剧《补天》近日正式发布&#xff0c;从剧本构思、角色设定到场景设计以及后期剪辑&#xff0c;各个环节均深度融入了AI技术&#xff0c;展现了人工智能在内容创作方面的全面能力。 AI大模型的兴起正在深刻地改变着内容生产的方式&#xff0c;…

Animate软件基本概念:帧及关键帧

FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#xff1a;Animate教程及作品源文件https://zhuanlan.zhihu.co…

VBA读取不带后缀名文本文件的方法(解决Unix文本文件在Windows下变成一行的读取)

VBA在操作Excel等Office软件方面有天然的优势&#xff0c;虽说现在Python的Pandas&#xff0c;openpyxl和Java的poi包都可以处理Excel文件&#xff0c;但有两个问题&#xff1a;首先&#xff0c;目标电脑上必须先按照Java或python环境&#xff0c;如果在一些机构内部处于安全原…