C语言数据结构(超详细讲解)| 二叉树的实现

二叉树

引言

在计算机科学中,数据结构是算法设计的基石,而二叉树(Binary Tree)作为一种基础且广泛应用的数据结构,具有重要的地位。无论是在数据库索引、内存管理,还是在编译器实现中,二叉树都扮演着关键角色。本文将带你深入了解二叉树的基本概念、性质,以及如何在C语言中实现一个完整的二叉树。

首先,我们将探讨二叉树的定义和基本特性,包括其节点、子节点、以及树的高度和深度等基本概念。接着,我们会逐步介绍如何使用C语言构建二叉树,涉及节点的定义、树的初始化、节点的插入与删除等基本操作。最后,我们还将讨论一些常见的二叉树遍历算法,如前序遍历、中序遍历和后序遍历,并提供相应的代码示例。

通过这篇文章,你不仅能掌握二叉树的基本理论,还能学会如何在C语言中灵活运用这一数据结构,为解决实际编程问题提供有力的工具和方法。让我们一起开启这段探索二叉树世界的旅程吧!

本篇文章重点在4. 二叉树的链式结构及实现,有需要的小伙伴可以直接点击目录进行跳转

目录

  • 二叉树
    • 引言
    • 1. 树的概念及表示方法
      • 1.1 树的概念
      • 1.2 树的表示
    • 2. 二叉树的概念及性质
      • 2.1 二叉树的概念
      • 2.2 特殊的二叉树
      • 2.3 二叉树的性质
    • 3. 二叉树的顺序结构及实现
      • 3.1 堆的概念及结构
      • 3.2 堆的实现
        • 3.2.1 定义堆的结构体
        • 3.2.2 堆的初始化
        • 3.2.3 堆的销毁
        • 3.2.4 数据交换函数
        • 3.2.5 向上调整函数
        • 3.2.6 堆的插入
        • 3.2.7 向下调整算法
        • 3.2.8 堆的删除
        • 3.2.9 取堆顶的数据
        • 3.2.10 堆的判空
        • 3.2.11 堆的数据个数
      • 3.3 建堆的时间复杂度
      • 3.4 堆的应用
        • 3.4.1 堆排序
        • 3.4.2 Top-k 问题
    • 4. 二叉树的链式结构及实现
      • 4.1 二叉树的实现
        • 4.1.1 二叉树的前序遍历
        • 4.1.2 二叉树的中序遍历
        • 4.1.3 二叉树的后序遍历
        • 4.1.4 根据数组创建二叉树
        • 4.1.5 二叉树的销毁
        • 4.1.6 二叉树的节点个数
        • 4.1.7 二叉树叶子节点个数
        • 4.1.8 二叉树第k层节点个数
        • 4.1.9 二叉树查找值为x的节点
        • 4.1.10 层序遍历
        • 4.1.11 判断二叉树是否是完全二叉树

1. 树的概念及表示方法

在这一部分我会简单的介绍一下树的相关概念,不会太详细,因为这篇文章着重介绍的是二叉树,所以默认读者是对树这种结构有一定了解了,如果给您带来不便,还请见谅。

1.1 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
在这里插入图片描述
注意:树型结构中,子树之间不能有交集,否则就不是树形结构
在这里插入图片描述
重点
在这里插入图片描述


1.2 树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法。

typedef int DataType;
struct Node
{struct Node* firstChild1; // 第一个孩子结点struct Node* pNextBrother; // 指向其下一个兄弟结点DataType data; // 结点中的数据域
};

在这里插入图片描述


2. 二叉树的概念及性质

2.1 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者由一个根结点加上两棵别称为左子树和右子树的二叉树组成。在这里插入图片描述

二叉树的特点:

  1. 二叉树不存在度大于2的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注意:对于任意的二叉树都是由以下几种情况复合而成的:
在这里插入图片描述

2.2 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k)-1 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

对于完全二叉树的概念介绍大家可能还不能深刻理解什么是完全二叉树,我来给大家总结一下,我们可以对着下面的图片进行理解。
在这里插入图片描述
假设树的高度为h,前h-1层都是满的,最后一层不满,但是最后一层一定是从左往右连续分布的,不能有空节点。这就是完全二叉树。


2.3 二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的i层上最多有2^(i-1)个结点.
  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是(2^h)-1 .
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0 = n2 +1的关系.

下边附上性质3的证明

/*
* 假设二叉树有N个结点
* 从总结点数角度考虑:N = n0 + n1 + n2 ①
* 
* 从边的角度考虑,N个结点的任意二叉树,总共有N-1条边
* 因为二叉树中每个结点都有双亲,根结点没有双亲,每个节点向上与其双亲之间存在一条边
* 因此N个结点的二叉树总共有N-1条边
* 
* 因为度为0的结点没有孩子,故度为0的结点不产生边; 度为1的结点只有一个孩子,故每个度为1的结
点* * 产生一条边; 度为2的结点有2个孩子,故每个度为2的结点产生两条边,所以总边数为:
n1+2*n2 
* 故从边的角度考虑:N-1 = n1 + 2*n2 ②
* 结合① 和 ②得:n0 + n1 + n2 = n1 + 2*n2 - 1
* 即:n0 = n2 + 1
*/
  1. 若规定根结点的层数为1,具有n个结点的满二叉树的深度为h,节点数与深度的关系为:h=log2(n+1) . (ps:关系式是log以2为底,n+1为对数)
  2. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
    1.i>0i位置结点的双亲序号:(i-1)/2i=0i为根结点编号,无双亲结点
    2.2i+1<n,左孩子序号:2i+12i+1>=n否则无左孩子
    3.2i+2<n,右孩子序号:2i+22i+2>=n否则无右孩

在讲二叉树的存储结构之前,我先为大家简单的说明下这两种存储结构(顺序结构、链式结构)。

  1. 顺序存储
    顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有才会使用数组来存储。二叉树顺序存储结构在物理上是一个数组,在逻辑上是一颗二叉树。
    在这里插入图片描述
  2. 链式存储
    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。
    在这里插入图片描述
    代码展示二叉树的结构
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{struct BinTreeNode* left; // 指向当前结点左孩子struct BinTreeNode* right; // 指向当前结点右孩子BTDataType data; // 当前结点值域
}

3. 二叉树的顺序结构及实现

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

用数组来存储就避不开一个问题,当你拥有父节点的下标时,你如何快速的找到左右子节点的下标?
在这里插入图片描述
这张图总结的父子节点下标关系,是大家必须要掌握的,因为后边在顺序存储结构实现阶段会频繁用到

接下来我们将通过堆来继续学习顺序存储结构


3.1 堆的概念及结构

堆的概念实在是有点晦涩难懂,在这里我斗胆带大家通过堆的性质和图片实例简单的了解一下什么是堆。
堆主要有两种类型:大根堆和小根堆。

堆的性质:

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

在这里插入图片描述
根据上面的信息我们可以看出,最大堆的根节点是所有节点的中最大的,小根堆同理。每个节点都比自己的左右子节点要大或者小。希望大家现在可以对“堆”清晰的认识了。


3.2 堆的实现

3.2.1 定义堆的结构体
typedef int HPDataType;typedef struct Heap
{HPDataType* a;	//堆是基于数组结构来实现的int size;		//数组下标int capacity;	//数组容量
}HP;
3.2.2 堆的初始化
//堆的初始化
void HPInit(HP* php)	//传入数组结构的指针
{assert(php);php->a = NULL;	//置空php->size = php->capacity = 0;	//归零
}
3.2.3 堆的销毁
//堆的销毁
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

3.2.4 数据交换函数
//数据交换
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}

3.2.5 向上调整函数
//向上调整——可以将数组恢复成堆的顺序——小根堆
void AdjustUp(HPDataType* a, int child)	//传入数组的地址和新插入的叶节点的下标
{int parent = (child - 1) / 2;	//无论是奇偶子节点,均可以通过数组的下标“-1再/2”来获得父节点的下标//while (parent >= 0) 为了防止数组的越界访问,我们不采取这个循环条件while (child > 0){if (a[child] < a[parent])		//如果子节点小于父节点{Swap(&a[child], &a[parent]);	//使用交换函数,交换父子的数据child = parent;				//子节点位置换到父节点上parent = (child - 1) / 2;	//然后父节点通过转换方法重新指向变换后的子节点的父节点		}else{break;						//如果子节点大于父节点就不需要调整了}}
}
3.2.6 堆的插入

示例:先插入一个10到数组的尾上,在进行向上调整算法,直到满足堆。
在这里插入图片描述

