【二叉树从无到有】

news2024/11/24 5:59:13

目录:

  • 前言
  • 一、树
      • 1.什么是树结构?
      • 2.为什么使用树结构?
      • 3.树的概念
      • 4.树的表示
      • 5.树在现实生活中的应用
  • 二、二叉树
    • (一)二叉树概念既结构
      • 1.概念
      • 2.现实中的二叉树
      • 3.特殊的二叉树
      • 4.二叉树的性质
      • 5.二叉树的存储结构
    • (二)二叉树的顺序存储既实现
      • 1.二叉树的顺序存储
      • 2.堆的概念既结构
      • 3.堆的实现
        • 向下调整算法
        • 堆的创建
        • 建堆时间复杂度
        • 堆的插入
        • 堆的删除
      • 4.堆的代码实现
      • 5.堆的应用
        • 堆排序
        • Top-k问题
    • (三)二叉树的链式存储既实现
      • 1.二叉树的遍历
      • 2.层序遍历
      • 3.基本操作补充
  • 总结

前言

打怪升级:第8天
在这里插入图片描述

朋友们大家好啊,前段时间我们结束了线性存储结构的学习,经过之前的刷级打怪,今天我们就要开启新的地图、挑战更强大的怪兽 - - 树。


一、树

1.什么是树结构?

树,顾名思义就是像现实生活中的树一样的,不过这棵树和我们平常见到的树不太一样:它是一颗倒立的树。
如下图:
在这里插入图片描述
在这里插入图片描述
树:树是由n(n>=0)个有限节点组成带有层次结构的集合。由图我们可以看出树是一种非线性数据结构,把它叫做树是因为他看起来像是一个倒挂的树。


2.为什么使用树结构?

我们在前面学到的线性存储结构包含:顺序表和链表两种,而线性表中数据的对应关系为:1对1,
也就是说一个数据最多只能和一个数据存在关联,换句话说就是一个人最多只能认识一个其他人。
那么显而易见,这种存储结构是不能满足我们现实生活使用的,而今天要介绍的树则可以将多个数据关联在一起,
就比如:我们的族谱、公司或学校的领导管理层次划分等。


3.树的概念

  1. 有一个特殊节点:根节点,根节点没有前驱节点

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

  3. 需要注意的是:树形结构中子树之间不能存在交集,否则就不是树了(属于图)。
    如下图:
    在这里插入图片描述

  4. 节点的度:一个节点含有的子树的个数叫做节点的度,比如上图中:A的度为3,B的度为2;

  5. 叶子节点或终端节点:度为0的节点称为叶子结点,比如上图中:J, F, K, L, H, I;

  6. 非终端节点或分支节点:度不为0的节点,如上图的:B, C, D, E等;

  7. 双亲节点或父节点:若一个节点含有子节点,那这个节点就称为它子节点的父节点,如上图中:A是B的父节点;

  8. 孩子节点或子节点:若一个节点含有子节点,那这个节点的子节点就是它的孩子节点,如上图中:B是A的子节点;

  9. 兄弟节点:具有相同父节点的节点互称为兄弟节点,如上图中:B, C, D互称为兄弟节点;

  10. 树的度:一个树中最大的节点的度称为树的度,如上图中:树的度为3;

  11. 节点的层次:从根节点开始计为1(或0)的话,第二层就是2(或1),以此类推;
    如图:在这里插入图片描述

  12. 树的高度或深度: 树中节点的最大层次,上图中为:4;

  13. 堂兄弟节点:双亲在同一层次的节点互为堂兄弟节点,如上图中:F和G,F和H;

  14. 节点的祖先:从根节点出发到达该节点所经过的所以节点都是它的祖先节点(父节点也是它的祖先节点),如上图中A是所有节点的祖先;

  15. 节点的子孙:以某节点为根的子树的所有节点都是它的子孙节点,如上图中:所以节点都是A的子孙节点;

  16. 森林:由m(m>0)棵互不相交的树的集合称为森林。


4.树的表示

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

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

在这里插入图片描述


5.树在现实生活中的应用

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


二、二叉树

