您的位置:首页 > 其它

简单就能弄懂堆排序

2012-08-17 20:30 162 查看
对大多数学习数据结构的同学来说,堆是感觉上很难懂的一个数据结构,更何况用堆进行的操作,但你又不得不承认,堆在算法中绝对算一个很高效很重要的数据结构。大家弄不明白的原因大概是没有静下来去深究堆,笔者曾经也一样,但最近静下心来着实认真的梳理了一下堆,觉得只要弄懂以下几点,其实,堆也就是那么回事。

一、理解堆的定义

满足两个条件:

(1)满足完全二叉树的性质(要理解完全二叉树的性质,如果不懂的话翻翻尘封的书你就知道了)

(2)对于每个父节点Ki,满足Ki<=K2i+1且Ki<=K2i+2(或者Ki>=K2i+1且Ki>=K2i+2),分别称为最小堆和最大堆。

二、堆的构造思想(筛选法)

(1)初始化:将初始的关键字按层次的次序放到用一维数组表示的一棵完全二叉树的各个结点中。

(2)从子树建堆:很明显,因为是完全二叉树,所有i>(n-2)/2后面的结点ki都是叶结点(叶节点符合堆的性质,已经是堆不必调整),接下来要做的就是需要逐步把从 i = (n-2)/2开始结点ki,ki-1,...k0为根的子树逐次的排成堆。最后形成建堆的过程。(n指的是堆元素的总数)

三、建堆的算法与实现

下面我们以最小堆为例子来说明建堆过程。首先就要从序列索引 i = (n-2)/2的为根的结点Ki为子树开始形成最小堆。

(1)循环,步长为1,从 i = (n-2)/2开始到i= 0(整个树的根节点)位置,进行如下操作:逐次比较ki、k2i+1(Ki的左孩子)、k2i+2(ki的右孩子,如果存在的话)的大小,找到其中最小的数值赋给ki,因为这次过程可能会破坏之前更小子树建好的最小堆,此时我们还需要以同样的方式向下进行筛选,直到这整个子树形成最小堆为止。这就是在建堆中必不可少的向下的筛选法:FilterDown(int heap,int i,int n))

//筛选法调整堆
void FilterDown(int *heap,int start,int n)
{
int i = start,j = 2*i+1;//i node and the left child of i
int temp = heap[i];
while(j<n)
{
if(j+1<n && heap[j]>heap[j+1])
j++;//找到heap[i],heap[2i+1],heap[2i+2]中的最小值位置,然后赋值给heap[i]

if(heap[j]>temp) break;//heap[i]已经是最小的了
else
{
heap[i] = heap[j];//把三者中最小的值赋给子树父节点 i
i = j;//对子树的子树进行相应的最小堆构造
j = 2*i+1;// 置换参数
}
heap[i] = temp;
}
}


(2)循环对ki,ki-1,...k0的为根结点的子树进行向下筛选,直到整个完全二叉树的根为止。

//创建最小堆
void CreateMinHeap(int *heap,int n)
{
int start = (n-2)/2;
for(start = (n-2)/2;start>=0;start--)
{
FilterDown(heap,start,n);
}
}


四、堆排序应该怎么做?

很显然,形成最小堆之后最小的元素就是整个完全二叉树的根,每次挑出形成最小堆的根元素(根节点,即heap[0]),挑出来后进行交换然后在进行除这个元素以外更小规模的最小堆创建,依次找到各个规模更小的堆中的根元素,直到最后一个元素为止,这时候就能对这些元素进行排序了。

//最小堆排序
void MinHeapSort(int *heap,int n)
{
int size;
CreateMinHeap(heap,n);//建成最小堆可以得到最小的一个元素
for(size = n-1;size>0;size--)
{
swap(heap[0],heap[size]);//交换堆顶和最后一个元素,即将最小元素放到最后面
FilterDown(heap,0,size);//重新调整堆顶节点成为小顶堆
}
}


示例展示:



五、总结
堆排序是适合排序文件中的元素个数较大的情况。对于n个元素进行堆排序的时,其时间复杂度是O(nlog2n)。附加的存储空间需要一个只要求用于交换的节点,因此空间复杂度为O(1)。你弄明白了吗?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: