堆排序-算法导论

在看搜索引擎做查询结果排序的用到了堆排序,特来复习一下。
那么在深入堆排序之前先来列举一下常见的排序方法,
Insertion sort ,最简单直观的排序方法,时间复杂度最坏O(n2 ),in place(Recall that a sorting algorithm sorts in place if only a constant number of elements of the input array are ever stored outside the array.)就是说除了输入数组,仅还需耗费常数大小的空间, 这里对于insert sorting,应该只在交换element时,需要一个element的额外的暂存空间。此方法适用于size很小的输入。
Merge sort ,基于分治的一种排序算法,时间复杂度O(nlgn),但不是in place的,明显merge的时候需要较多的额外空间。
Heap sort ,我们下面要介绍的,时间复杂度O(nlgn), 而且是in place的。
Quick sort , 快排,最差时间复杂度O(n2 ),平均的时间复杂度为O(nlgn),但是据说在实际引用时比堆排序高效。

下面开始介绍heap sort,
那么堆排序当然核心就是堆这个数据结构,堆是个完全二叉树,而且每个节点都比左右子节点大(或小),因为堆分为max堆和min堆。
完全二叉树有个非常高效的存储方法,就是数组,一般的树都要用链表去存储。
对于heap sort的输入数组,如A[16,4,10,14,7,9,3,2,8,1],要进行堆排序,首先要建堆,建堆可以分为两步:
将输入数组抽象成完全二叉树
建堆BUILD-MAX-HEAP 
那么上面的输入数组可以抽象成如下的二叉树,
            16
         4     10
       14   7  9   3
      2  8 1 
那么一般你必须去记录这个树结构,对吧,一般用链表来记录节点,节点的左右子节点的指针,这样就需要耗费比输入数组多几倍的空间,这样就无法in place了。
妙就妙在,你根据输入数组依次建立的这个完全二叉树,不用任何额外的空间去记录。这就得益于完全二叉树本身就是可以用数组存储的,这种数据结构是非常高效的。
对于数组中任一节点,你想知道它在完全二叉树中的parent,left,right,非常容易:
PARENT (i)
   return i/2

LEFT (i)
   return 2i

RIGHT (i)
   return 2i + 1
那么现在对于输入数组,已经抽象为完全二叉树了,那就要开始建堆,
先来学习一个重要的堆操作MAX-HEAPIFY 
MAX-HEAPIFY (A, i)
 1 l ← LEFT(i)
 2 r ← RIGHT(i)
 3 if l ≤ heap-size[A] and A[l] > A[i]
 4    then largest ← l
 5    else largest ← i
 6 if r ≤ heap-size[A] and A[r] > A[largest]
 7    then largest ← r
 8 if largest ≠ i
 9    then exchange A[i],A[largest]
10         MAX-HEAPIFY(A, largest)
这个函数就是对数组A中的第i个节点进行heapify操作
其实比较简单,1~7就是比较找出,i节点和左右子节点中,哪个最大
8~10,如果最大的不是i,那就把最大节点的和i节点交换,然后递归对从最大节点位置开始继续进行heapify
显而易见,对于n个节点的完全二叉树,高为lgn,对每个节点的heapify操作是常数级的,所以这个操作的时间复杂度就是lgn
那么有了heapify操作,建堆的算法很简单的,
BUILD-MAX-HEAP (A)
1  heap-size[A] ← length[A]
2  for i ← length[A]/2 downto 1
3       do MAX-HEAPIFY(A, i)

说白了,就是对i从length[A]/2到1的节点进行heapify操作。所以这个操作的时间复杂度上限咋一看应该是nlgn,其实比这个小的多,约等于2n,就是说建堆的时间复杂度是O(n),能够在线性时间内完成,这个是很高效的。
这个算法的依据是the elements in the subarray A[(n/2+1) ‥ n] are all leaves of the tree,所以我们只需要对所有非叶节点进行heapify操作就ok了
折腾半天堆建好了,怎么堆排序了,光从堆是得不到一个有序序列的。
HEAPSORT (A)
1 BUILD-MAX-HEAP(A)
2 for i ← length[A] downto 2
3    do exchange A[1],A[i]
4       heap-size[A] ← heap-size[A] - 1
5       MAX-HEAPIFY(A, 1)
原理很简单,从堆我们只能知道最大的那个,那么就把最大的那个去掉,然后heapify找到第二大的,依次下去。
实现也很巧妙,没有用到额外的存储空间,把堆顶放到堆尾,然后堆size-1
这个算法的时间复杂度也是nlgn

