数据结构(初阶5)---堆与堆排序(详解)

堆与堆排序

再将堆排序之前,我们先引入二叉树概念

一.二叉树初探

1).基本概念

二叉树是一种数据结构,二叉树形如:

1.其中A节点被称为根节点,B和C对应的两个树分别被称为左子树右子树

2.度:节点上连接的子树的个数被称为这个节点的度,二叉树不存在度大于2的节点

3.度为0的节点被称为叶子节点。

4.该节点的孩子节点为该节点链接的下一个节点,如B 的孩子节点为DE

5.该节点的父亲节点为该节点的上一个节点,如B 的父亲节点为A

6.树的度:该数中所有节点的度的最大值。

其中二叉树还有两种特殊的结构:

7.树的层数,字面意思,根节点记为一层

2).满二叉树和完全二叉树

1.满二叉树:

若每一层的节点都是满的的情况,即层数为n,节点个数为2^n-1

2.完全二叉树

当二叉树共有n层,其中n-1层是满二叉树 ,并且第n层的节点分布是从左到右依次排列的

1.度为0的节点的个数 ==度为2的节点的个数+1

2.有n个节点的满二叉树的高h=log2(n-1);

3.)二叉树的存储方式

在这里我们重点探究二叉树的顺序储存(ps:适用于满二叉树和完全二叉树)

将完全二叉树的每一层按顺序依次排列到数组中去,可以发现以下特点:

1.父亲节点的脚标n,儿子节点的脚标m,可以得出m=n * 2+1或m=n * -2+2 ,同理n=(m-1)/2

通过这种特性,我们可以用顺序结构去访问我们的完全二叉树

二.堆与堆排序

1.堆(完全二叉树的特例)

堆分为大堆和小堆:

大堆表示为一棵完全二叉树 ,其每一个父亲节点都比他的孩子节点要大

小堆表示为一棵完全二叉树 ,其每一个父亲节点都比他的孩子节点要小

1).建堆(向下调整法)

1.向下调整法:

c 复制代码
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(HPDataType* a, int n, int root)
{
	int parent = root;
	int child =  root * 2 + 1;
	while (child<n)
	{
		if ((child+1)<n&&*(a + child) > *(a + child + 1))
		{
			child++;
		}
		if (*(a + child) < *(a + parent))
		{
			Swap(a + child, a + parent);
			parent = child;
			child = parent * 2 + 1;
			
		}
		else
		{
			break;
		}
	}
}

不难得出向下调整法的时间复杂度为O(logn)

** 2.建堆**

这是处理一个节点无序的情况,如果整个给的数组完全无顺,我们该怎么办呢

接下来,我们来讲一下建堆 的过程:

我们找到最后一个叶子节点M,可知M的父亲节点N是最后一个父亲节点,所以我们只需要从这个父亲节点开始向上依次遍历一遍,其中每一次遍历都在经历一次向下调整法,就可以实现建堆

eg:建小堆

c 复制代码
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(HPDataType* a, int n, int root)
{
	int parent = root;
	int child =  root * 2 + 1;
	while (child<n)
	{
		if ((child+1)<n&&*(a + child) > *(a + child + 1))
		{
			child++;
		}
		if (*(a + child) < *(a + parent))
		{
			Swap(a + child, a + parent);
			parent = child;
			child = parent * 2 + 1;
			
		}
		else
		{
			break;
		}
	}
}
void HeapInit(Heap* php, HPDataType* a, int n)
{
	php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	memcpy(php->_a, a, n*sizeof(HPDataType));
	php->_size = n;
	php->_capacity = n;
	int child_ini = (n - 1-1) / 2;
	while (child_ini>=0)
	{
		AdjustDown(php->_a, n, child_ini);
		child_ini--;
	}
int main()
{
	int a[] = { 27,15,22,33,44,23,43,2,32,12 };
	Heap HP;
	HeapInit(&HP, a, sizeof(a)/sizeof(a[0]));
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", HP._a[i]);
	}

	return 0;
}

