堆排序

堆结构

是一种数据结构,它的结构和完全二叉树 一致,分为大顶堆和小顶堆。什么是大顶堆、小顶堆呢?以大顶堆为例,堆顶元素是整个堆内值最大的元素,小顶堆反之保存的是最小的元素。如下图所示:
image.png

堆结构数据保存

堆结构原本是一个完全二叉树的结构,但是这里我们是借用堆来实现对数组的排序,所以我们用数组来保存堆中的数据,比如用下面的数组来保存上面堆结构里的数据:
image.png
除此之外我们还要知道在数组中如何对节点进行操作,比如如何获取当前节点的左子节点lchild、右子节点节点rchild和父节点parent。通过上面两个图的对比我们可以得出如下结论:
lchild = 2 * i+ 1;
rchild = 2 * i+ 2;
parent = (i - 1) / 2;
注意:这里的lchild、rchild、i(当前节点)都是数组中的下标索引。

heapify过程

heapify是将当前节点和它的两个子节点进行比较,把最大的值与父节点(当前节点)中的值进行交换,但是如果父节点(当前节点)的值就是三个节点中值最大的那么就不用交换
但是要注意的是:这里经过一趟heapify过程之后其实堆顶不一定是最大值,因为heapify只是保证了当前节点与它子节点值的大小关系,并没有保证整个堆的大小关系。
递归实现heapify过程:

/* arr[]: 待排序数组
*  len: 数组的长度
*  i: 当前要执行heapify过程的节点在数组中的索引
*/
void heapify(int arr[], int len,int i)
{
	if (i >= len) return;  // 递归边界
	int lchild = 2 * i + 1;  // 获取左子节点索引
	int rchild = 2 * i + 2;  // 获取右子节点索引
	int max = i;    // 保存最大值节点对应的索引
	if (lchild < len && arr[max] < arr[lchild])
	{
		max = lchild;
	} 
	if (rchild < len && arr[max] < arr[rchild])
	{
		max = rchild;
	}
    // 如果最大值保存在子节点中就交换 并且继续递归执行
	if (max != i)
	{
		mySwap(arr, max, i);
		heapify(arr, len, max);
	}
}

buildHeapify过程

由于heapify过程执行一遍后,堆顶元素不一定是最大值,所以我们要想办法将最大值移动到堆顶去,所以就需要用到buildHeapify。但是怎么实现buildHeapify才能将最大值移动到堆顶呢?
我们可以尝试一下从后向前挨个执行heapify过程然后观察结果。由于heapify里面每次比较的都是当前和子节点这三个节点的值,我们可以把它看做一个三元的最小单位,所以我们的开始位置是最后一个节点的父节点,依次从后向前遍历。这样说可能不太好理解 下面我们再通过画图的方式来实现下这个过程
如下面这个待排序的数组以及对应的完全二叉树:
image.png
(1)找到开始位置并对该位置进行一次heapify
开始位置 = (数组长度 - 1) / 2;
image.png
(2)继续向前遍历
image.png
(3)最后执行结果可见堆顶元素就是最大值
image.png
这里为了方便展示所以画的图比较简单,大家可以尝试一下其它比较复杂的图,比如9个节点的完全二叉树,然后按照这个过程自己再实现一遍。

void buildHeap(int arr[],int len)
{
	int parent = (len - 1) / 2; // 获取开始位置
    // 依次向前执行 heapify过程
	for (; parent >= 0; parent--)
	{
		heapify(arr, len, parent);
	}
}

堆排序 heapSort

了解了上面两个过程之后想必你已经对数组排序大致的思路,没错就是取出最大值然后再进行一次buildHeapify过程保证最大值在堆顶,然后再取再buildHeapify直到将元素取完。不过我们这里不是取值而是交换,怎么交换呢?就是将最大的值与数组最后一个元素的值进行交换然后再执行buildHeapify(注意这里的最后一个数组原数是相对的,也就是还没取出的前面一段子数组的最后一个元素 后面会在代码里面标出)这样做的目的就是为了节省额外额空间。

void heapSort(int arr[], int len)
{
	buildHeap(arr, len);
	for (int i = len - 1; i >= 0; i--)
	{
		mySwap(arr, i, 0);
		heapify(arr, i, 0);  // 这里的i对应的就是未取出子数组的最后一个元素
	}
}

稳定性分析

由于在堆排序过程中有很多次的交换操作,所以不能保证大小相等元素之间初始位置不变,所以堆排序是不稳定的排序算法。

复杂度分析

堆排序是一种选择排序,整体主要由构建初始堆、交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以一般认为堆排序 时间复杂度O(nlogn) 级。这里我们并没有申请额外的空间所以空间复杂度为O(1) 级。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值