Python版


1 def heapSort(input):
2 output = []
3 buildHeap(input)
4 print input
5 while input:
6 i = len(input)-1
7 input[0],input[i] = input[i],input[0]
8 output.append(input.pop())
9 if input:
10 maxHeapify(input,0)
11 return output
12
13 def maxHeapify(input, i):
14 if i <0:
15 return
16 left = 2*i+1 # because the i from 0, not 1
17 right = 2*i+2
18 largest = i
19 length = len(input)
20 if left < length:
21 if input[i]< input[left]: largest = left
22 if right < length:
23 if input[largest]< input[right]: largest = right
24 if largest != i:
25 input[i], input[largest] = input[largest], input[i]
26 maxHeapify(input,largest)
27
28 def buildHeap(input):
29 length = len(input)
30 if length < 2: return
31 nonLeaf = length/2
32 for i in range(nonLeaf,-1,-1):
33 maxHeapify(input,i)

堆排序介绍完了,有什么应用
我看到的在搜索引擎在生成查询结果时,需要对N个候选集进行排序并取前r个作为查询结果,这时r<<N
这时用堆排序比较经济,首先生成堆,然后排序的时候只要做r次heapify,然后后面的就可以不管了,省了很多时间。

书上介绍的典型应用是Priority queues
说了堆排序是个非常好的排序算法,但是在实际应用中了还是输给了快排,所以别人都用快排了。但是heap这个数据结构的应用是很广的。
比如这个典型应用Priority queues
queue就是先进先出,那么Priority queues有了priority,复杂一点了,priority大的先出,这个可以用于比如cpu的task,job调度。
这个priority queue用堆实现就很合适了,下面就是定义了需要的一些操作,
HEAP-MAXIMUM (A)
1 return A[1]

HEAP-EXTRACT-MAX (A)
1 if heap-size[A] < 1
2   then error "heap underflow"
3 max ← A[1]
4 A[1] ← A[heap-size[A]]
5 heap-size[A] ← heap-size[A] - 1
6 MAX-HEAPIFY(A, 1)
7 return max

HEAP-INCREASE-KEY (A, i, key)
1 if key < A[i]
2   then error "new key is smaller than current key"
3 A[i] ← key
4 while i > 1 and A[PARENT(i)] < A[i]
5     do exchange A[i],A[PARENT(i)]
6         i ← PARENT(i)

MAX-HEAP-INSERT (A, key)
1 heap-size[A] ← heap-size[A] + 1
2 A[heap-size[A]] ← -∞
3 HEAP-INCREASE-KEY(A, heap-size[A], key)

本文章摘自博客园,原文发布日期:2011-07-04

时间: 2024-09-17 05:39:54

堆排序-算法导论的相关文章

【算法导论】排序 (二):堆排序

