数据结构(初阶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=log~2~(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=log~2~(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-log~2~(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)

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

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

相关推荐
项目申报小狂人22 分钟前
广义正态分布优化算法(GNDO)Generalized Normal Distribution Optimization
算法·概率论
陵易居士30 分钟前
力扣周赛T2-执行操作后不同元素的最大数量
数据结构·算法·leetcode
LabVIEW开发1 小时前
什么样的LabVIEW控制算自动控制?
算法·labview
liuming19921 小时前
Halcon中histo_2dim(Operator)算子原理及应用详解
图像处理·人工智能·深度学习·算法·机器学习·计算机视觉·视觉检测
sc写算法2 小时前
Hash 映射
数据结构·算法·哈希算法
雅妮yyn2 小时前
头歌数据结构-排序的实现及其应用
数据结构·算法
云边有个稻草人2 小时前
【优选算法】—移动零(双指针算法)
算法·排序算法·双指针算法
小墨&晓末2 小时前
【PythonGui实战】自动摇号小程序
python·算法·小程序·系统安全
落魄君子3 小时前
SVM分类-支持向量机(Support Vector Machine)
神经网络·算法·支持向量机·分类
小王爱吃月亮糖3 小时前
补充--C++的项目结构和管理
数据结构·c++·笔记·学习