【数据结构】树和二叉树——堆

news2024/10/6 2:27:33

目录

🍉一.树的概念及结构🍉

1.树的概念

2.树的相关术语

3.树的表示

4.树在实际中的应用

🍊二.二叉树的概念和结构🍊

1.二叉树的概念

 2.特殊的二叉树

2.1.满二叉树

2..2.完全二叉树

3.二叉树的性质

4.二叉树的存储结构

         4.1.顺序存储

4.2.链式存储

🍎三.堆的顺序结构和实现🍎

1.二叉树的顺序结构

 2.堆的概念及结构

3.堆的实现

3.1向上调整算法

3.2向下调整算法

3.3堆的构建

3.4堆的插入

3.5堆的删除

3.6堆的初始化

3.7堆的销毁

🍏四.堆的应用🍏

1.堆排序

2.TOP-K问题


🍉一.树的概念及结构🍉

🍏1.树的概念🍏

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

       1.有一个特殊的节点,称为根节点,根节点没有前驱节点

       2.除根节点外,其余节点被分为M(M>0)个互不相交的集合T1,T2,T3..Tm,其中每一个集合Ti(1<=i<=m)又是一颗结构与树类似的子树。每颗子树的根节点有且只有一个前驱,可以有0个或多个后继

       3.因此,树是递归定义的

        

9d7fd459670b4733a19f08bb8b453dff.png80bf2ee6010a45fe99e4f6a8cf3ef288.png

 注意:树形结构中,子树之间不能有交集,否则就不是树形结构

a8fdd13953aa45dda22b50b28bcd7a6b.png

🍊2.树的相关术语🍊

7ad9b4ea1c6549e290edf5e0f80bdbcb.png

节点的度:一个节点含有的子树的个数称为该节点的度;如上图:A的度为6,D的度为1,E的度为2,B的度为0。

叶节点或终端节点:度为0的节点称为叶节点;如上图,B,C,H,I,K,L,M,N,P,Q为叶节点。

非终端节点或分支节点:度不为0的节点;如上图:A,D,E,F,G,J为分支节点。

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图,A是B的父节点。

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;如上图,B,C,D,E,F,G是A的孩子节点。

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

树的度:一棵树中,最大的节点的度称为树的度;如上图,节点的度最大为6,故树的度为6。

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
树的高度或深度:树中节点的最大层次;如上图,树的高度为4。

堂兄弟节点:双亲在同一层的节点互为堂兄弟节点;如上图,H,I互为堂兄弟节点。

节点的祖先:从根到该节点所经分支上的所有节点;如上图,P的祖先为J,E,A。

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

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

🍋3.树的表示🍋

        树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了 既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的 孩子兄弟表示法 
        孩子兄弟法即只让父节点只指向自己的一个孩子,然后被指向的孩子指向自己的兄弟节点,这样找到被指向的孩子就可以找到所有的孩子
typedef int DataType;
struct Node
{
 struct Node* _firstChild1; // 第一个孩子结点
 struct Node* _pNextBrother; // 指向其下一个兄弟结点
 DataType _data; // 结点中的数据域
};

79947dd3ecbf4f0185d2e169b9b98fae.png

🍌4.树在实际中的应用🍌

         表示文件系统的目录树结构

6befd0ebbeec41e49b2d6dc49f911b4a.png

🍍二.二叉树的概念和结构🍍

🥭1.二叉树的概念🥭

        一颗二叉树是节点的一个有限集合,该集合:1.或者为空,2有一个根节点加上两颗分别称为左子树和右子树的二叉树组成 

3281e201b9314183a29d84f169e690e5.png

 从上图可以看出:

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

对于任意的二叉树都是由以下几种情况复合而成:bc88c14445754578b1abcd289ffabc97.png

 现实中的二叉树:

141bfcb219b14a0682b6cbbfbc1eeacf.png984777bff61d4b25bcb9ca8f0daeea94.png

 🍎2.特殊的二叉树🍎

🍏2.1.满二叉树🍏

        一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2的k次方-1 ,则它就是满二叉树。 

🍐2.2.完全二叉树🍐

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

