从认识堆到堆排序一篇搞定(动图解释)

什么是堆?

在这里插入图片描述
在这里插入图片描述
🔶像上面所展示的两个完全二叉树都称作为堆,第一个叫小堆,因为双亲结点的值都小于子节点,第二个叫大堆,因为双亲结点的值都大于子节点,如果一个二叉树不满足大堆或小堆的特征,就不叫堆。

堆满足的性质:
0️⃣:子节点都不大于或不小于父结点。
1️⃣:是一个完全二叉树。

数据结构在堆实现时用的是数组的形式,父节点和子节点之间下标的关系是:

左子结点下标=父节点下标*2+1;

右子节点下标=父节点下标*2+2;

➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖

如何建堆?

向下调整算法
 以建小堆为例:比较两个子节点,找到小的那个和父结点交换,重复此步骤直到小堆建成。
 思想就是把小的往上换。

❗❗❗注意: 根的左子树右子树必须是堆

在这里插入图片描述
在这里插入图片描述
代码实现:

void Swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void Heapdown(int * p,int sz,int begin)//sz是数组大小,begin是开始调整的初始位置
{
	int parent = begin;
	int minson = parent * 2 + 1;//找到子节点,默认小的那个是左节点
	while (minson < sz)
	{
		if (minson+1<sz&&*(p + minson)>*(p + minson + 1))//如果右节点不存在就不需要比较,存在就比较,得到小的。
			minson++;
		if (*(p + minson) < *(p + parent))//把小的换上去
		{
			Swap(p + minson, p + parent);
			parent = minson;//迭代
			minson=parent*2+1;
		}
		else//如果较小的子节点比父节点还大,就不需要继续了,因为左右两边都是小堆,后面的还大。
			break;
	}

}

一般要建堆的数据可能不会那么美好,根的左右子树可能不是堆,向下调整算法就没办法了,例如:
在这里插入图片描述
如果使用向下调整算法得到的结果(建大堆):
在这里插入图片描述

可以看到得到的结果不是堆。

反向向下调整算法

为了解决上面的问题,我们的前辈想出来了这种方法。

🔶 算法思想如果把最后一个子结点(3)看为根,那么它没有子结点,可以将它看做大堆或小堆,其他没子节点的结点也一样不需要处理,第一个特殊的结点就是3的父节点,因为3可以看作大堆或小堆,满足向下调整算法的特点,直接调用即可,其他父节点也一样,这样当根使用向下调整算法后,堆自然也就形成了。
同样对于上面不能使用向下调整算法的数据,反向向下调整的顺序如下:
在这里插入图片描述
在这里插入图片描述
代码实现:

void Swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void Heapdown(int * p,int sz,int begin)//sz是数组大小,begin是开始调整的初始位置
{
	int parent = begin;
	int minson = parent * 2 + 1;//找到子节点,默认小的那个是左节点
	while (minson < sz)
	{
		if (minson+1<sz&&*(p + minson)>*(p + minson + 1))//如果右节点不存在就不需要比较,存在就比较,得到小的。
			minson++;
		if (*(p + minson) < *(p + parent))//把小的换上去
		{
			Swap(p + minson, p + parent);
			parent = minson;//迭代
		}
		else//如果较小的子节点比父节点还大,就不需要继续了,因为左右两边都是小堆,后面的还大。
			break;
	}

}

void HeapCreat(int *p, int sz)//建堆
{
	assert(p);
	int parent = (sz -1- 1) / 2;//最后一个堆结点的父结点
	while (parent >= 0)
	{
		Heapdown(p, sz,parent);//注意传sz数据时仍然传的是原数组的大小
		parent--;
	}
}
向上调整算法

算法思想:先将一个结点看作为堆,将后面的结点插入堆,以建小堆为例,子节点比父的小就交换
,否则不然,重复步骤直到小堆建成。

还是以上面不能使用向下调整算法为例:
在这里插入图片描述

代码实现:

void Heapup(int *p,int sz)
{
	assert(p);

	int child = 1;
	int parent = (child - 1) / 2;

	while (child < sz)
	{
		int tmp = child;//代替child迭代
		while (tmp >= 0)
		{
			if (*(p + tmp) < *(p + parent))
			{
				Swap(p + tmp, p + parent);
				//向上走
				tmp = parent;
				parent = (tmp - 1) / 2;
			}
			else
			{
				break;
			}
		}
		//新子结点,新父节点
		child++;
		parent = (child - 1) / 2;
	}
}

➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖

堆排序

堆排序的时间复杂度是O(nlogn),空间复杂度是O(1),稳定。 算法稳定性介绍

要使用堆排序肯定要建一个堆,先来一个小思考:

如果让你将一组数据使用堆排序为升序,是建大堆还是小堆?
在这里插入图片描述
答案是最好建大堆!

以升序为例:
算法思想:大堆堆顶数据是最大的,升序应该在数组的最后一位,那么我们将堆顶元素和最后一一个元素交换就可以达到,因为堆顶元素已经到达它该在的位置了,不需要对其再操作,所以数组大小-1屏蔽掉它,堆顶元素换成了不知大小的数,但是因为根的左子树和右子树的大堆结构没被破坏,之间使用向下调整算法就可以构成新大堆,重复此操作直到数组大小为1结束。

在这里插入图片描述

代码实现:

void Swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void Heapup(int *p, int sz)
{
	assert(p);

	int child = 1;
	int parent = (child - 1) / 2;

	while (child < sz)
	{
		int tmp = child;//代替child迭代
		while (tmp >= 0)
		{
			if (*(p + tmp) > *(p + parent))
			{
				Swap(p + tmp, p + parent);
				//向上走
				tmp = parent;
				parent = (tmp - 1) / 2;
			}
			else
			{
				break;
			}
		}
		//新子结点,新父节点
		child++;
		parent = (child - 1) / 2;
	}
}

void HeapCreat(int *p, int sz)//建堆
{
	assert(p);
	Heapup(p, sz);
}

void Heapsort(int *p, int sz)//排升序建大堆
{
	HeapCreat(p, sz);//建大堆,不能直接用向下调整算法!
	while (sz > 0)
	{
		Swap(p, p + sz - 1);//交换堆顶数据和最后一个数据
		sz--;//屏蔽最后一个数据
		Heapup(p, sz);//建大堆,上面介绍的三种方法都行
	}
}

关于堆的介绍就到这里了哈😀,如果有任何想法及博客中出现的问题欢迎再评论区留言哦,博主一定会回的!

创作不易,
赠人玫瑰,手留余香!
感谢支持!

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
堆排序法是一种基础的排序算法,其基本思想是将待排序的序列看作一棵完全二叉树,并将其转换为一个堆。堆是一种特殊的树形数据结构,满足以下两个条件: 1. 堆中的任意节点的值都必须大于等于(或小于等于)其子节点的值。 2. 堆总是一棵完全二叉树。 在堆排序中,我们首先将待排序的序列构建成一个大根堆(或小根堆),然后将堆顶元素与序列的最后一个元素交换位置,并将堆的规模减小1,再将堆进行调整,使其重新成为一个堆。重复这个过程,直到整个序列排好序为止。 下面是用C语言实现堆排序的示例代码: ```c void adjust_heap(int arr[], int i, int len) { int temp = arr[i], j; for (j = 2 * i + 1; j < len; j = 2 * j + 1) { if (j + 1 < len && arr[j] < arr[j + 1]) { j++; } if (temp >= arr[j]) { break; } arr[i] = arr[j]; i = j; } arr[i] = temp; } void heap_sort(int arr[], int len) { int i, temp; for (i = len / 2 - 1; i >= 0; i--) { adjust_heap(arr, i, len); } for (i = len - 1; i > 0; i--) { temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; adjust_heap(arr, 0, i); } } ``` 在这段代码中,我们首先定义一个 `adjust_heap` 函数,用于将一个节点及其子树调整为一个大根堆。在 `adjust_heap` 函数中,我们首先将节点值 `arr[i]` 存储在临时变量 `temp` 中,然后依次比较该节点和其左右子节点的值,找到最大的值,并将其与该节点进行交换。然后将指针 `i` 移动到子节点的位置,继续进行调整操作,直到该节点及其子树成为一个大根堆。 接着定义一个 `heap_sort` 函数,用于对整个序列进行堆排序。在 `heap_sort` 函数中,我们首先将待排序的序列构建成一个大根堆,然后依次将堆顶元素与序列的末尾元素交换位置,并将堆的规模减小1,再将堆进行调整,使其重新成为一个堆。重复这个过程,直到整个序列排好序为止。 堆排序的时间复杂度为 $O(n\log n)$,是一种原地排序算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值