堆排序
堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,其实很好理解。有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
或者说,堆排序将所有的待排序数据分为两部分,无序区和有序区。无序区也就是前面的最大堆数据,有序区是每次将堆顶元素放到最后排列而成的序列。每一次堆排序过程都是有序区元素个数增加,无序区元素个数减少的过程。当无序区元素个数为1时,堆排序就完成了。本质上讲,堆排序是一种选择排序,每次都选择堆中最大的元素进行排序。只不过堆排序选择元素的方法更为先进,时间复杂度更低,效率更高。
图例说明一下:(图片来自http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html)
具体步骤如下:
1 首先从第一个非叶子节点开始,比较当前节点和其孩子节点,将最大的元素放在当前节点,交换当前节点和最大节点元素。
2 将当前元素前面所有的元素都进行1的过程,这样就生成了最大堆
3 将堆顶元素和最后一个元素交换,列表长度减1。由此无序区减1,有序区加1
4 剩余元素重新调整建堆
5 继续3和4,直到所有元素都完成排序
堆的应用:优先队列
优先队列(priority queue)实际上也是由堆来实现,所以有最大优先队列和最小优先队列两种。优先队列的其中一个应用就是在共享计算机系统的作业调度。最大优先队列记录将要指定的各个作业及它们之间的相对优先级。当一个作业完成或者被中断后,调度器调用extract
从所有的等待作业中,选出具有最高优先级的作业来执行。在任何时候,调度器可以调用insert
把一个新作业加入到队列中来。
最小优先队列可以诶用于基于事件驱动的模拟器,队列中保存要模拟的事件,每个事件都有一个发生时间作为其关键字。事件必须按照发生的时间顺序模拟,而某一事件的模拟结果可能会触发其他事件。模拟程序调用extract
来选择下一个要模拟的事件,当一个新事件产生时,模拟程序调用insert
将其插入最小优先队列中。
class priQueue:
heap = []
last = 0
def __init__(self, num):
self.heap = num
self.last = len(self.heap) - 1
# print self.heap[1:]
def HeapAdjust(self, i, last):
minIndx = i
left = i * 2
right = i * 2 + 1
if i <= last / 2:
if left <= last and self.heap[minIndx] > self.heap[left]: # has left child
minIndx = left
if right <= last and self.heap[minIndx] > self.heap[right]: # has right child
minIndx = right
if i != minIndx:
self.swap(i, minIndx)
self.HeapAdjust(minIndx, last)
def HeapBuild(self):
i = self.last / 2
while i > 0:
self.swap(1, i)
self.HeapAdjust(i, self.last)
i = i - 1
def HeapSort(self):
i = self.last
while i > 1:
self.swap(1, i) # has order
# print self.heap[1:]
self.HeapAdjust(1, i - 1)
i = i - 1
def swap(self, i, j):
if i != j:
temp = self.heap[i]
self.heap[i] = self.heap[j]
self.heap[j] = temp
def insert(self, val):
self.heap.append(val)
self.last = self.last + 1
print "insert heap: ", self.heap[1:]
self.insertInto(val, self.last)
def insertInto(self, val, last):
i = last
p = i / 2
while p >= 1 and self.heap[i] < self.heap[p]:
self.swap(i, p)
i = p
p = i / 2
def extract(self):
self.swap(1, self.last)
res = self.heap.pop()
self.last = self.last - 1
self.HeapAdjust(1, self.last)
return res
if __name__ == "__main__":
Heap = [0, 49, 38, 65, 97, 76, 13, 27, 49]
print "orginal heap:",Heap[1:]
qq = priQueue(Heap)
qq.HeapBuild()
print "build heap:", Heap[1:]
print qq.extract()
print "extract heap:", Heap[1:]
qq.insert(13)
print "insert adjust heap:", Heap[1:]
qq.HeapSort()
print "heap sort: ", Heap[1:]
堆排序和快速排序的比较
尽管堆排序和快速排序都是 O(nlogn) O(nlogn)的排序算法,但这只是算法渐近的趋势,实际上它们的性能(主要表现在常数因子上)还是有一些区别的。下表给出了快速排序、堆排序和插入排序比较次数和交换次数的对比:(数据来自Comparing Quick and Heap Sorts)