《算法导论》学习笔记--第六章 堆排序
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)
运行结果:
当然也可以从大到小排列,请读者自行实现。
那么在深入堆排序之前先来列举一下常见的排序方法,
插入排序 ,最简单直观的排序方法,时间复杂度最坏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; }
运行结果:
当然也可以从大到小排列,请读者自行实现。
相关文章推荐
- 算法导论学习笔记-第六章-堆排序
- 算法导论学习笔记--2--堆排序
- 学习《算法导论》第六章 堆排序 总结
- 学习《算法导论》第六章 堆排序 总结二
- 算法导论学习笔记(一)排序算法之堆排序
- 算法导论学习笔记之四--堆排序
- 算法导论学习笔记——堆排序
- 算法导论-堆排序学习笔记
- 《算法导论》第六章 堆排序 笔记
- 算法导论学习笔记——第6章 堆排序
- 算法导论学习笔记(1)---堆排序
- 算法导论笔记-第六章-堆排序
- 算法导论学习笔记(17)——最小生成树
- 事务处理及锁定 【数据库高效编程 - 学习笔记 第六章】
- 算法导论学习笔记1---排序算法(平台:gcc 4.6.7)
- 算法导论---学习笔记012
- 算法导论学习笔记——快速排序算法
- Essential C++学习笔记-----第六章
- Scala学习笔记 - 快学scala第六章
- Java并发编程实战(学习笔记五 第六章 任务执行)