您的位置:首页 > 其它

堆排序算法

2018-01-10 19:58 176 查看
前言:

一直对堆排序一知半解,现在总结一下,权当记录。

1、堆的概念

在介绍堆排序之前,首先需要说明一下,堆是个什么玩意儿。
是一棵顺序存储完全二叉树
其中每个结点的关键字都不大于其孩子结点的关键字,这样的堆称为小根堆
其中每个结点的关键字都不小于其孩子结点的关键字,这样的堆称为大根堆
举例来说,对于n个元素的序列{R0, R1, ... , Rn}当且仅当满足下列关系之一时,称之为堆:
(1) Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
(2) Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)
其中i=1,2,…,n/2向下取整; 



如上图所示,序列R{3, 8, 15, 31, 25}是一个典型的小根堆。
堆中有两个父结点,元素3和元素8。
元素3在数组中以R[0]表示,它的左孩子结点是R[1],右孩子结点是R[2]。
元素8在数组中以R[1]表示,它的左孩子结点是R[3],右孩子结点是R[4],它的父结点是R[0]。可以看出,它们满足以下规律
设当前元素在数组中以R[i]表示,那么,
(1) 它的左孩子结点是:R[2*i+1];
(2) 它的右孩子结点是:R[2*i+2];
(3) 它的父结点是:R[(i-1)/2];
(4) R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。

2、堆的构建
通过实例图来看一下如何构建初始堆。
设有一个无序序列{1,3,4,5,2,6,9,7,8,0}



3、堆排序
核心思想:简单来说,就是每次交换第一个和最后一个元素,输出最后一个元素(对于大根堆来说,这就是整个堆数组中的最大值),然后把剩下的元素调整为最大堆,当输出完最后一个元素时,整个数组已经是一个有序数组了。
仍然以上例加以说明



4、核心代码(以C++为例)
#include <iostream>

#include <vector>

using namespace std;

void heapAdjust(vector<int>& array, int parent, int len)

{
int temp = array[parent];    //temp保存当前父节点
int child = parent * 2 + 1;    //先获得左孩子
while (child < len)
{
// 如果有右孩子结点,并且右孩子结点值大于左孩子结点值,则选取右孩子结点
if (child + 1 < len && array[child + 1] > array[child])
child++;

if (temp >= array[child])
break;
array[parent] = array[child];
parent = child;
child = child * 2 + 1;
}

array[parent] = temp;

}

void heapSort(vector<int>& array)

{
// 为何i的初值为array.size() / 2 - 1:因为数组从array[array.size() / 2 - 1]开始的元素都是树的叶结点。
for (int i = array.size() / 2 - 1; i >= 0; i--)
heapAdjust(array, i, array.size());

// 进行n-1次循环,完成排序
for (int i = array.size() - 1; i >= 0; i--)
{
// 堆排序核心:最后一个元素和第一个元素交换
int temp = array[i];
array[i] = array[0];
array[0] = temp;

heapAdjust(array, 0, i);
}

}

int main()

{
vector<int> array;
array.push_back(1);
array.push_back(3);
array.push_back(4);
array.push_back(5);
array.push_back(2);
array.push_back(6);
array.push_back(9);
array.push_back(7);
array.push_back(8);
array.push_back(0);

cout << "before sort : ";
for (int i = 0; i < array.size(); i++)
cout << array[i] << " ";
cout << endl;

heapSort(array);

cout << "after sort : ";
for (int i = 0; i < array.size(); i++)
cout << array[i] << " ";
cout << endl;

system("pause");
return 0;

}

5、堆排序总结
(1)时间复杂度

堆的存储表示是顺序的。因为堆所对应的二叉树为完全二叉树,而完全二叉树通常采用顺序存储方式。
当想得到一个序列中第k个最小的元素之前的部分排序序列,最好采用堆排序。
因为堆排序的时间复杂度是O(n+klog2n),若k≤n/log2n,则可得到的时间复杂度为O(n)
(2)算法稳定性

堆排序是一种不稳定的排序方法。
因为在堆的调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径,
因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  排序算法