我们来思考一下,建堆的时间复杂度O 是多少

已知共有n个节点(假设为满二叉树)

树高h=log2(n+1)

每一层的个数=2^(h'-1)    h'为当前层数

遍历次数S=h*2^(0)+(h-1)*2 ^(1)+(h-2)2 ^(2)...+1 2 ^(h-1)

这是一个等差数列乘上等比数列,用错位相减法

2S-S最后的结果是

S=2^h -h -1=n-log2(n+1)

时间复杂度为O(n)

2).堆排序

接下来进入真正的重头戏------堆排序

如果我们随意给出一个数组,里面的数全是无序的

那么我们如何去排序呢,直接使用冒泡排序,选择排序,插入排序?

如果我们给的数组足够大,那么使用这种排序方法效率就会非常的低下

这时我们强大的堆排序就要派出用场了:

(ps:这边我们以升序为例,升序建大堆,降序建小堆)

c 复制代码
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(HPDataType* a, int n, int root)
{
	int parent = root;
	int child =  root * 2 + 1;
	while (child<n)
	{
		if ((child+1)<n&&*(a + child) < *(a + child + 1))
		{
			child++;
		}
		if (*(a + child) > *(a + parent))
		{
			Swap(a + child, a + parent);
			parent = child;
			child = parent * 2 + 1;
			
		}
		else
		{
			break;
		}
	}
}
void HeapSort(HPDataType* php, int n)
{
	int child_ini = (n - 1 - 1) / 2;
	while (child_ini >= 0)
	{
		AdjustDown(php, n, child_ini);
		child_ini--;
	}//先建堆
	Swap(php, php + n-1);
	n--;//第一次先交换
	for (;n>0;n--)
	{
		AdjustDown(php, n, 0);//每一次向下调整
		Swap(php, php + n - 1);
	}
}
nt main()
{
	int a[] = { 20,15,16,13,12,19 };
	HeapSort(a, sizeof(a) / sizeof(a[0]));
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

我们来思考一下,堆排序的时间复杂度是多少

第一次的建堆O(n)

往后的n次遍历,每一次都向下调整O(logn)

我们可以近似的去处理,时间复杂度为

O(n+nlogn)=O(nlogn)

可以看出这是一种极为高效的排列方式,

稳定:在最好和最坏的情况下时间复杂度都是O(nlogn)

不稳定:堆排序有可能会改变两个值相等的数的相对位置,这也是他的不稳定性

创作不易,恳请留赞ヾ(^^*)))

相关推荐
elseif1235 小时前
【C++】vector 详细版
开发语言·c++·算法
LDR0066 小时前
LDR6020:多 Type‑C 端口角色管理与外设上电顺序的智慧核心
c语言·开发语言·云计算
变量未定义~6 小时前
既约分数、阶乘约数、逆元、最大质因子个数【算法赛】
算法
KaMeidebaby6 小时前
卡梅德生物技术快报|Western Blot 实验应用:肺肠轴机制研究全流程技术解析
前端·数据库·人工智能·算法·百度
AhriProGramming6 小时前
计算机科普故事会-<2>见微知著
算法
東隅已逝,桑榆非晚7 小时前
新手入门指南:认识 C 语言文件操作(上)
c语言·开发语言·笔记
BD4SXV7 小时前
线性二次调节器(Linear Quadratic Regulator,LQR)的无限时域最优控制求解与黎卡提方程
算法·自动化
ST——Jess7 小时前
2026年度传统文化数字化与命理科技(Ethno-tech)行业趋势研究报告:专业级数智工作台的技术壁垒与评测标准
人工智能·科技·算法·架构
Matrix_117 小时前
第13篇:非线性位移场——漩涡、鱼眼、水波纹与球面化
图像处理·算法
Lucky_ldy7 小时前
数据结构从入门到精通:顺序表
数据结构·链表