排序—堆排序

优先队列(堆)可以使用于时间复杂度为O(NlogN)的排序,基于该想法的排序被称为堆排序。

堆排序的一种简单实现方法是:对于要排序的数组A,首先通过线性时间将数组构建成一个堆,然后使用另一个数组B,每次deletemin后的结果都放入数组B中,当A变为空时,数组B就是排序后的结果,这个算法需要额外的空间来存放数组B,当数据量过大,并且内存较小时,这个算法可能会出现问题。

另一种方法不需要使用额外的空间,这种方法利用每次deletemin之后,堆中的元素个数-1这个事实,将删除的元素放在堆之后,当堆为空时,排序也就结束了。这个方法只需要在数组A中进行操作,且由于删除的元素会放在堆后,所以大根堆的排序结果为正序,小根堆排序结果为逆序。

对于如下数组进行堆排序:

6346382040435307826

首先建立堆:在优先队列中,元素下标从1开始,而在堆排序中,元素下标从0开始。对于N个元素的排序,首先找到下标为N/2的元素mid(由二叉堆的结构特性可知,mid后的元素一定没有孩子节点,所以它们已经满足堆序性),mid(包括mid)之前的元素都可能有孩子节点,那么借鉴于deletemin,将该位置的节点值保存在临时变量中,并且将该位置视为空穴,则通过下沉操作,可以使以该节点为根节点的子堆满足堆序性,当完成下标为0元素的下沉后,整个数组就变成了一个二叉堆。对于上面的数组,mid=43没有孩子节点,所以下沉从40真正开始。以 i 表示下标

i=4下沉:孩子节点 A[child=i*2+1]=26,有A[i]>A[child],满足堆序性。

6346382040435307826

i=3下沉:有两个孩子节点,找到其中的大者与父亲节点交换位置

6346382040435307826
6346387840435302026

i=2下沉:

6346387840435302026
6346437840385302026

i=1下沉:

6346437840385302026
6378434640385302026

而交换后的位置还有孩子节点,所以继续交换

6378434640385302026

i=0下沉:

6378434640385302026
7863434640385302026

继续下沉

7863434640385302026

到此就建立好了一个二叉堆。由于下沉操作是从底层向根进行的,所以当上层节点进行下沉时,下层的节点实际上已经满足堆序性,所以大部分下沉操作可能只循环一次,所以构建堆是花费线性时间的。

下面就要进行排序工作:

首先将A[0]删除(实际上是与堆之后的一个元素交换),那么堆的大小就-1,这时候堆最后一个位置就不再属于堆,刚好可以用来存放删掉的元素(最大元素)。而原来堆末尾的元素仍然属于堆(将0处元素与堆之后的一个元素交换),但交换后不一定满足堆序性,所以对该元素进行一次下沉(下沉操作与建立堆时的下沉相同,就不再详细演示)。

7863434640385302026
2663434640385302078
6346433040385262078

现在78已经位于正确的位置,下面再次进行deletemin,这时候堆之后的第一个位置就是20所在的位置:

6346433040385262078
2046433040385266378
4640433020385266378

之后的操作都是相似的,当堆的大小为2时,就是最后一趟排序,因为在这次排序中,就决定了A[0]A[1]的大小,整个数组的排序也就完成了。

堆排序的实现:

//堆排序
void down(int* arr, int i, int size) {//下沉操作
	int tmp = arr[i];//首先将i位置的元素存放在临时变量中,然后将i位置视为空穴
	int j = 0;
	int child = 0;
	for (j = i; j * 2 + 1 <= size - 1; j = child) {
		child = j * 2 + 1;
		if (child < size - 1 && arr[child] < arr[child + 1])//找到j位置的孩子节点中的大者
			child++;
		if (arr[child] > tmp) {//如果这个大者要大于i位置本来的元素(tmp),那么tmp就要下沉到该孩子节点的位置
			arr[j] = arr[child];
		}
		else {//如果tmp比两个(或一个)孩子节点都大,说明此时的j就是要插入的位置,就退出循环
			break;
		}
	}
	arr[j] = tmp;//将tmp放在正确的位置
}

void CreatHeap(int* arr, int size) {//构建堆,即size/2位置之前的所有元素进行下沉操作
	for (int i = size / 2; i >= 0; i--) {
		down(arr, i, size);
	}
}

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

void heap_sort(int* arr, int size) {

	CreatHeap(arr,size);//建立堆

	for (int i = size - 1; i > 0; i--) {
		swap(&arr[0], &arr[i]);//交换值
		down(arr, 0, i);
	}
}

堆排序的时间复杂度为O(NlogN),但在实践中,堆排序却是慢于使用Sedgewick增量的希尔排序(时间复杂度为O(N^{\frac{4}{3}}))。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值