//堆的插入
void HPPush(HP* php, HPDataType x)
{assert(php);	//传入的堆不能为空if (php->size == php->capacity)	//堆的空间如果不足,则进行扩容{int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;	//将数据x插入,数组下标为size的空间中php->size++;AdjustUp(php->a, php->size - 1);	//利用向上调整将插入新数据之后的数组恢复为堆
}

3.2.7 向下调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

在这里插入图片描述

//向下调整——可以将数组恢复成堆的顺序——小根堆
void AdjustDown(HPDataType* a, int n, int parent)	//传入数组的地址和数组的总大小还有根的下标
{	//传入根位置的下标是因为需要调整的元素的位置就是根位置// 先假设左孩子小——利用假设法int child = parent * 2 + 1;	//第一次先找到根节点下面的子节点while (child < n)  // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent])	{Swap(&a[child], &a[parent]);	//将小的子节点和父节点交换数据parent = child;				//父节点位置换到子节点上child = parent * 2 + 1;		//然后子节点通过转换方法重新指向变换后的父节点的子节点	}else{break;						//如果最小的节点都大于父节点了,就说明不需要调了}}
}

堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过向下调整算法,把它构建成一个堆。从倒数的第一个非叶子结点的子树开始调整,一直调整到根结点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6};

图片中的是调整为大根堆,思路是一样的,代码只有些许不同
在这里插入图片描述

3.2.8 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据跟最后一个数据一换,然后删除数组最后一个数据,再进行向下调
整算法。
在这里插入图片描述

//堆顶的删除
void HPPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);//要想删除堆顶元素,需要先将堆顶元素与数组最后一个元素交换位置php->size--;							 //这样可以保持除堆顶以外的元素仍然能保持堆的顺序,可以大大提升效率	AdjustDown(php->a, php->size, 0);	//利用向下调整将删除数据之后的数组恢复为堆
}

3.2.9 取堆顶的数据
//取堆顶的数据
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}
3.2.10 堆的判空
//堆的判空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}
3.2.11 堆的数据个数
//堆的数据个数
int HPSize(HP* php)
{assert(php);return php->size;
}

3.3 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化,使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个结点不影响最终结果):
在这里插入图片描述
在这里插入图片描述


3.4 堆的应用

3.4.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆
    升序:建大堆
    降序:建小堆
  2. 利用堆删除思想来进行排序
    建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
    在这里插入图片描述
//堆排序 0(N*logN)
void HeapSort(int* a, int n)
{for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

3.4.2 Top-k 问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

粗略讲解图
在这里插入图片描述
为了方便测试Top-k方法,我们写一个造数据的函数

//造数据
void CreateNDate()
{// 造数据int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) ;fprintf(fin, "%d\n", x);}fclose(fin);
}

Top-k方法实现

//Top-k方法
void Topk()
{int k;printf("请输入k>:");scanf("%d", &k);int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc fail");return;}const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}// 读取文件中前k个数for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}// 建K个数的小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(kminheap, k, i);}// 读取剩下的N-K个数int x = 0;while (fscanf(fout, "%d", &x) > 0){if (x > kminheap[0]){kminheap[0] = x;AdjustDown(kminheap, k, 0);}}printf("最大前%d个数:", k);for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");
}

4. 二叉树的链式结构及实现

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是在二叉树上进行其它运算的基础。

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

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

这3种遍历方式均是深度优先遍历

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。


下面主要分析前序递归遍历,中序与后序都是同理,大家可以自行分析。

前序遍历递归图解
在这里插入图片描述
在这里插入图片描述


4.1 二叉树的实现

因为二叉树的递归结构,可能会有些绕,我也尽量能配图的我就配图帮助大家理解,在二叉树中,大家自己的反复画图,和逻辑推理也是不能少的,只有足够的耐心才能掌握二叉树。

4.1.1 二叉树的前序遍历
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{//先判空if (root == NULL){return;}//打印根节点printf("%c",root->data);//向下递归此根节点的左子树和右子树BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}

后面在实现结构的时候我们会经常遇到这段代码

 //先判空if (root == NULL){return;}

它的作用,我大概总结有以下几点,希望大家能够牢记

//开头的判空可以防止空指针的解引用
//递归遍历都需要先判空,因为这是“归”的条件
//当“递”到叶子节点的子节点的时候,一定为空,这时候需要return往回归
4.1.2 二叉树的中序遍历
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root) 
{if (root == NULL) {return;}BinaryTreeInOrder(root->left);printf("%c", root->data);BinaryTreeInOrder(root->right);
}

