排序(三) 堆排序
2014-04-20 23:24
746 查看
堆排序
堆排序算法是 Floyd 和 Williams 在1964 年共同发明的,同时,他们发明了“堆”这样的数据结构。
引子:叠罗汉
定义:堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子节点的值,称为大项堆;或者每个结点的值都小于或等于其左右孩子节点的值,成为小项堆。
这里需要注意的是:根节点一定是堆中所有节点最大(小)者。
堆排序思路:将线性表排列成大顶(或者小顶)堆,然后将堆顶元素(当前最大值)与最后一个元素交换,然后将去除最后元素的其它元素重新调整为大顶堆,重复之前的操作,直到最后一个元素。
代码如下:
复杂度分析
它的运行时间主要是消耗在初始构建堆和在重建堆的反复筛选上。
在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和如有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第 i 次去堆顶记录重建堆需要用O(long i )的时间(完全二叉树的某个结点到根节点的距离为[log2 i ]+1),并且需要取 n-1 次堆顶记录, 因此, 重建堆的时间复杂度为O(n log n)。
所以,总体来说,堆排序的时间复杂度为O(n log n)。
由于堆排序对原始记录的排序状态并不敏感,因此无论是最好、最坏和平均时间复杂度均一样。这在性能上显然要远远好于冒泡、简单选择、直接插入的o(n^2)的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的交换与比较是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
堆排序算法是 Floyd 和 Williams 在1964 年共同发明的,同时,他们发明了“堆”这样的数据结构。
引子:叠罗汉
定义:堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子节点的值,称为大项堆;或者每个结点的值都小于或等于其左右孩子节点的值,成为小项堆。
这里需要注意的是:根节点一定是堆中所有节点最大(小)者。
堆排序思路:将线性表排列成大顶(或者小顶)堆,然后将堆顶元素(当前最大值)与最后一个元素交换,然后将去除最后元素的其它元素重新调整为大顶堆,重复之前的操作,直到最后一个元素。
代码如下:
/* 已知L->r[s..m]中记录的关键字除L->r[s]之外均满足堆的定义 */ /* 本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆 */ /* 通俗化理解:将一串数字进行从中间往前的最小树根排序过程,下例中,将 下标为 length/2 至 1 的元素进行最小树倒着排序 */ /* 50 50 / \ / \ 10 90 10 90 / \ / \ -> / \ / \ -> 30 70 40 80 60 70 40 80 / \ / \ 60 20 30 20 (第一次排序,L = length/2 ,在本例中,就是 第四个节点,即 60,调整排序) 50 50 / \ / \ 10 90 70 90 -> / \ / \ -> / \ / \ 60 70 40 80 60 10 40 80 / \ / \ 30 20(第二次排序,对 90、40、80排序) 30 20(第三次排序,i--,对以70为根的二叉树调整顺序) 90 / \ 70 80 -> / \ / \ 60 10 40 50 / \ 30 20(第四次排序,对以50为根的二叉树调整顺序,50与90交换,50再与80交换,最后完成) */ void HeapAdjust( SqList *L, int s, int m) { int temp, j; temp = L->r[s]; for( j = 2*s; j <= m; j*=2 ) //j=j*2的递增,是为了判断做过替换的j是否有孩子,如果有再作调整,没有则完成。 { /* 沿关键字较大的孩子结点向下筛选 */ if( j < m && L->r[j] < L->r[j+1] ) ++j; /* j为关键字中较大的记录的下标 */ if( temp >= L->r[j] ) break; /* 没有破坏原堆的性质,终止for循环 */ L->r[s] = L->r[j]; s = j; } L->r[s] = temp; } /* 我们所谓的将待排序的序列构建成一个大顶堆,其实就是从下往上、从右到左,将每个非终端节点(非叶节点)*/ /* 当作根节点,将其和其子树调整为大顶堆。 */ /* 对顺序表L进行堆排序 */ void HeapSort(SqList *L) { int i; for( i = L->length/2; i>0; i--) /* 把 L 中的 r 构建成一个大顶堆 */ HeapAdjust(L, i, L->length); for( i = L->length; i>1; i--) { /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */ swap(L, 1, i); /* 将L->r[1...i-1]重新调整为大顶堆 */ HeapAdjust(L, 1, i-1); } }
复杂度分析
它的运行时间主要是消耗在初始构建堆和在重建堆的反复筛选上。
在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和如有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第 i 次去堆顶记录重建堆需要用O(long i )的时间(完全二叉树的某个结点到根节点的距离为[log2 i ]+1),并且需要取 n-1 次堆顶记录, 因此, 重建堆的时间复杂度为O(n log n)。
所以,总体来说,堆排序的时间复杂度为O(n log n)。
由于堆排序对原始记录的排序状态并不敏感,因此无论是最好、最坏和平均时间复杂度均一样。这在性能上显然要远远好于冒泡、简单选择、直接插入的o(n^2)的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的交换与比较是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
相关文章推荐
- 排序六 堆排序
- 快速排序、堆排序、归并排序
- 基本排序系列之最简单讲述堆排序
- 选择类排序-堆排序 简单选择排序
- 排序练习【sdut 1582】【堆排序】
- 9种排序算法——堆排序,归并排序,插入排序,选择排序
- 10种算法原理(冒泡排序,选择排序,快速排序,堆排序,希尔排序,桶排序等)
- 排序算法2--简单选择排序、堆排序
- 排序(6)堆排序
- 排序-堆排序
- 【程序员笔试面试必会——排序①】Python实现 冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序、希尔排序
- 【数据结构和算法】排序算法之二:选择排序和堆排序
- 程序员必知的8大排序(二)-------简单选择排序,堆排序(java实现)
- 冒泡排序,插入排序,快速排序,归并排序,堆排序,选择排序,希尔排序
- 几种常用的排序方法5--堆排序
- 数据结构例程——选择排序之堆排序
- 算法基础:排序(四)——二叉堆、优先队列、堆排序——Python实现
- 排序——堆排序-大根堆(大顶堆)
- 《大话数据结构》第9章 排序 9.7 堆排序(上)
- 好用的排序之堆排序