(一)二叉树概念既结构

1.概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空;
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
    如下图:
    在这里插入图片描述
    从上图可以看出:
  3. 二叉树不存在度大于2的节点;
  4. 二叉树有左右子树之分,次序不能颠倒,因此二叉树为有序树。
    注意:任意二叉树都是由以下几种情况复合而成的:
    在这里插入图片描述

2.现实中的二叉树

在这里插入图片描述


3.特殊的二叉树

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

4.二叉树的性质

  1. 若规定根节点为1,则一棵非空二叉树第i层上最多有 2^(i-1) 个节点;
  2. 若规定根节点为1,则一棵非空二叉树最多有 2^n-1 个节点;
  3. 对任意一棵二叉树,如果度为0的叶子节点有n0个,度为2的节点有n2个,则有:n0 = n2 + 1;
  4. 若规定根节点为1,则一棵有n 个节点的完全二叉树深度为:h = log(n + 1),(n = 2^h -1);
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
    于序号为i的结点有:
    ①若 i > 0 ,则 i 位置的双亲节点序号为: (i - 1) / 2
    ②若 i * 2 + 1 < n ,左孩子下标为: i * 2 + 1,如果 i * 2 + 1 >= n ,则无左孩子
    ③若 i * 2 + 2 < n, 右孩子下标为: i * 2 + 2,如果 i * 2 + 2 >= n ,则无右孩子

5.二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

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

(二)二叉树的顺序存储既实现

1.二叉树的顺序存储

普通二叉树是不适合顺序存储的,那可能会造成大量的空间浪费,而完全二叉树更适合顺序存储。
在现实生活中我们把**堆(一种二叉树)**使用顺序结构来存储。
需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
在这里插入图片描述


2.堆的概念既结构

如果有一个关键码的集合K = { k0,k1 ,k2 ,…,k n-1 },把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足: k i<= k 2i+1且 k i<= k 2i+2( k i>= k 2i+1且 k i>= k 2i+2) i = 0,1,
2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:

  1. 堆的根节点一定不大于或不小于它的子节点;
  2. 堆一定是完全二叉树。
    在这里插入图片描述
    在这里插入图片描述

3.堆的实现

堆的实现可以使用向下调整和向上调整两种方法,不过由于向上调整比向下调整时间复杂度大很多(向上调整:O(nlogn),向下O(n) ),因此在实际使用是我们都使用向下调整,下面我们使用的方法也是向下调整。

向下调整算法

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

int array[] = {27,15,19,18,28,34,65,49,25,37};
在这里插入图片描述

堆的创建

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

int a[] = {1,5,3,8,7,6};
在这里插入图片描述

建堆时间复杂度

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

堆的插入

插入数据到数组的尾上,然后通过向上调整算法,直到满足堆。
在这里插入图片描述

堆的删除

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

4.堆的代码实现


// 堆 -- 数组
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

// 堆的构建
void HeapCreate(Heap* php, HPDataType* a, int n)
{
	assert(php);

	php->_a = a;
	php->_size = php->_capacity = n;

	for (int i = (php->_size - 2) / 2; i >= 0; i--) //  从第一个非叶子节点开始向下调整
	{
		AdjustDown(php->_a, n, i);
	}
}
//堆的销毁
void HeapDestroy(Heap* php)
{
	assert(php);

	php->_a = NULL;
	php->_size = php->_capacity = 0;
}

static void Swap(HPDataType*x, HPDataType*y)
{
	HPDataType z = *x;
	*x = *y;
	*y = z;
}
// 向上调整  --  大堆存储
static void AdjustUp(Heap* php)
{
	assert(php);

	int child = php->_size - 1;

	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (php->_a[child] > php->_a[parent])
		{
			Swap(&php->_a[child], &php->_a[parent]);

			child = parent;
		}
		else
		{
			break;
		}
	}
}
//堆的插入,同时保持堆的形态
void HeapPush(Heap* php, HPDataType data)
{
	assert(php);

	if (php->_size == php->_capacity)
	{
		int newcapacity = php->_capacity == 0 ? 4 : php->_capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("HeapPush::malloc");
			exit(-1);
		}

		php->_a = newnode;
		php->_capacity = newcapacity;
	}

	php->_a[php->_size++] = data;

	AdjustUp(php);
}
// 堆的打印
void HeapPrint(Heap* php)
{
	assert(php);

	for (int i = 0; i < php->_size; i++)
	{
		printf("%d ", php->_a[i]);
	}
	printf("\n");
}
// 向下调整
static void AdjustDown(HPDataType* arr, int n, int parent)
{
	assert(arr);

	int child = parent * 2 + 1; // 左孩子下标
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] < arr[child]) // 右孩子存在,并且值更大
			child++;

		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