1412405b57b34d7ab44dbc2a2ce63204.png

         可以看到,满二叉树每一层都是满的,即除了叶子结点外,其他所有的节点有且只有两个孩子,完全二叉树则可以看做是满二叉树从最后一层从右往左删掉了若干个节点,由于是从右往左,故完全二叉树不可能存在一个节点只有右孩子而没有左孩子的情况

🍑3.二叉树的性质🍑

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

证明:最多的情况下即这一层类似于满二叉树的节点数,满二叉数从根节点开始,下一层的节点数是上一层的2倍,第一层为1个即2的0次方,第2层为2个即2的1次方,第3层为4个即2的2次方,以此类推,第i层为2的i-1次方

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

证明:深度h即由h层,由性质1得每一层的最大节点数为2的i-1次方,每一层的和:1+2+4+8+16+...+2的h-1次方,可以看出这是一个等比数列求和,由等比数列求和公式求得最大节点数为2的h次方-1

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

证明:从节点个数和边个数关系证明。除了根节点之外,其余n-1每个节点都有一个父节点,即每个节点上面都有一条边,有n-1个节点则总共有n-1条边,又度为1的节点下面有一条边,度为2的节点下面有两条边,度为0的节点即叶节点下面没有边

        设度为2的节点数为n2,度为1的节点为n1,度为0的节点为n0,总节点数为n,则边数为n-1

        有:n0+n1+n2=n(节点个数关系)

               n1*1+n2*2=n-1(边个数关系)

联立上式即可得n0=n2+1

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

证明:由性质2可得深度为h的满二叉树的节点数n为2的h次方-1,即2的h次方-1=n,解得h=log2(n+1)

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

         双亲节点:若i>0,i位置节点的双亲节点:(i-1)/2;i=0,即为根节点,无双亲节点;

         左孩子:若2i+1<n,则其左孩子存在,左孩子编号为2i+1,否则无左孩子

         右孩子:若2i+2<n.则其右孩子存在,右孩子编号为2i+2,否则无右孩子

🍒4.二叉树的存储结构🍒

        二叉树一般有两种存储方式,一种为顺序结构,一种为链式结构

        🍆4.1.顺序存储🍆

         顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实使用中只有堆才会使用数组来存储,堆的结构和实现会在文章后面讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树

1e7c9e3b2caa4bf3b1f2011ece859615.png

从上图可以看出,二叉树的顺序存储即按照从上到下从左到右的顺序依次将节点的值放到数组中即可

🍇4.2.链式存储🍇

        二叉树的链式存储结构是指, 用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面的高阶数据结构如红黑树等会用到三叉链。

396cb2dcfb3e4fed8f5c5c922920a411.png

 71392244a1994197b8cf95583778cd4e.png

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
 struct BinTreeNode* _pLeft; // 指向当前节点左孩子
 struct BinTreeNode* _pRight; // 指向当前节点右孩子
 BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
 struct BinTreeNode* _pParent; // 指向当前节点的双亲
 struct BinTreeNode* _pLeft; // 指向当前节点左孩子
 struct BinTreeNode* _pRight; // 指向当前节点右孩子
 BTDataType _data; // 当前节点值域
};

可以看到,链式存储分为两种方式分别为二叉链和三叉链,二叉链即节点有两个指针域分别指向左孩子和右孩子,而三叉链在二叉链的基础上多了一个指针域指向父节点

🍈三.堆的顺序结构和实现🍈

🍉1.二叉树的顺序结构🍉

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。 现实中我们通常把堆 ( 一种二叉树 ) 使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
7a5e7631854e4d68a14a751c42bdef13.png

🍊 2.堆的概念及结构🍊

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

        堆的性质:    

        1.堆中某个节点的值总是不大于或不大于其父节点的值

        2.堆总是一颗完全二叉树

ef196b786abf40c683847e64dd462d20.png 通过上图我们看到,堆在数组的存储中时按照从上到下从左至右的顺序存储在数组中的,因此我们可以总结出父节点和孩子节点下标的关系 :  

1.通过父亲节点找孩子节点:

        leftchild=parent*2+1

        rightchild=parent*2+2
 2.通过孩子节点找父节点

        parent=(child-1)/2(左右孩子都符合)

   

🍋3.堆的实现🍋

        堆的实现主要设计向上调整算法、向下调整算法、堆的构建、堆的插入、堆的删除等接口函数