在这里插入图片描述

4.1.3 二叉树的后序遍历
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){return;}BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);printf("%c", root->data);
}
4.1.4 根据数组创建二叉树
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
//定义一个构造二叉树函数,将前序遍历的字符串用节点全部构建出来
//传入字符串和一个下标变量(此变量需要地址来改变,防止同时传给左右子树相同下标导致数组中数据覆盖)
BTNode* BinaryTreeCreate(char* a, int* pi) 
{//首先先判空if (a[*pi] == '#') {(*pi)++;return NULL;}//前序遍历保存根节点BTNode* root = (BTNode*)malloc(sizeof(BTNode));root->data = a[(*pi)++];//对左右子树进行递归root->left = BinaryTreeCreate(a, pi);root->right = BinaryTreeCreate(a, pi);return root;
}

在这里插入图片描述

4.1.5 二叉树的销毁
// 二叉树销毁   使用后序遍历的思想从后往前销毁每一个节点
void BinaryTreeDestory(BTNode* root)   
{	//要销毁二叉树链表就得把链表的头结点的指针的地址传入if (root == NULL)return;BinaryTreeDestory(root->left);BinaryTreeDestory(root->right);free(root);
}
4.1.6 二叉树的节点个数
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}   //子树节点个数等于左右节点个数+根节点(1)个数

在这里插入图片描述

4.1.7 二叉树叶子节点个数
//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{//先判空if (root == NULL){return 0;}//如果左右子节点都为空就是叶子结点,则返回1.if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}   //通过+运算和return可以达到统计的功能

在这里插入图片描述

4.1.8 二叉树第k层节点个数
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{   //参数部分的k没有传地址,是因为需要左右子树记录向下递的层数相同。if (root == NULL){return 0;}//k--,减到1的时候就是第k层了,返回1if (k == 1){return 1;}return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}   //通过+运算和return对k层节点数进行统计并返回顶层   //每次递下去,对k-1,可以达到对应层k为1的效果

在这里插入图片描述

4.1.9 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//用前序遍历思想进行比较,找到就返回地址if (root == NULL){return NULL;}//进行比较,判断此节点是否为x,不是就向下递归if (root->data == x){return root;}//递归函数将归回来的结果传给变量进行判断,这样才能将结果递归回去。BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1)   //if的结果只有可能是NULL和值为x的节点指针root{return ret1;}BTNode* ret2 = BinaryTreeFind(root->right, x);if (ret2){return ret2;}//如果下边都没有那就向上返回NULL,找到了就返回节点地址return NULL;
}

在这里插入图片描述


层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
在这里插入图片描述
在实现层序遍历的时候我们需要用到队列结构,如果有对队列结构还不熟悉的同学,可以先点击链接跳转,学习过后再来看,可能也会有更多的收货。

4.1.10 层序遍历
//层序遍历
//层序遍历不像二叉树一样递归遍历,而是用队列结构通过循环先进先出进行遍历
//运用上一层根节点出带动下一层子节点入
void BinaryTreeLevelOrder(BTNode* root)
{Queue Q;QueueInit(&Q);          //先初始化队列结构,实现先进先出的功能,进行一个层序遍历if (root)       //根不为空的时候,就入对列开始遍历 {                       //如果为空,直接跳到函数的最后销毁QueuePush(&Q, root);}while(!QueueEmpty(&Q))      //队列不为空就一直循环{BTNode* front = QueueFront(&Q);//需要创建一个指针,指向队顶的元素,防止Pop出队顶元素,找不到其子节点QueuePop(&Q);       //排出上一层的子节点printf("%c", front->data);//并打印if (front->left)    //带入下一层的节点(只要不为空){QueuePush(&Q, front->left);}if (front->right){QueuePush(&Q, front->right);}}QueueDestroy(&Q);
}
4.1.11 判断二叉树是否是完全二叉树
// 判断二叉树是否是完全二叉树
//当所有非空节点都出队列的时候,判定队列是否为空,即可判断其是否为完全二叉树
bool BinaryTreeComplete(BTNode* root)
{Queue Q;QueueInit(&Q);          //先初始化队列结构,实现先进先出的功能,进行一个层序遍历if (root)       //根不为空的时候,就入对列开始遍历 {                       //如果为空,直接跳到函数的最后销毁,并返回falseQueuePush(&Q, root);}while (!QueueEmpty(&Q))      //队列不为空就一直循环{BTNode* front = QueueFront(&Q);//需要创建一个指针,指向队顶的元素,防止Pop出队顶元素,找不到其子节点QueuePop(&Q);       //排出上一层的子节点//空节点也入队列,所以需要if语句来判断何时结束循环if (front == NULL)//如果前面的非空节点都Pop掉了,后面就应该全部是空节点了{                   break;        //所以break跳出循环,进入下一个循环来判断}//带入下一层的节点QueuePush(&Q, front->left);QueuePush(&Q, front->right);}while (!QueueEmpty(&Q)){BTNode* front = QueueFront(&Q);QueuePop(&Q);//如果进入了if语句,就说明此节点为非空节点,也就证明了此二叉树不是完全二叉树if (front){QueueDestroy(&Q);return false;}}//如果队列剩下的节点均为空节点,就可以销毁队列,并返回true了QueueDestroy(&Q);return true;
}

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

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