// 堆的判空  为空返回 1
bool HeapEmpty(Heap* php)
{
	assert(php);

	return php->_size == 0;
}
//堆的删除,保持堆的形态
void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	Swap(&php->_a[php->_size - 1], &php->_a[0]); // 将最后一个数据放到第一个
	php->_size--;
	// 将交换到最上面的数据进行向下调整
	AdjustDown(php->_a, php->_size, 0);
}
//获取堆中数据个数
int HeapSize(Heap* php)
{
	assert(php);

	return php->_size;
}
//获取堆顶数据
HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->_a[0];
}

5.堆的应用

堆排序

堆排序是利用堆的思想来排序

  1. 建堆:
    升序:建大堆
    降序:建小堆
  2. 利用堆删除的特性来进行排序
    建堆和堆删除过程中都用到了向下调整,因此掌握了向下调整就可以实现堆排序。
    升序排序思想介绍:
    首先建大堆,此时堆顶数据就是最大值,我们在每次删除的过程中会将当前堆顶元素换到最后,后重新调整堆,那么我们每次删除数据时都会将当前堆中的最大值调整到最后,直到堆中只剩下一个数据就结束,此时获得的数据就是按升序排列好的。
    如图:
    在这里插入图片描述

代码如下:


void HeapSort(int* a, int n)
{
	Heap hp;
	HeapCreate(&hp, a, n);
	while(HeapSize(&hp) > 1)
	{
		HeapPop(&hp);
	}
	
	HeapDestory(&hp);
}

Top-k问题

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

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

示例:

void PrintTopK(int* a, int n, int k)
{
  // 1. 建堆--用a中前k个元素建堆
 	 Heap hp;
	 HeapCreate(&hp, a, k);
	 int i=k;
	 while(i<n)
	 {
	 	if(hp._a[0]<a[i])
		{
		 	Swap(&hp._a[0], a[i]);
	 		AdjustDown(hp._a, k, 0);
	 	}
	 	i++;
	 }
	  // 2. 将剩余n-k个元素依次与堆顶元素交换,不满足则替换
	for(i=0;i<k;i++)
	{
		printf("%d ", hp._a[i]);
	}
	printf("\n");
	
	HeapDestroy(&hp);
}

void TestTopk()
{
 int n = 100000;
 int* a = (int*)malloc(sizeof(int)*n);
 srand((unsigned int)time(NULL));
 for (size_t i = 0; i < n; ++i)
 {
 a[i] = rand() % 1000000;
 }
 a[5] = 1000000 + 1;   //  额外创建k个数据,便于判断结果的有效性
 a[1231] = 1000000 + 2;
 a[531] = 1000000 + 3;
 a[5121] = 1000000 + 4;
 a[115] = 1000000 + 5;
 a[2335] = 1000000 + 6;
 a[9999] = 1000000 + 7;
 a[76] = 1000000 + 8;
 a[423] = 1000000 + 9;
 a[3144] = 1000000 + 10;
 PrintTopK(a, n, 10);
}

(三)二叉树的链式存储既实现

在学习二叉树的实现之前先来分析一下二叉树的结构:

  1. 空树
  2. 非空树:根节点,根节点的左子树,根节点的右子树
    在这里插入图片描述
    从概念中可以看出二叉树是递归定义的,因此在后序基本操作中基本都是按照概念实现的。

1.二叉树的遍历

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

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

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

根的前序遍历图解:
在这里插入图片描述
在这里插入图片描述