下面依次来看这些接口函数,默认建立大堆

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

3.1向上调整算法

        以某一个孩子节点开始,让它与自己的父节点比较,当孩子节点大于父亲节点时,显然这不符合大堆的性质,于是需要将两个节点的值交换,原来的父节点变为孩子节点,之前的孩子节点往上成为父节点,新的父节点作为孩子节点依然需要与上一个父节点比较,直到所有节点都符合大堆的性质,原来的孩子节点再调整的过程中不断往上走,故称为向上调整,如图所示

434d292ac6124f3280100b2b205524ee.png

 代码设计和过程:

        函数的参数为一个数组和孩子节点下标,首先通过孩子节点下标算出父节点的下标,接着进行两者的比较,如果孩子节点大于父节点则两者交换,并更新孩子节点,再算出新的父节点,以此循环直到符合大堆的性质或者孩子节点已经为堆顶则调整结束

void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;//通过孩子节点下标找到父节点下标
	while (child>0)//调整结束的条件
	{
		if (a[child] > a[parent])//孩子节点大于父节点则调整
		{
			Swap(&a[child], &a[parent]);//交换两个节点的值
			child = parent;//孩子节点更新
			parent = (child - 1) / 2;//再求出新的父节点
		}
		else
		{
			break;//如果不需要调整则直接退出
		}
	}
}

3.2向下调整算法

        以某一个父亲节点开始,让它与自己的孩子节点比较,当父节点小于最大的孩子节点时,(为什么要与最大的孩子节点交换,如果将较小的孩子换上去成为父节点,此父节点依然小于最大孩子节点)显然这不符合大堆的性质,于是需要将两个节点的值交换,最大孩子节点变为父节点,之前的父节点往下成为孩子节点,新的孩子节点作为父节点依然需要与下一个孩子节点比较,直到所有节点都符合大堆的性质,原来的父亲节点再调整的过程中不断往下走,故称为向下调整,如图所示

        向下调整算法有一个前提:左右子树必须是一个堆,才能调整

d6f04df9f2ac4c3f9c78e1b5a2784f7f.png

 代码设计和过程:  

        函数的参数为一个数组以及数组的大小和父节点下标,首先通过父节点下标算出最大孩子节点的下标,接着进行两者的比较,如果父节点小于最大孩子节点则两者交换,并更新父节点,再算出新的最大孩子节点,以此循环直到符合大堆的性质或者父节点已经走到最后则调整结束

void AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;//通过父节点算出最大孩子节点,假设最大孩子为左节点
	while (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;//不需要调整则直接退出
		}
	}
}

3.3堆的构建

        给定一个数组,我们可以看做是完全二叉树,我们可以通过向上调整和向下调整两种方式建堆,并对两种方法进行分析和比较

     向上调整建堆:

        从第二层开始(堆顶可以视为一个大堆),对每一个节点应用向上调整算法,直到最后一个节点应用完向上调整算法

        时间复杂度:O(n*logn),证明如图所示

3df5038cbf8b451ba8e835a776aa1c96.png

      

向下调整建堆:

       从倒数第二层开始(每个叶节点可以视为一个大堆),对倒数第一个非叶子节点应用向下调整算法,直到最后堆顶应用完向下调整算法

        时间复杂度:O(n),证明如下图所示

3085532c86c24309a9578adac980ccd5.png

通过上述分析,向下调整建堆的时间复杂度更优,因此在后续的建堆过程中采用向下调整建堆

3.4堆的插入

        堆的插入是首先将节点插入到数组即堆的尾部,再对该节点应用向上调整即可

        过程如图所示

327518e3749e4b61ab4b46c52b223d11.png

void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)//当前容量等于最大容量则进行扩容
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//第一次相当于开辟空间,后续扩容至2倍
		HPDataType* tmp = (HPDataType *)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;//插入到数组尾部
	hp->size++;//当前容量加1
	AdjustUp(hp->a, hp->size-1);//对新节点采用向上调增算法
}

3.5堆的删除

        堆的删除过程:首先将堆顶与最后一个节点交换,然后当前容量-1,在对堆顶应用向下调整算法

        删除过程如图所示:

0f17607b42e34c8db149a8361b5d5c67.png

 3.6堆的初始化

        堆的初始化即让指向数组的指针置为空,当前容量和最大容量置为0即可

void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

3.7堆的销毁

        堆的销毁即释放指针指向的空间,并让指针置为空,当前容量和最大容量置为0即可

void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

全部代码:

#include"Heap.h"
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	HPDataType*tmp = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	hp->a = tmp;
	hp->size = hp->capacity = n;
	memcpy(hp->a, a, sizeof(HPDataType) * n);
	//已有一个数组,向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(hp->a, n, i);
	}

}
void HeaPPrint(Heap* hp)
{
	assert(hp);
	for (int i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;//通过孩子节点下标找到父节点下标
	while (child>0)//调整结束的条件
	{
		if (a[child] > a[parent])//孩子节点大于父节点则调整
		{
			Swap(&a[child], &a[parent]);//交换两个节点的值
			child = parent;//孩子节点更新
			parent = (child - 1) / 2;//再求出新的父节点
		}
		else
		{
			break;//如果不需要调整则直接退出
		}
	}
}
void AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;//通过父节点算出最大孩子节点,假设最大孩子为左节点
	while (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;//不需要调整则直接退出
		}
	}
}
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)//当前容量等于最大容量则进行扩容
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//第一次相当于开辟空间,后续扩容至2倍
		HPDataType* tmp = (HPDataType *)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;//插入到数组尾部
	hp->size++;//当前容量加1
	AdjustUp(hp->a, hp->size-1);//对新节点采用向上调增算法
}
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->a, hp->size, 0);
}
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}
void PrintTopK(int* a, int n, int k)
{
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}
	for (int i = k+1; i < n; i++)
	{
		if (a[i] > a[0])
		{
			Swap(&a[i], &a[0]);
			AdjustDown(a, k, 0);
		}
	}
}
void TestTopk()
{
	int array[] = { 27,15,19,18,28,34,65,49,25,37 };
	int k = 5;
	int n = sizeof(array) / sizeof(int);
	PrintTopK(array, n, k);
	for (int i = 0; i < k; i++)
		printf("%d ", array[i]);

}

🍏四.堆的应用🍏

🥭1.堆排序🥭

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

    1.建堆

                升序:建大堆

                降序:  建小堆

    2.利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序

        排序过程:建立大堆,大堆能够相对较大的元素在堆顶,首先让堆顶元素和最后一个元素交换,然后数组大小减1,再对堆顶元素应用向下调整(此时第二大的数就在堆顶了),第一轮就可以将最大的树放到数组最后面,依次进行,第二轮则可以将第二大的数放到数组的倒数第二个位置,直到数组的大小变为1即排序完成

111ee56b2c2745aa86bb0c115d2fe563.png

 代码:

void HeapSort(int* a, int n)
{
	//时间复杂度O(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--;
	}
}

🍓2.TOP-K问题🍓

        topk问题即在n个数中最大或者最小的K个数,通常n数量很大 

        比如专业前几名,王者荣耀国服排名等

         对于这种问题最容易想到的就是排序,但是当数据量非常大的时候排序就不可取了,最佳的方式就是用堆来解决,以下由两种思路,其中第一个有一定的局限性,故采用第二种思路最佳
 

       思路1:建一个大小为n的堆,pop K次,依次取堆顶,这K次取到的元素便是最大或者最小的K个数

        局限性:当N非常大的时候,比如100亿个整数,占用的空间就高达40G,这是加载不进内存的,只能放到磁盘

        时间复杂度分析:O(n)

        由于向下调整建堆算法为O(n),故建大小为n的堆的时间为n,然后pop K次,每次最快情况下需要移动堆的高度次,堆的高度近似为logn,故Pop过程时间为k*logn,总时间为n+k*logn

        思路2:建立一个大小为K的堆,找最大的K个数则建小堆,反之则建大堆,让后面的N-K个数与堆顶元素,比堆顶的元素大则将堆顶的元素替换,再对堆顶进行向下调整,这样后面的数全部与堆顶元素比较完之后,最大的K个数便在堆中

        时间复杂度分析:O(N)

        建大小为K的堆时间为K,然后后面N-K个元素依次与堆顶,最坏的情况便是后面N-K个数升序排序,即每个数都会与堆顶元素替换并进行向下调整,比较过程时间为(N-K)*logK,总时间为K+(N-K)*logK

思路2代码:

void PrintTopK(int* a, int n, int k)
{
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, k, i);//建立大小为k的堆
	}
	for (int i = k+1; i < n; i++)
	{
		if (a[i] > a[0])//后面的元素与堆顶比较,大于对顶元素则替换
		{
			Swap(&a[i], &a[0]);
			AdjustDown(a, k, 0);//替换之后再对堆顶元素进行向下调整
		}
	}
}

 好啦,关于堆的学习就先到这里,如果对您有所帮助,欢迎一键三连~

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

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

相关文章

限流算法(计数器、滑动时间窗口、漏斗、令牌)原理以及代码实现

文章目录前言1、计数器&#xff08;固定时间窗口&#xff09;算法原理代码实现存在的问题2、滑动时间窗口算法原理代码实现存在的问题3、漏桶算法原理代码实现存在的问题4、令牌桶算法原理代码实现最后本文会对这4个限流算法进行详细说明&#xff0c;并输出实现限流算法的代码示…

一文了解Java ArrayList (源码逐行解析)

介绍 ArrayList 是最常用的 List 实现类&#xff0c;内部是通过数组实现的&#xff0c;它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔&#xff0c;当数组大小不满足时需要增加存储能力&#xff0c;就要将已经有数组的数据复制到新的存储空间中。当从 Arr…

c++积累5-lock_guard使用

1、std:mutex 在了解lock_guard之前&#xff0c;需要先学习下std:mutex,因为lock_guard内部就是使用的std:mutex std:mutex&#xff1a;是一个用于保护共享数据不会同时被多个线程访问的类&#xff0c;它叫做互斥量。我们可以把它看作一把锁&#xff0c;基本使用如下&#xff…

【致敬未来的攻城狮计划】— 连续打卡第四天:e2 studio 使用教程

系列文章目录 1.连续打卡第一天&#xff1a;提前对CPK_RA2E1是瑞萨RA系列开发板的初体验&#xff0c;了解一下 2.开发环境的选择和调试&#xff08;从零开始&#xff0c;加油&#xff09; 3.欲速则不达&#xff0c;今天是对RA2E1 基础知识的补充学习。 文章目录 系列文章目录 文…

Flutter成不了“顶流明星”的7大理由

Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;近年来备受关注。尽管Flutter在某些方面表现出色&#xff0c;但仍然有一些人对它的发展前景表示怀疑。近期一些文章针对Flutter的发展提出了不少质疑和批评&#xff0c;称其难以成为移动应用开发的“顶流明星”&…

【计算机网络—— 安装packet tracer的教程,管理MAC地址和配置路由器的实验步骤以及心得体会】

文章目录安装packet tracer的教程管理MAC地址实验目的 &#xff1a;管理MAC地址实验内容及过程记录&#xff08;一&#xff09;运行Cisco Packet Tracer软件&#xff08;二&#xff09;选择PC终端&#xff08;三&#xff09;运行终端&#xff08;四&#xff09;按表内容对交换机…

Spring Cloud Nacos使用总结

目录 安装Nacos服务器 服务发现与消费 服务发现与消费-添加依赖 服务发现-配置文件 服务发现-注解 服务发现-Controller 服务消费-配置文件 服务消费-注解与Ribbon消费代码 服务消费-运行 配置管理 配置管理-添加依赖 配置管理-配置文件 配置管理-注解 配置管理-…

Raft一致性算法(精简和扩展)

raft一致性算法 文章目录raft一致性算法一、raft简介1.1 raft涉及到的名词1.2 Rpc请求1.3 复制状态机二、raft⼀致性算法2.0 摘要2.0.1 所有服务器需遵守的规则2.0.2 跟随者2.0.3 候选⼈2.0.4 领导人2.0.5 状态2.0.6 特性2.1 raft基础2.2 leader选举2.2.1 集群启动时选举2.2.2 …

