堆排序 及 优先队列

本文介绍了堆排序的原理,包括最大堆和最小堆的概念,详细阐述了堆排序的具体步骤,并探讨了堆在优先队列中的应用,如作业调度。此外,还对比了堆排序与快速排序的性能差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


 

堆排序

堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,其实很好理解。有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。

或者说,堆排序将所有的待排序数据分为两部分,无序区和有序区无序区也就是前面的最大堆数据,有序区是每次将堆顶元素放到最后排列而成的序列。每一次堆排序过程都是有序区元素个数增加,无序区元素个数减少的过程。当无序区元素个数为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(nlog⁡n)的排序算法,但这只是算法渐近的趋势,实际上它们的性能(主要表现在常数因子上)还是有一些区别的。下表给出了快速排序、堆排序和插入排序比较次数和交换次数的对比:(数据来自Comparing Quick and Heap Sorts


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值