代码实现:

void BinaryTreePrevOrder(BTNode* root)
{

	if (root)
	{
		printf("%c->", root->_data);
		BinaryTreePrevOrder(root->_left);
		BinaryTreePrevOrder(root->_right);
	}
	else
	{
		printf("NULL->");
	}
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root)
	{
		printf("%c->", root->_data);
		BinaryTreeInOrder(root->_left);
		BinaryTreeInOrder(root->_right);
	}
	else
	{
		printf("NULL->");
	}
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root)
	{
		printf("%c->", root->_data);
		BinaryTreePostOrder(root->_left);
		BinaryTreePostOrder(root->_right);
	}
	else
	{
		printf("NULL->");
	}
}

2.层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层
上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历
在这里插入图片描述



typedef BTNode* QDataType;
// 链式结构:表示队列 
typedef struct QListNode
{
	QDataType _data;
	struct QListNode* _next;
}QNode;

// 队列的结构 
typedef struct Queue
{
	int len;
	QNode* _front;
	QNode* _rear;
}Queue;


// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	q->len = 0;
	q->_front = q->_rear = NULL;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (tmp == NULL)
		exit(-1);

	tmp->_data = data;
	tmp->_next = NULL;
	if (q->_front == NULL) // 空队列
	{
		q->_front = q->_rear = tmp;
	}
	else
	{
		q->_rear->_next = tmp;
		q->_rear = q->_rear->_next;
	}
	q->len++;
}
// 检测队列是否为空,为空返回1
bool QueueEmpty(Queue* q)
{
	assert(q);

	return q->len == 0;
}
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);

	if (QueueEmpty(q))
		exit(-1);

	QNode* next = q->_front->_next;
	free(q->_front);
	q->_front = next;
	if (q->_front == NULL)
		q->_rear = NULL;
	q->len--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);

	if (QueueEmpty(q))
		exit(-1);

	return q->_front->_data;
}
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);

	QNode* cur = q->_front;
	while (cur)
	{
		QNode* next = cur->_next;
		free(cur);
		cur = next;
	}
}



// 层序遍历  --  借助队列,出一个根节点,进两个孩子
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, root);
	BTNode* cur = NULL;

	while (cur || !QueueEmpty(&q))
	{
		cur = QueueFront(&q);  // 出一个  ,进两个
		QueuePop(&q);
		printf("%c->", cur->_data);
		if (cur->_left)
			QueuePush(&q, cur->_left);
		if (cur->_right)
			QueuePush(&q, cur->_right);

		cur = NULL;
	}

	QueueDestroy(&q);
}

3.基本操作补充

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	assert(a);

	if (a[*pi] == '#')
	{
		return NULL;
	}
	else
	{
		BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
		newnode->_data = a[*pi];
		(*pi)++;
		newnode->_left = BinaryTreeCreate(a, n, pi);
		(*pi)++;
		newnode->_right = BinaryTreeCreate(a, n, pi);

		return newnode;
	}
}
// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	if (root)
	{
		BinaryTreeDestory(root->_left);
		BinaryTreeDestory(root->_right);
		free(root);
	}
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root)
		return 1 + BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
	else
		return 0;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root)
	{
		if (root->_left == NULL && root->_right == NULL)
			return 1;
		else
			return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
	}
	else
		return 0;
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root)
	{
		if (k == 1)
			return 1;
		else
			return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
	}
	else
		return 0;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root)
	{
		if (root->_data == x)
			return root;
		else
		{
			BTNode* target = BinaryTreeFind(root->_left, x);
			if (target)
				return target;
			else
				return BinaryTreeFind(root->_right, x);
		}
	}
	else
		return NULL;
}

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	if (root)
	{
		if (root->_left == NULL && root->_right != NULL)  //  此情况则不是完全二叉树
			return false;
		return BinaryTreeComplete(root->_left)
			&& BinaryTreeComplete(root->_right);
	}
	else
		return true;
}

总结

文章内容汇总:
树的介绍、
二叉树的顺序存储–堆,
以及链式存储,
堆的应用–堆排序、top-k问题,
以及二叉树链式存储相关的基本操作,
如果有什么疑问或者建议都可以在评论区留言,感谢大家对在这里插入图片描述的支持。

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

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

相关文章

线程的深度剖析

线程和进程的区别和联系&#xff1a; 1.进程可以处理多并发编程的问题&#xff0c;但是进程太重&#xff0c;主要指它的资源分配和资源回收上&#xff0c;进程消耗资源多&#xff0c;速度慢&#xff0c;其创建、销毁、调度一个进程开销比较大。 2.线程的出现有效地解决了这一…

面向对象的个人理解(使用JAVA代码描述)

前言 功能分类 类&#xff08;class&#xff09;的第一个功能是隔离&#xff0c;起到边界的作用&#xff0c;使得不同功能的代码互不干扰。 干扰的起源 在非面向对象的语言中&#xff0c;我们主要定义结构和函数来实现功能。下边用C语言来举个例子。 某程序员写了宠物模拟…

Ajax学习:nodejs安装+express框架介绍

ajsx应用中&#xff0c;需要安装nodejs环境 基于Chrome V8引擎&#xff08;和浏览器上的谷歌的解析引擎一样&#xff09;JavaScript运行环境 (31条消息) Node.js_安装_哇嘎123的博客-CSDN博客 查看安装是否完成 express框架介绍--为了创建一个web服务器 (31条消息) Express…

Spark系列之Spark体系架构

title: Spark系列 第四章 Spark体系架构 4.1 Spark核心功能 Alluxio 原来叫 tachyon 分布式内存文件系统Spark Core提供Spark最基础的最核心的功能&#xff0c;主要包括&#xff1a; 1、SparkContext 通常而言&#xff0c;DriverApplication的执行与输出都是通过SparkC…

STM32G491RCT6,STM32H743BIT6规格书 32bit IC MCU

STM32G4系列将强大的ArmCortex-M4加上FPU和DSP能力与丰富和先进的模拟外设相结合。它引入了两种新的数学加速器(Cordic和Filtering)&#xff0c; CAN-FD (Flexible Datarate)&#xff0c;USB Type-C接口的功率传输&#xff0c;包括物理层(PHY)和先进的安全功能。&#xff08;图…

Day38——Dp专题

DP专题 动态规划五部曲&#xff1a; 确定dp数组以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历顺序 举例推导dp数组 1.斐波那契数 题目链接&#xff1a;509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;做dp类题目&#xff0c;根据…

Meta 内部都在用的 FX 工具大起底:利用 Graph Transformation 优化 PyTorch 模型

PyTorch 中的 graph mode 在性能方面表示更为出色&#xff0c;本文介绍 Torch.FX 这个强大工具&#xff0c;可以捕捉和优化 PyTorch 程序 graph。 一、简介 PyTorch 支持两种执行模式&#xff1a;eager mode 和 graph mode。 eager mode 中&#xff0c;模型中的运算符在读取时…

JUC并发编程

目录JUC概述什么是JUC进程与线程的概念线程的状态线程的方法并发与并行管程(Monitor)用户线程和守护线程Lock接口Synchronized 关键字Synchronized 作用范围Synchronized 实现卖票例子Lock接口什么是LockLock与synchronized的区别可重入锁&#xff08;ReentrantLock&#xff09…

剑桥大学哪些专业需要IB物理成绩?

如果IB学生申请目标是剑桥大学&#xff0c;那么申请哪些专业需要学习IBDP物理课程&#xff1f;要不要学习IBDP物理课程呢&#xff1f;剑桥大学&#xff1a;IB成绩要求高&#xff0c;招录竞争激烈 在申请英国大学之前&#xff0c;理性的申请者都会提前查看一下目标大学的学术成绩…

Python破解WIFI源代码,测试成功(暴力跑字典)

目录 1&#xff0c;先安装Python环境(这个不用多说了吧) 2&#xff0c;安装PyWifi 3,自己手工整理高频弱口令&#xff0c;不建议程序生成的字典&#xff0c;生成的字典成功率实在太低。 4&#xff0c;自己生成字典的算法&#xff1a; 5&#xff0c;破解WIF代码第一种&#…

