您的位置:首页 > 其它

《算法导论》学习笔记--第六章 堆排序

2012-02-20 12:55 351 查看
堆排序在运行时间上像合并排序,为(n*logn)。在空间上像插入排序,是一种原地排序算法,在任何时候,数组找那个只有常数个元素储存在输入数组以外。这样,堆排序就把以上两种算法的有点结合起来。

那么在深入堆排序之前先来列举一下常见的排序方法,

插入排序 ,最简单直观的排序方法,时间复杂度最坏O(n2 ),

合并排序,基于分治的一种排序算法,时间复杂度O(nlgn),但不是原地排序的,明显合并的时候需要较多的额外空间。

堆排序 ,我们下面要介绍的,时间复杂度O(nlgn), 而且是原地排序的。

快速排序 ,最差时间复杂度O(n2 ),平均的时间复杂度为O(nlgn),但是据说在实际引用时比堆排序高效。

下面开始介绍heap sort,

6.1 堆

那么堆排序当然核心就是堆这个数据结构,堆是个完全二叉树,而且每个节点都比左右子节点大(或小),因为堆分为max堆和min堆。

完全二叉树有个非常高效的存储方法,就是数组,一般的树都要用链表去存储。

对于heap sort的输入数组,如A[16,14,10,8,7,9,3,2,4,1],要进行堆排序,首先要建堆,建堆可以分为两步:

将输入数组抽象成完全二叉树

建堆BUILD-MAX-HEAP

那么上面的输入数组可以抽象成如下的二叉树,



那么一般你必须去记录这个树结构,对吧,一般用链表来记录节点,节点的左右子节点的指针,这样就需要耗费比输入数组多几倍的空间,这样就无法原地排序了。

妙就妙在,你根据输入数组依次建立的这个完全二叉树,不用任何额外的空间去记录。这就得益于完全二叉树本身就是可以用数组存储的,这种数据结构是非常高效的。

对于数组中任一节点,你想知道它在完全二叉树中的parent,left,right,非常容易:

PARENT (i)

return i/2

LEFT (i)

return 2i

RIGHT (i)

return 2i + 1

6.2保持堆的性质

那么现在对于输入数组,已经抽象为完全二叉树了,那就要开始建堆,

先来学习一个重要的堆操作MAX-HEAPIFY



这个函数就是对数组A中的第i个节点进行heapify操作

其实比较简单,1~7就是比较找出,i节点和左右子节点中,哪个最大

8~10,如果最大的不是i,那就把最大节点的和i节点交换,然后递归对从最大节点位置开始继续进行heapify

显而易见,对于n个节点的完全二叉树,高为lgn,对每个节点的heapify操作是常数级的,所以这个操作的时间复杂度就是lgn

6.3 建堆

那么有了heapify操作,建堆的算法很简单的,



说白了,就是对i从length[A]/2到1的节点进行heapify操作。所以这个操作的时间复杂度上限咋一看应该是nlgn,其实比这个小的多,约等于2n,就是说建堆的时间复杂度是O(n),能够在线性时间内完成,这个是很高效的。

所以我们只需要对所有非叶节点进行heapify操作就ok了

6.4 堆排序算法

折腾半天堆建好了,怎么堆排序了,光从堆是得不到一个有序序列的。



原理很简单,从堆我们只能知道最大的那个,那么就把最大的那个去掉,然后heapify找到第二大的,依次下去。

实现也很巧妙,没有用到额外的存储空间,把堆顶放到堆尾,然后堆size-1

这个算法的时间复杂度也是nlgn。

堆排序算法最重要的三步就是:保持堆的性质,建堆,堆排序算法

下面贴出c++语言实现:

(注:由于c++中数组是从0开始到n-1结束,所以left(i)=2i+1)

#include <IOSTREAM>

//#define PARENT(i) (i/2)
#define LEFT(i) (2*i+1)
//#define RIGHT(i) (2*i+1)

using namespace std;
//保持堆的性质
//是以i为跟的子树成为最大堆
void max_heapity(int* Array,int i,int n)
{
int l = LEFT(i);
int r = l+1;
int largest = i;
if (l<=n-1 && Array[l] > Array[i])
largest = l;
else
largest = i;
if (r<=n-1 && Array[r] > Array[largest])
{
largest = r;
}
if (largest != i)
{
int temp = Array[largest];
Array[largest] = Array[i];
Array[i] = temp;
max_heapity(Array,largest,n);
}

}
//建堆
void bulid_heap(int* Array,int n)
{

for(int i = n/2-1;i>=0;i--)
{
max_heapity(Array,i,n);
}
}

//堆排序算法
void heap_sort(int* Array,int n)
{

bulid_heap(Array,n);
int tmp_n = n;
for (int i = n-1;i>=1;i-- )
{
int temp = Array[i];
Array[i] = Array[0];
Array[0] = temp;
max_heapity(Array,0,--tmp_n);
}

}

int main()
{
int Array[10] = {16,14,10,8,7,9,3,2,4,1};
heap_sort(Array,10);
for(int i = 0 ; i <10;i++)
{
cout<<Array[i]<<" ";
}
cout<<endl;
return 0;
}


运行结果:



当然也可以从大到小排列,请读者自行实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: