【数据结构】树的概念与堆的实现

news2024/9/20 1:22:35

树的概念与堆的实现

  • 1、什么是树
    • 1.1 树的概念
    • 1.2 树的相关概念
    • 1.3 树的表示
  • 2、二叉树概念及结构
    • 2.1 概念
    • 2.2 特殊的二叉树
    • 2.3 二叉树的性质
    • 2.4 二叉树的存储结构
  • 3、二叉树的顺序结构及实现
    • 3.1 二叉树的顺序结构
    • 3.2 堆的概念及结构
    • 3.3 堆的实现
      • 3.3.1 创建一个堆
      • 3.3.2 初始化堆
      • 3.3.3 堆的插入函数以及向上调整函数
      • 3.3.4 堆的打印
      • 3.3.5 删除堆顶元素以及向下调整函数
      • 3.3.6 返回堆顶元素
      • 3.3.7 销毁堆
      • 3.3.8计算堆当前储存元素个数
    • 3.4堆的完整代码

1、什么是树

1.1 树的概念

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

  • 有一个特殊的结点,称为根节点,根节点没有前驱节点
  • 除根节点外,其余节点被分成M(M>0)个互不相交的集合T1、T2……、Tm,其中每一个集合Ti(1<=i<=m)又是一颗结构与树类似的子树。每一颗子树的根节点有且只有一个前驱,可以有0个或多个后继
  • 因此树是递归定义的
    在这里插入图片描述
    注意:树型结构中,子树之间不能有交集,否则就不是树型结构,如下
    在这里插入图片描述

1.2 树的相关概念

在这里插入图片描述

  • 节点的度:一个节点含有的子树的个数称为该节点的度:如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点;如上图:B、C、H、I…等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点;如上图:D、E、F、G…等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图:A是B的父亲节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点:如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点:如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度;如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,依次类推
  • 树的高度或深度:树中节点的最大层次;如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄节点;如上图:H、I互为堂兄弟节点
  • 节点的祖先:从根节点到该节点所经分支节点上的所有节点 ;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)颗互不相交的树的集合称为森林。

1.3 树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保持值域,也要保存节点和节点的关系,实际中树有很多种表示方式如:双亲表示法、孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。这里着重介绍最常用的孩子兄弟表示法(左孩子右兄弟)。

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

在这里插入图片描述

2、二叉树概念及结构

2.1 概念

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

在这里插入图片描述
从上图可以看出:
1.二叉树不存在度大于2的节点
2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

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

2.2 特殊的二叉树

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

2.3 二叉树的性质

1.若规定根节点的层数为1,则一颗非空二叉树的第i层上最多有2^(i-1)个节点。
2.若规定根节点的层数为1,则深度为h的二叉树的最大根节点树是(2^h)-1。
3.对任何一颗二叉树,如果度为0其节点个数为n0,度为2的分支节点个数为n2,则有n0=n2+1。
4.若规定根节点的层数为1,具有n个根节点的满二叉树的深度,h=log2(n+1)(log以2为底,n+1为对数)
5.对于具有n个节点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的节点有:

1、若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
2、2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3、2i+1<n,右孩子序号:2i+1,2i+1>=n否则无右孩子

2.4 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序存储,一种链式存储
1、顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上一颗二叉树。
在这里插入图片描述
2、链式存储
二叉树的链式存储结构是指,用链来表示一颗二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个节点由三个域组成,数据域和左右指针域,左右指针分别用来给出该节点左孩子和右孩子所在的链式节点的存储地址。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。

在这里插入图片描述

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

3.1 二叉树的顺序结构

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

3.2 堆的概念及结构

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

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父亲节点的值;
  • 堆总是一颗完全二叉树。
    在这里插入图片描述

3.3 堆的实现

3.3.1 创建一个堆

在.c文件中用一个结构体创建一个堆

//创建一个小根堆,小根堆的具体实现在插入函数中引用的向上调整函数,以此不断插入就能创建堆
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;   //堆用数组实现,创建一个整形数组用来存放堆的数据
	int size;        //堆当前的实际大小
	int capacity;    //堆的容量
}HP;
  1. 结构体中包含了实现堆的数组a、堆的当前的实际大小与堆的容量。
  2. 因为堆可以插入与删除,所以堆的大小要求是可变的,即要求实现堆的数组是动态开辟的,动态开辟的空间用一个指针存储其首地址。

3.3.2 初始化堆

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;

}
  1. 在堆中还没有任何数据时,数组中也没有任何数据,即还没有动态开辟空间,将指针指向一个空指针。
  2. 此时,堆的当前大小与总容量都是0。

3.3.3 堆的插入函数以及向上调整函数

插入x以后依然保持堆的形状

void HeapPush(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("ralloc fail");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	//扩容完后将数据插入到堆的最后一个位置(数组的最后一个位置)
	php->a[php->size] = x;
	php->size++;

	//将插入的数据根据情况向上调整
	AdjustUp(php->a, php->size - 1);  //将数组和数组中最后一个数的下标传过去
}
  1. 先对接受的形参判空
  2. 插入之前先检查堆中是否还有多余的空间以供插入。当前大小与总容量相等时,说明堆已经满了,需要动态开辟空间。
  3. 堆满了有两种情况,一种是堆刚刚初始化,还没有往里面储存任何内容,这时可以先给他四个整形大小的空间(动态开辟四个整形空间的大小)(也可以多给点,看自己);另一种是,已经储存了数据,当时数组已经没有多余的空间,这时动态开辟原有空间两倍大小的空间(为什么是两倍,因为如果开多了,空间会浪费,如果开小了,需要频繁动态开辟空间,造成空间碎片化)。
  4. 将动态开辟空间的首地址指向数组a
  5. 空间足够以后,将需要插入的数据插入到数组的最后一个位置上,即堆的最后子节点,这样不会破坏堆整体的父子关系。
  6. 创建一个向上调整函数,将插入的子节点与其父节点进行比较,根据是大根堆还是小根堆,将子节点与父节点位置关系进行调整,以维持堆的形状。

向上调整函数

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;  //找到堆最后一个数的父亲的下标
	while (child > 0)   //孩子的下标等于0时,说明堆从最后一个数一路向上比较,已经到达堆顶了
	{
		//小根堆,任意孩子的值要大于父节点的值,不是的话则要向上调整
		if (a[child] < a[parent])   //改为>,这个堆结构就成为大堆了
		{
			Swap(&a[child], &a[parent]);

			//修正父亲与孩子的下标,通过循环不断比较,直到成为堆的形状
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

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

  1. 向上调整函数将整个数组与数组最后一个下标传过去,即向上调整函数是将堆中最后的元素与其父节点进行比较,依次向上调整。
  2. 当插入一个节点后,找到其父节点,子节点与父节点的坐标关系如下:

leftchild = parent * 2 + 1,左孩子的数组下标都是奇数
rightchild = parent * 2 + 2,右孩子的数组下标都是偶数
parent = (child - 1)/2,对于由子节点找父节点,不论是左孩子还是右孩子,都是这个公式,因为对于同一父节点的奇数子节点与偶数子节点-1除2得到的结果都是一样的。

  1. 对于小根堆来说,父节点小于等于子节点,如果插入的子节点小于其父节点,将子节点与其父节点进行交换,交换完毕后,修正子节点与父节点的下标,通过循环,不断往堆顶修正,直到循环终止。
  2. 循环终止条件,当子节点等于0了,说明已经向上调整到堆顶了。或者不满足修正条件,直接break出来。

在主函数中调用插入函数

int main()
{
	//int a[] = { 15,18,19,25,28,34,65,49,27,37 };
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);    //依次插入,插入完成后就是堆的形状
	}

	
	return 0;
}

调试结果

在这里插入图片描述

在这里插入图片描述

  1. 创建一个堆 HP hp;,将堆 hp初始化HeapInit(&hp);
  2. 给定一组数{ 65,100,70,32,50,60 },根据插入函数依次插入,最终得到堆的形状{ 32,50,60,100,65,70 }
  3. 这个插入函数随着树的插入,堆也随之构建完成。

3.3.4 堆的打印

//判空函数
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