NetCore多租户开源项目,快速后台开发企业框架,赚钱就靠她了

今天给大家推荐一个开源项目&#xff0c;基于.NetCore开发的、多租户的企业开发框架。 文章目录项目简介技术架构项目结构系统功能代码生成器部分功能截图项目地址项目简介 这是一个基于.Net和Layui、基于多数据库的多租户&#xff0c;敏捷开发优选框架。系统自带权限功能、数…

面向移动支付过程中网络安全的研究与分析

基础防护系统设计 4.1.1入侵监测系统 入侵监测系统&#xff08;IDS&#xff09;的部署主要是防治外界非法人员对银行网络进行攻击&#xff0c;及时发现非法人员的入侵行为&#xff0c;以确保能够立刻采取网络阻止措施。在银行网络中的关键部位部署入侵检测系统&#xff0c;可…

springboot中controller层代码优雅写法

在基于spring框架的项目开发中&#xff0c;必然会遇到controller层&#xff0c;它可以很方便的对外提供数据接口服务&#xff0c;也是非常关键的出口&#xff0c;所以非常有必要进行规范统一&#xff0c;使其既简洁又优雅。 controller层的职责为负责接收和响应请求&#xff0c…

快手资讯|快手推出多档世界杯相关节目

1、快手直播间上线“相亲角”功能 近日&#xff0c;快手直播间上线了“相亲角”功能&#xff0c;可为主播打造相亲功能。 此外&#xff0c;快手还在“热门活动”中推出了“婚庆”频道&#xff0c;主要展示“婚礼现场”、“婚纱照”等短视频内容。企查查App显示&#xff0c;北京…

博主常用的 idea 插件,建议收藏!!!

一、Key Promoter X **快捷键提示工具&#xff1a;**每次操作&#xff0c;如果有快捷键&#xff0c;会提示用了什么快捷键。 二、Maven Helper maven 助手&#xff1a;展示 jar 包依赖关系 三、Lombok 只需加上注解 什么get set 什么toString 等等方法都不需要写 四、MyBati…

Postman下载安装注册登录简介登录后界面简介

一、为什么选择Postman? 如今&#xff0c;Postman的开发者已超过1000万(来自官网)&#xff0c;选择使用Postman的原因如下:1、简单易用 - 要使用Postman&#xff0c;你只需登录自己的账户&#xff0c;只要在电脑上安装了Postman应用程序&#xff0c;就可以方便地随时随地访问…

小程序上新(2022.11.15~11.28)

20221115 小程序基础库 2.27.3 更新 更新 框架 设备 VoIP 能力授权更新 框架 支持 worker 代码打包到小程序&小游戏分包 详情更新 组件 scroll-view 接近全屏尺寸时默认开启点击回到顶部更新 API createVKSession 在不需要用到摄像头的时候不再发起摄像头授权 详情修复 框…

elasticsearch7.6.2和logstash安装和初步

一、linux安装 参考以下链接&#xff1a; Linux&#xff08;centos7&#xff09;如何部署ElasticSearch7.6.2单节点跟集群&#xff08;es部署指南&#xff09; 二、window安装 参考下文更加详细&#xff1a;windows ElasticSearch 7.6.0集群搭建 2.1 下载elasticsearch7.6.…

开源多商户商城源码代码分析

如今&#xff0c;互联网几乎普及到了所有地区&#xff0c;同时也推动了传统行业发展。目前&#xff0c;越来越多的线下商家开始搭建多商户商城系统&#xff0c;打造属于自己的淘宝、天猫电商服务平台。什么是多商户商城系统呢&#xff1f;想必大部分人并不是很了解&#xff0c;…

多线程基本概念

多线程多线程基本概念线程控制创建终止等待分离线程安全基本概念实现互斥互斥锁死锁同步线程应用生产者与消费者模型线程池单例模式多线程基本概念 线程是进程中一个执行流程&#xff0c;是 CPU 进行执行调度的基本单元&#xff1b; 进程是系统进行资源分配的基本单元。 Linu…