四.(1)堆排序 第一次听堆排序是在107lab听忠哥讲的,但是没讲怎么实现.那时刚看了数据结 构的二叉树,还以为要通过指针建立二叉树的方式来实现,觉得挺难的. 其实堆排序实现没有想象中 的那么难. "堆"这个词最初是在堆排序中提出来的,但后来就逐渐指"废料收集储存区",就像程 序设计语言Lisp和Java中所提供的设施那样.我们这里的堆数据结构不是废料收集存储区. 堆排序的 运行时间与归并排序一样为O(n lg n),  但是一种原地(in place)排序. (

【算法导论】排序(一)

虽然久闻大名,但第一次接触算法导论,是看了网易公开课MIT的<算法导论>课,记得 第一集就讲到了 插入排序和归并排序. 几个星期前也买了算法导论这本书,准备慢慢啃~ 这星期主要在看前两 部分,除了对于讲渐进时间.递归式分析这些东西感到云里雾里的,其它的都就 感觉越看越有觉得入 迷,果然不愧是一本经典之作 好吧,开始.本文主要是用C++把书中的算法实现,以及一些笔记. 一.插入排序. 插入算法的设计使用的是增量(incremental)方法:在排好子数组A[1..j- 1]后,将 元素A[ j]

【算法导论】排序算法总结

排序算法总结         从六月初开始看算法导论,陆陆续续看了有2个月了,但实际看的时间只有半个月左右.这期间都忙着找导师.期末考试,同时还回家修养了十来天.真正专心的看算法是在离家返校后,由于没有考试和作业的烦恼,天天都沉浸在算法中,感觉效率较高.这段时间学到的东西较多,下面来总结一下:         学到的排序算法可以分为两类:比较排序.非比较排序.(这些排序算法的详细介绍及c程序实现在本文末都给出了链接,欢迎参考与指正!)         比较排序有:插入排序法.合并排序法.堆排序法

理解二叉堆数据结构及Swift的堆排序算法实现示例_Swift

二叉堆的性质1.二叉堆是一颗完全二叉树,最后一层的叶子从左到右排列,其它的每一层都是满的 2.最小堆父结点小于等于其每一个子结点的键值,最大堆则相反 3.每个结点的左子树或者右子树都是一个二叉堆 下面是一个最小堆: 堆的存储通常堆是通过一维数组来实现的.在起始数组为 0 的情形中: 1.父节点i的左子节点在位置 (2*i+1); 2.父节点i的右子节点在位置 (2*i+2); 3.子节点i的父节点在位置 floor((i-1)/2); 维持堆的性质我们以最大堆来介绍(后续会分别给出最大堆和最小堆

C++编程:C++归并排序实现(算法导论)

  算法导论上的下标是从1开始的,但是为了和c++ STL的设计思想一致,所有函数的实现统一用左闭右开区间.中间修改了很多次,因为下标修改不是很容易就改掉的,需要始终维持循环不变式,稍微一个步骤出错就会使结果有些错误. #include #include #include #include using namespace std; void merge(int *A, int p, int q, int r) { //merge [p, r),左闭右开区间 int n1 = q - p, n2

看算法导论有感(1)——谈谈算法的五性对用户体验的影响

做程序的人,都知道了算法的5性--可行性,健壮性,有穷性,高效性,可读性. 这15个字谁都会说了,但是,你是否真正的思考过这个对当今程序界最最重要的用户体验的思考. 过去,我也没多做思考,但是,看了mit的算法导论公开课,我却是觉得一个好的算法, 确实严格遵从算法算法五性. ①可行性--算法原则上能够精确地运行,而且人们用笔和纸做有限次运算后即可完成.算法肯定要可行的,不可行的话,是一坨shit,一般可行性是与硬件息息相关.比如,10年前,你要先像现在的搜索引擎一样能够做视搜索,做各种各样的算法

算法导论 最大子数组数量问题

问题描述 算法导论 最大子数组数量问题 进行问题变换后,检查的子数组数量为什么会减少呢. 暴力求解方法有n(n-1)/2 种组合,进行问题变换后为什么有(n-1)(n-2)/2种组合呢,组合的数量应噶事保持不变的啊. 解决方案 http://www.cnblogs.com/chinaxmly/archive/2012/10/10/2718621.html 复杂度降低到O(n)

高效算法的常用技术(算法导论)

对于高效算法, 有些比较简单的技术, 如分治法, 随机化, 和递归求解技术. 这边介绍些更为复杂的技术, 动态规划, 贪心算法 当对于复杂问题设计算法时, 首先会想到使用分治法 来解决, 分而治之, 一个很有哲理性的思路, 再复杂的问题都可以不断分解到你可以轻松解决的粒度, 把所有简单问题都解决完后, 组合在一起就得到了复杂问题的解, 可以看出其中典型的递归求解的思路. 使用分治法的要求, 各个子问题是独立 的 (即不包含公共的子子问题,子问题不重叠 ). 如果子问题重叠, 用分治法就比较低效,

求问算法导论中一个非常简单的对数问题

问题描述 求问算法导论中一个非常简单的对数问题 求问算法导论中一个非常简单的对数问题.额,各位不要笑话啊. 请问这两个对数是如何推出相等的啊,用的是哪个公式啊? 只记得这个公式了.... 解决方案 解决方案二: begin{align} ln(3^{log_4^n}) & = ln(n^{log_4^3}) log_4^ncdot ln(3) & = log_4^3cdot ln(n) frac{ln(n)}{ln(4)}cdot ln(3) & = frac{ln(3)}{ln(

如何学算法导论 拜求方法

问题描述 我想看算法导论,但是拿着那么厚厚的一本书我就蒙了想问问高手都怎么看这本书的~~~~ 解决方案 解决方案二:慢慢看解决方案三:要学算法,先学好数据结构解决方案四:要参加ACM么?牛叉!解决方案五:看书┗━━━━━练习解决方案六:下学期就要学习数据结构和算法了--看了一小下下,感觉和DM有很多相似哦~~解决方案七:一边看一边练吧,如果要参加ACM的话要搞的很熟,一般情况的话把基本的数据结构和常用算法搞熟就行了,对将来编写高效率,高质量的代码有好处解决方案八:先学数据结构,然后一边看这本书,