void HeapPrint(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	int i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}
  1. 堆的打印就是依次将数组中的元素遍历一遍,访问一个数打印一个数,知道数组访问完毕。
  2. 堆的打印首先堆传过来的指针进行断言,防止传过来的是空指针。
  3. 再对传过来的堆进行判空,如果堆中没有储存元素,也就不用再进行接下来的打印了。

3.3.5 删除堆顶元素以及向下调整函数

//向下调整函数
void AdjustDown(HPDataType* a, int n, int parent)
{
	int minChild = parent * 2 + 1; //先默认左边的孩子是整个小根堆中次小的孩子
	while (minChild < n)
	{
		//与右孩子比较一下,找出小的那个孩子的下标
		if (minChild + 1 < n && a[minChild + 1] < a[minChild])
		{
			minChild++;
		}
		//找到次小的孩子后将其与父节点比较
		if (a[minChild] < a[parent])
		{
			Swap(&a[minChild], &a[parent]);
		    //修正父亲与孩子的下标,通过循环不断比较,直到成为堆的形状
			parent = minChild;
			minChild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
	
}

//删除堆顶的元素 --找次大或者次小,小堆找次小,大堆找次大
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	//将堆顶元素与堆中最后一个元素交换,然后将最后一个元素删除,这样堆顶元素就被删除了。
	Swap(&(php->a[0]), &(php->a[php->size - 1]));
	php->size--;

	//向下调整,对于小根堆来说,找到次小元素,将其作为堆的堆顶,然后向下不断调整,恢复之前小根堆的形状。
	AdjustDown(php->a, php->size, 0);
}
  1. 删除堆顶元素后,要保证堆依然要保持堆的形状,所以不能将数组中的第一个元素删除掉,然后将数组的第二个元素提到前面来作为根节点,这样会将原本的兄弟关系变成父子关系,打乱堆的形状。将打乱的堆重新插入一遍回复堆的形状非常的浪费时间。
  2. 删除堆顶元素最好的方式是将堆顶元素与最后一个元素进行交换,交换后,将最后一个元素删除,这时堆顶元素就被删除了。堆顶元素被删除后,其他层级的父子关系并没有被打乱,只有新调换上来的堆顶元素不符合堆,这时创建一个现下调整函数,对于小根堆来说,找到原来堆中的次小元素,由其来作为堆顶元素(对于大根堆来说,找到原来堆中的次大元素,由其来作为堆顶元素)。
  3. AdjustDown(php->a, php->size, 0); 向下调整函数,将数组和数组的大小,以及堆顶元素的坐标传过去。
  4. 向下调整函数一个重要的功能就是找到次小或次大的节点,然后将其作为根节点。这段代码是小根堆,所以向下找次小的孩子。
  5. 先默认为左孩子是整个小根堆中次小的节点,将左孩子与右孩子比较一下,如果右孩子比左孩子小,就将其值赋给次小孩子。找到次小孩子后,将其与父节点进行比较,将次小孩子换到根节点上位置去。然后将父节点与子节点的坐标进行修正,通过循环,不断将父节点与子节点进行交换,直到循环终止。
  6. 循环终止条件,当子节点的坐标等于堆中的当前实际大小时,说明已经循环已经来到最后,所有的都已经进行比较过了。循环终止的另一条件是,当子节点与父节点不用交换,直接break出来。

在主函数中调用删除堆顶元素函数

int main()
{
	//int a[] = { 15,18,19,25,28,34,65,49,27,37 };
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);

	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);    //依次插入,插入完成后就是堆的形状
	}

	
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	return 0;
}

打印结果
在这里插入图片描述
在这里插入图片描述

  1. 从打印结果来看,堆顶元素32被删除了,堆的最终形状依然维持着小根堆的形状。

3.3.6 返回堆顶元素

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}
  1. 直接将堆顶元素return就行

在主函数中调用返回堆顶元素函数

int main()
{
	//int a[] = { 15,18,19,25,28,34,65,49,27,37 };
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);    //依次插入,插入完成后就是堆的形状
	}

	
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	printf("%d ",HeapTop(&hp));

	return 0;
}

打印结果
在这里插入图片描述

  1. 堆顶元素50被返回。