相关文章

记录Win11安装打印机驱动过程

1. 首先下载打印机对应型号的驱动 可以从这里下载&#xff1a;打印机驱动,打印机驱动下载 - 打印机驱动网 2. 下载 3. 打开控制面板-->设备和打印机 找到目标打印机添加设备即可 新增打印纸张尺寸

B站稿件生产平台高可用建设分享

背景 B站作为国内领先的内容分享平台&#xff0c;其核心功能之一便是支持UP主们创作并分享各类视频内容。UP主稿件系统作为B站内容生产的关键环节&#xff0c;承担着从内容创作到发布的全过程管理。为了满足不同创作者的需求&#xff0c;B站提供了多种投稿渠道&#xff0c;包括…

方差分析的七种类型

方差分析&#xff08;ANOVA&#xff09;是一种用于检验两个以上样本均数差别的显著性统计方法。根据不同的研究设计和数据类型&#xff0c;方差分析可以分为以下7种类型。 一、单因素方差分析 ①单因素方差分析说明 单因素方差分析用于研究一个定类数据&#xff08;自变量&am…

【原创教程】MES服务器与成品打标机控制说明

1 实现的功能及应用的场合 MES即制造执行系统(manufacturing execution system,简称MES),即在加强MRP计划的执行功能,把MRP计划同车间作业现场控制,通过执行系统联系起来。 MES是一个生产管理智能化的一个系统,是用于生产时记录数据、产量等信息的智能管理系统。 该项…

SpockMockStatic方法

SpockMockStatic方法 参考: https://blog.csdn.net/knighttools/article/details/44630975 ‍ static方法 import com.meituan.mafka.client.producer.IProducerProcessor; import com.meituan.mdp.langmodel.api.message.AssistantMessage; import com.sankuai.gaigc.arrang…

文件批量重命名001到100如何操作?这几个文件改名小技巧学起来

文件批量重命名001到100怎么操作&#xff1f;作为打工一族&#xff0c;每天都需要跟很多文件打交道&#xff0c;有时文件太多了&#xff0c;查找起来像是大海捞针&#xff0c;特别是图片文件。这个时候我们就需要对大量文件进行整理和排序&#xff0c;这样有助于提高我们的工作…

微信小程序 自定义 tabBar

自定义 tabBar | 微信开放文档 本文案例使用的Taro 非原生微信小程序 使用流程 1. 配置信息 在 app.json 中的 tabBar 项指定 custom 字段&#xff0c;同时其余 tabBar 相关配置也补充完整。所有 tab 页的 json 里需声明 usingComponents 项&#xff0c;也可以在 app.json 全局…

电脑提示缺少vcruntime140_1.dll的解决方法,总结7种有效方法

vcruntime140_1.dll是Microsoft Visual C 2015运行时库的一部分&#xff0c;它为使用Visual Studio 2015开发的应用程序提供了必要的运行时组件。该文件支持C程序的执行&#xff0c;包括内存管理、输入输出操作以及多线程功能等。缺失或损坏此文件可能导致应用程序无法启动或运…

广告联盟四大家

国内四大广告承接商&#xff1a;①抖音旗下-穿山甲②快手旗下-快手联盟③百度旗下-百青藤④腾讯旗下-优量汇 我们目前在互联网上能看到的所有广告都是由他们发放的&#xff0c;在其中我们打小游戏复活看广告&#xff0c;获得道具看广告&#xff0c;看剧看广告&#xff0c;这…

基于词频统计的聚类算法(kmeans)

基于词频统计的聚类算法&#xff08;kmeans&#xff09; 数据集是三个政府报告文件&#xff0c;这里就不做详细描述了&#xff0c;就是简单的txt文件。 实验过程主要分为如下几步&#xff1a; 1.读取数据并进行停用词过滤 2.统计词频 3.基于三篇文章词频统计的层次聚类 4.基于…

废品回收小程序怎么做?有哪些核心功能?

废品回收行业正逐步走向高质量发展的道路。在国家政策的推动下&#xff0c;再生资源市场需求旺盛&#xff0c;行业内部竞争格局逐渐明朗。 随着互联网技术的发展&#xff0c;"互联网回收"成为废品回收行业的一个新趋势。通过微信小程序这种线上平台&#xff0c;用户…

数据可视化在智慧园区中的核心价值解析

数据可视化在智慧园区中发挥着至关重要的价值。智慧园区是一种基于物联网、大数据、云计算等先进技术的现代化管理模式&#xff0c;旨在通过智能化手段提升园区的管理效率、服务水平和用户体验。而数据可视化作为数据处理和展示的重要工具&#xff0c;正是智慧园区实现这些目标…

BUG: VS Code C++输出中文乱码

BUG: VS Code C输出中文乱码 环境 Windows 11 VS Code 编辑器详情 在Windows 使用 cout 函数输出中文时出现乱码 问题的原因在cmd的显示编码和c程序编码的不同。cmd默认的是gbk编码&#xff0c;而VS Code 软件的CMD终端默认是utf-8编码&#xff0c;因而在输出中文文本时会出…

Ubuntu server 24 安装配置 snort3 3.2.1.0 网络入侵检测防御系统 配置注册规则集

一 下载并安装源代码 地址:https://github.com/snort3/snort3/releases #下载&#xff0c;解压 wget https://github.com/snort3/snort3/archive/refs/tags/3.2.1.0.tar.gz tar zxvf 3.2.1.0.tar.gz 二 安装软件依赖包 1 安装依赖包 sudo apt update sudo apt install…

代码随想录算法训练营第四十四天 | 01背包问题 二维、 01背包问题 一维、416. 分割等和子集

01背包问题 二维 代码随想录 视频讲解&#xff1a;带你学透0-1背包问题&#xff01;| 关于背包问题&#xff0c;你不清楚的地方&#xff0c;这里都讲了&#xff01;| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili 1.dp数组定义 dp[i][j] 下标为[0,i]之间的物品&…

【C#】类和对象的区别

1.区别概述 结构体和类的最大区别是在存储空间上&#xff0c;前者是值类型&#xff0c;后者是引用类型&#xff0c;它们在赋值上有很大的区别&#xff0c;在类中指向同一块空间的两个类的值会随一个类的改变而改变另一个&#xff0c;请看如下代码所示&#xff1a; namespace …

【漯河市人才交流中心_登录安全分析报告-Ajax泄漏滑动距离导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

JavaSE:异常

1、什么是异常 在生活当中&#xff0c;不管是人还是动物又或是植物&#xff0c;都会生病&#xff1b;在程序中也是&#xff0c;作为程序猿&#xff0c;虽然我们会尽力将程序写的完美&#xff0c;可难免会出现一些问题~ 在程序执行过程中&#xff0c;发生的一些不正常行为&…

Windows系统安装openvino(2024.1.0)

一、openvino下载&#xff1a; 下载地址&#xff1a;下载英特尔发行版 OpenVINO 工具套件 (intel.cn) 下载完之后将压缩包解压&#xff0c;然后重命名文件夹为openvino_2024.1.0。 二、环境配置 以python环境为例&#xff1a;&#xff08;建议使用moniconda虚拟环境来安装&am…

Android 图表开发开源库 MPAndroidChart 使用总结

1. 引言 电视项目中需要一个折线图表示节电数据变化情况&#xff0c;类比 H5 来说&#xff0c;Android 中也应该有比较成熟的控件&#xff0c;经过调研后&#xff0c;发现 MPAndroidChart 功能比较强大&#xff0c;网上也有人说可能是目前 Android 开发最好用的一个三方库了&a…