微电网两阶段鲁棒优化经济调度方法(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

清华大学ChatGLM-6B部署运行

一、模型介绍 开源项目&#xff1a; GitHub - THUDM/ChatGLM-6B: ChatGLM-6B&#xff1a;开源双语对话语言模型 | An Open Bilingual Dialogue Language Model ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&…

PLC高精度定时器(T法测速功能块)

S7-200 SMART PLC时间间隔指令BGN_ITIME,和CAL_ITIME采用的是系统自带的1ms高精度定时器,PLC里只能调用一次。T法测速和M法测速应用时,都需要高精度时序定时器的支持(当然你也可以采用定时中断的方式获取高精度时序),时间间隔指令请参看下面的博客文章: SMART PLC时间间…

keil5使用c++编写stm32控制程序

keil5使用c编写stm32控制程序一、前言二、配置图解三、std::cout串口重定向四、串口中断服务函数五、结尾废话一、前言 想着搞个新奇的玩意玩一玩来着&#xff0c;想用c编写代码来控制stm32&#xff0c;结果在keil5中&#xff0c;把踩给我踩闷了&#xff0c;这里简单记录一下。…

FlinkSQL的Watermark机制和Checkpoint机制

Watermark机制 Watermark机制&#xff0c;就是水印机制&#xff0c;也叫做水位线机制。就是专门用来解决流式环境下数据迟到问题的。 MonotonousWatermark&#xff08;单调递增水印&#xff09; package day05;import lombok.AllArgsConstructor; import lombok.Data; impor…

【谷粒商城之JSR303数据校验和集中异常处理】

本笔记内容为尚硅谷谷粒商城JSR303数据校验和集中异常处理部分 目录 一、简介 二、SR303数据校验使用步骤 1、引入依赖 2、给参数对象添加校验注解 常见的注解 3、接口参数前增加Valid 开启校验 三、异常的统一处理 四、分组解决校验 1、创建Groups 2、添加分组 …

MySQL数据库之表的增删改查(进阶)

目录1. 数据库约束1.1 约束类型1.2 NULL约束1.3 UNIQUE&#xff1a;唯一约束1.4 DEFAULT&#xff1a;默认值约束1.5 PRIMARY KEY&#xff1a;主键约束1.6 FOREIGN KEY&#xff1a;外键约束1.7 CHECK约束2 表之间的关系2.1 一对一2.2 一对多2.3 多对多3 新增4 查询4.1 聚合查询4…

Redis一致性问题

&#xff08;1&#xff09;何为一致性&#xff1f; 1、定义&#xff1a; 指系统中各节点数据保持一致。 分布式系统中&#xff0c;可以理解为多个节点中的数据是一致的。 2、分类&#xff1a; 强一致性&#xff1a;写进去的数据是什么&#xff0c;读出来的数据就是什么。弱一…

DeepSpeed-Chat:最强ChatGPT训练框架,一键完成RLHF训练!

https://github.com/microsoft/DeepSpeedExamples/tree/master/applications/DeepSpeed-Chat 一个快速、负担得起、可扩展和开放的系统框架&#xff0c;用于实现端到端强化学习人类反馈 (RLHF) 培训体验&#xff0c;以生成各种规模的高质量 ChatGPT 样式模型。 目录 &#x…

计算机体系结构-体系结构基础与流水线原理

计算机体系结构&#xff1a;体系结构基础与流水线原理 ​ 计算机体系结构&#xff1a;量化设计与分析一书以RISC-V为例介绍计算机体系结构。本文为第一部分&#xff0c;介绍体系结构的基本知识和流水线原理。笔记内容为原书的第一章&#xff0c;附录A、B、C。 第一章 量化设计…

练习Tomcat

文章目录1. 简述静态网页和动态网页的区别。2. 简述 Webl.0 和 Web2.0 的区别。3. 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用。1. 简述静态网页和动态网页的区别。 静态网页&#xff1a; &#xff08;1&#xff09;静态网页不能简单地理解成静止不…

SCADE Display(OpenGL)软件设计文档生成工具的设计考虑

SCADE Display&#xff08;OpenGL&#xff09;软件设计文档生成工具的设计考虑 2018年6月 1 引言 本文档描述在SCADE Display软件设计文档生成工具&#xff08;以下简称为SDYSDDGEN&#xff09;的设计过程中考虑到的一些问题及其解决方案。 2 目标 SDYSDDGEN的目标设定为&…