3.3.7 销毁堆

void HeapDestroy(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
  1. 因为储存堆中元素的数组是动态开辟的,所以将数组free掉,再直接置空。
  2. 数组为空后,再将表示堆当前的实际大小与堆的容量赋值为0.

在主函数中调用销毁堆的函数

int main()
{
	//int a[] = { 15,18,19,25,28,34,65,49,27,37 };
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);    //依次插入,插入完成后就是堆的形状
	}

	
	HeapPrint(&hp);

	HeapDestroy(&hp);
	HeapPrint(&hp);

	return 0;
}

打印结果
在这里插入图片描述

  1. 可以看到,当堆被销毁后,再次调用打印堆函数,打印函数中断言出现警告,说明堆中已无元素。

3.3.8计算堆当前储存元素个数

//返回堆当前存储数据的个数
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

在主函数中调用

int main()
{
	//int a[] = { 15,18,19,25,28,34,65,49,27,37 };
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);    //依次插入,插入完成后就是堆的形状
	}

	
	HeapPrint(&hp);

	int z = HeapSize(&hp);
	printf("%d\n", z);

	return 0;
}

打印结果
在这里插入图片描述

3.4堆的完整代码

Heap.h部分:包含了库函数的头文件以及自定义函数的声明。

#pragma once

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

//堆是完全二叉树
//堆的二叉树用数组表示,在数组的顺序从上至下,从左至右
//小根堆,任何节点的值小于等于孩子的值
//大根堆,任何节点的值大于等于孩子的值
//数组下标计算父子关系的公式
//leftchild = parent*2 + 1   左孩子的数组下标都是奇数
//rightchild = parent*2 + 2  右孩子的数组下标都是偶数
//parent = (child - 1)/2



//创建一个小根堆,小根堆的具体实现在插入函数中引用的向上调整函数,以此不断插入就能创建堆
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;   //堆用数组实现,创建一个整形数组用来存放堆的数据
	int size;        //堆当前的实际大小
	int capacity;    //堆的容量
}HP;

//初始化堆
void HeapInit(HP* php);

//打印堆
void HeapPrint(HP* php);

//销毁堆
void HeapDestroy(HP* php);

//插入x以后依然保持堆的形状
void HeapPush(HP* php, HPDataType x);  

//向上调整
void AdjustUp(HPDataType* a, int child);

//向下调整
void AdjustDown(HPDataType* a, int n, int parent);

//删除堆顶的元素
void HeapPop(HP* php);

//返回堆顶的元素
HPDataType HeapTop(HP* php);

//判空函数
bool HeapEmpty(HP* php);

//返回堆当前存储数据的个数
int HeapSize(HP* php);

Heap.c部分:实现各自定义函数的功能。

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"


//初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;

}

//打印堆
void HeapPrint(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	int i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

//销毁堆
void HeapDestroy(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//向上调整函数
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;  //找到堆最后一个数的父亲的下标
	while (child > 0)   //孩子的下标等于0时,说明堆从最后一个数一路向上比较,已经到达堆顶了
	{
		//小根堆,任意孩子的值要大于父节点的值,不是的话则要向上调整
		if (a[child] < a[parent])   //改为>,这个堆结构就成为大堆了
		{
			Swap(&a[child], &a[parent]);

			//修正父亲与孩子的下标,通过循环不断比较,直到成为堆的形状
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//插入x以后依然保持堆的形状
void HeapPush(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("ralloc fail");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	//扩容完后将数据插入到堆的最后一个位置(数组的最后一个位置)
	php->a[php->size] = x;
	php->size++;

	//将插入的数据根据情况向上调整
	AdjustUp(php->a, php->size - 1);  //将数组和数组中最后一个数的下标传过去
}

//向下调整函数
void AdjustDown(HPDataType* a, int n, int parent)
{
	int minChild = parent * 2 + 1; //先默认左边的孩子是整个小根堆中次小的孩子
	while (minChild < n)
	{
		//与右孩子比较一下,找出小的那个孩子的下标
		if (minChild + 1 < n && a[minChild + 1] < a[minChild])
		{
			minChild++;
		}
		//找到次小的孩子后将其与父节点比较
		if (a[minChild] < a[parent])
		{
			Swap(&a[minChild], &a[parent]);
		    //修正父亲与孩子的下标,通过循环不断比较,直到成为堆的形状
			parent = minChild;
			minChild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
	
}

//删除堆顶的元素 --找次大或者次小,小堆找次小,大堆找次大
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	//将堆顶元素与堆中最后一个元素交换,然后将最后一个元素删除,这样堆顶元素就被删除了。
	Swap(&(php->a[0]), &(php->a[php->size - 1]));
	php->size--;

	//向下调整,对于小根堆来说,找到次小元素,将其作为堆的堆顶,然后向下不断调整,恢复之前小根堆的形状。
	AdjustDown(php->a, php->size, 0);
}

//返回堆顶的元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}

//判空函数
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

//返回堆当前存储数据的个数
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

Test.c部分:主函数放在这,在主函数中调用个函数。在实现各函数时,可以用来测试各函数的功能。

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

int main()
{
	//int a[] = { 15,18,19,25,28,34,65,49,27,37 };
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);    //依次插入,插入完成后就是堆的形状
	}

	
	HeapPrint(&hp);

	int z = HeapSize(&hp);
	printf("%d\n", z);

	return 0;
}

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

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

相关文章

【计算机毕业设计】基于JSP的网上购物系统的设计与实现

分类号&#xff1a;TP315 U D C&#xff1a;D10621-408-(2007)5883-0 密 级&#xff1a;公 开 编 号&#xff1a;2003214012 学位论文 基于JSP的网上购物系统的设计与实现 基于JSP的网上购物系统的设计与实现 摘 要 近年来&#xff0c;随着Internet的迅速崛起&#xff0c…

内存 分页、交换空间

目录 1. 分页 1.1 地址转换 1.2 页表存在哪里 1.3 列表中究竟有什么 1.4 分页的优缺点 2. 快速地址转换&#xff08;TLB&#xff09; 2.1 TLB 的基本算法 2.2 谁来处理 TLB 未命中 2.2.1 硬件处理 2.2.2 软件&#xff08;操作系统&#xff09;处理 2.3 TLB 的内容 …

[附源码]Python计算机毕业设计SSM精准扶贫系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

(免费分享)基于springboot,vue公司财务系统

该系统是一个简单的公司财务管理系统&#xff0c;包含用户基本信息管理&#xff08;员工管理&#xff0c;管理员管理&#xff09;&#xff0c;工资管理&#xff08;员工工资管理&#xff0c;管理员工资管理&#xff09;&#xff0c;业务管理&#xff08;员工业务管理&#xff0…

Windows server 2012搭建用户隔离FTP站点

Windows server 2012搭建用户隔离FTP站点 系统添加FTP功能创建FTP登陆账户和其使用的文件夹D盘根目录下创建FTP站点主目录ftp文件夹ftp下创建用户主目录localuser&#xff08;名称不可更改&#xff0c;实现用户隔离必要步骤&#xff09;Localuser文件夹下创建对应用户的文件夹…

opencv入门笔记(二)

目录图像运算位运算位与运算位或运算取反运算异或运算位运算特点示例&#xff1a;位运算示例加法运算示例&#xff1a;查看三种加法运算的区别滤波器均值滤波中值滤波高斯滤波双边滤波示例&#xff1a;查看多种滤波器的处理效果视频处理示例&#xff1a;打开笔记本电脑内置摄像…

轻量化神经网络(移动设备上的神经网络)的整体框架

提示&#xff1a;不断更新中 文章目录一、为什么要引入轻量化神经网络二、模型压缩(Model Compression)参数修建低秩因子分解参数量化知识蒸馏人工神经架构设计三、自动压缩和神经架构搜索(Automated Compression and Neural Architecture Search)自动模型压缩(Automated Model…

【软件工程】白盒测试:基本路径测试

基本路径测试是在程序控制流图的基础上&#xff0c;通过分析控制构造的环路复杂性&#xff0c;导出基本可执行的路径集合&#xff0c;从而设计测试用例的方法。 步骤(以一段代码为例)&#xff1a; &#xff08;1&#xff09;画出控制流图 void sort(int num,int t) 1. { 2. i…

MySQL数据库的SQL语句

MySQL数据库的SQL语句MySQL的常用数据类型MySQL数据类型及含义char与varchar的区别MySQL的基本命令登录数据库查看MySQL数据库版本查看当前服务器中的数据库查看MySQL库中的表查看表的结构&#xff08;字段&#xff09;SQL语句SQL简介及分类创建新的数据库创建新的表添加表中各…

卡尔曼滤波之基本概念和状态观测器

图裂参考原文&#xff1a;https://www.cnblogs.com/odesey/p/16937124.html 1. 为什么使用 Kalman Filters&#xff1f; 1.1 什么是 Kalman Filters&#xff1f; Kalman Filters 是一种优化估计算法。下面使用使用两个例子说明 Kalman Filters 的用处。 例子1&#xff1a; …

电子学会2021年3月青少年软件编程(图形化)等级考试试卷(二级)答案解析

目录 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题&#xff08;共2题&#xff0c;共30分&#xff09; 青少年软件编程&#xff08;图形化&a…

Java#32(异常, File)

目录 一.异常 异常: 代表程序出现问题 二.File 1.File的三种构造方法 2.File的常见的成员方法 一.异常 异常: 代表程序出现问题 图解: 从Java文件------>字节码文件的过程就是编译(在这期间的异常就是编译时异常) 从字节码文件------->运行结果的过程就是运行(在这期…

Linux系统调用与API

系统调用是应用程序与操作系统内核之间的接口&#xff0c;它决定了应用程序是如何与内核打交道的。无论程序是直接进行系统调用&#xff0c;还是通过运行库&#xff0c;最终还是会达到系统调用这个层面。 系统调用介绍 1、什么是系统调用 在现代的操作系统里&#xff0c;程序运…

CEC2015:动态多目标野狗优化算法求解CEC2015(提供完整MATLAB代码,含GD、IGD、HV和SP评价指标)

一、动态多目标优化问题简介 现实世界中&#xff0c;许多优化问题不仅具有多属性&#xff0c;而且与时间相关&#xff0c;即随着时间的变化&#xff0c;优化问题本身也发生改变&#xff0c;这类问题称为动态多目标优化问题&#xff08;dynamic multi-objective optimization p…

写一个flutter程序—记录

目录 使用外部package 添加一个Stateful widget Flutter是谷歌的移动UI框架&#xff0c;可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界&#xff0c;Flutter正在被越来越多的开发者和组织使用&#xff0c;并且Flutter是完全…

[附源码]Python计算机毕业设计SSM精品旅游项目管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【Pytorch安装】windows下,安装了torch但是import torch失败

【Pytorch安装】windows下&#xff0c;安装了torch但是import torch失败问题原因解决问题 输入 python 进入python环境&#xff0c;输入 import torch 但是显示找不到这个包 输入 pip list 查看之前是否安装成功 但是可以看到包都在 原因 电脑中先前安装了多个 python &am…

Ajax用法总结

目录 Ajax简介 Ajax使用 xhr内部五种状态 Ajax在IE浏览器上存在的缓存问题 如何发送post请求 如何取消Ajax请求 ​编辑 jQuery封装的Ajax如何使用&#xff1f; Ajax简介 Ajax全称为Asynchous Javascript And XML&#xff0c;即异步的JS和XML&#xff0c;通过Ajax可以在…

Linux进程的调度

目录 调度策略与调度类 实时调度策略 普通调度策略 调度类 sched_class有几种实现&#xff1a; 完全公平调度算法 调度队列与调度实体 调度类是如何工作的&#xff1f; 调度策略与调度类 在Linux里面&#xff0c;进程大概可以分成两种。 一种称为实时进程&#xff0c;…

Redis主从复制+哨兵模式

必读 redis的主从复制是单向的&#xff0c;只能有主节点到从节点&#xff0c;主节点以写为主从节点以读为主不能写入数据&#xff01;因为系统的80%的需求都是读的需求。 redis服务默认自己是主节点&#xff0c;一个主节点由一个或多个从节点&#xff0c;一个从节点只有一个主…