一、堆

1、堆是一棵完全二叉树,这棵二叉树需要满足堆序:任何分支结点(即除去叶结点所剩余的结点)的值都大于等于(或小于等于)其左右子结点的值

2、一般用列表来表示堆(Python中的列表下标从0开始),i结点的父结点位置为(i-1)//2(取整),i结点的左右子结点位置为2*i+1和2*i+2

3、如果堆序是小元素优先,则构造出来的称为‘小顶堆’(小元素在上);如果堆序是大元素优先,则构造出来的称为‘大顶堆’(大元素在上)。 以下的内容默认为小顶堆。


二、插入元素

向堆中插入元素需要用到‘向上筛选’来使得新插入的元素符合堆序。插入元素可以在O(log n)时间内完成

向上筛选大体步骤为:待插入元素首先插入到二叉树最下层最右边新的叶结点位置,然后,比较插入元素与其父结点的大小,如果插入元素值较小,则交换两个元素的位置,重复次操作,不断向上进行比较,直到某一父结点的元素值小于插入元素的值,或者插入元素已经到达根结点为止。   


三、弹出堆顶元素

弹出堆顶元素后,剩余的元素已经不再满足堆序。因此需要通过‘向下筛选’来使得剩余的元素满足堆序。其时间复杂度也为O(log n)。

向下筛选大体步骤为:在弹出堆顶元素后,将堆中的最后一个元素e放在堆顶;然后比较堆顶元素e(父结点)与其两个左右子结点的大小将三者中最小的元素放置在父结点位置。如果左子结点最小,则交换父结点与左子结点中的元素(这样e就被下移了一层),这个左子结点又有以其为父结点的子树,然后重复进行相同的比较和交换步骤,直到元素e在某次比较中是三者中最小的那个,则说明当前已经满足堆序,停止;或者元素e已经被下移到了最后一层,此时也满足堆序了,停止。


四、时间空间复杂度

基于堆实现的优先队列,在创建时需要O(n)的时间复杂度。插入和弹出的时间复杂度为O(log n)。此外,插入时第一步是在列表的最后加一个元素,可能导致列表替换元素存储区,此时就会出现O(n)的最糟糕情况。所有的操作都只用了一个简单变量,没有其他的结构,所以空间复杂度是O(1)。

以下是代码实现:

class HeapPriQueueError(ValueError):
    pass


class Heap_Pri_Queue(object):
    def __init__(self, elems=[]):
        self._elems = list(elems)
        if self._elems:
            self.buildheap()

    def is_empty(self):
        return self._elems is []

    def peek(self):   #取出堆顶元素,但不删除
        if self.is_empty():
            raise HeapPriQueueError("in pop")
        return self._elems[0]

    def enqueue(self, e):   #在末尾增加一个元素
        self._elems.append(e)  # 此时,总的元素的长度增加了1         self.siftup(e, len(self._elems) - 1)

    def siftup(self, e, last):  # 向上筛选
         elems, i, j = self._elems, last, (last - 1) // 2  # jlast位置的父结点
         while i > 0 and e < elems[j]:  # 如果需要插入的元素小于当前的父结点的值
             elems[i] = elems[j]  # 则将父结点的值下放到其子结点中去
             i, j = j, (j - 1) // 2  # 更新i为当前父结点的位置,j更新为当前父结点的父结点的位置
         elems[i] = e  # 如果i已经更新为0了,直接将e的值赋给位置0.或者需要插入的元素
                         # 大于当前父结点的值,则将其赋给当前父结点的子结点

     def dequeue(self):
        if self.is_empty():
            raise HeapPriQueueError("in pop")
        elems = self._elems
        e0 = elems[0]  # 根结点元素
         e = elems.pop()  # 将最后一个元素弹出,作为一个新的元素经过比较后找到插入的位置,以维持栈序
         if len(elems) > 0:
            self.siftdown(e, 0, len(elems))
        return e0

     def siftdown(self, e, begin, end):  # 向下筛选
         elems, i, j = self._elems, begin, begin * 2 + 1  # ji的左子结点
         while j < end:
            if j + 1 < end and elems[j] > elems[j + 1]:  # 如果左子结点大于右子结点
                j += 1  # 则将j指向右子结点,将j指向较小元素的位置
             if e < elems[j]:  # j已经指向两个子结点中较小的位置,
                break  # 如果插入元素e小于j位置的值,则为3者中最小的
             elems[i] = elems[j]  # 能执行到这一步的话,说明j位置元素是三者中最小的,则将其上移到父结点位置
             i, j = j, j * 2 + 1  # 更新i为被上移为父结点的原来的j的位置,更新j为更新后i位置的左子结点
         elems[i] = e  # 如果e已经是某个子树3者中最小的元素,则将其赋给这个子树的父结点
                        # 或者位置i已经更新到叶结点位置,则将e赋给这个叶结点。

      def buildheap(self):
        end = len(self._elems)
        for i in range(end // 2 - 1, -1, -1):  # 初始位置设置为end//2 - 1。 如果end=len(elems)-1,则初始位置为(end+1)//2-1.
            #            print(self._elems[i])
            self.siftdown(self._elems[i], i, end)


#            print(self._elems)


if __name__ == "__main__":
    temp = Heap_Pri_Queue([5, 6, 8, 1, 2, 4, 9])
    print(temp._elems)
    temp.dequeue()
    print(temp._elems)
    temp.enqueue(0)
    print(temp._elems)
    print(temp.peek())

五、初始化堆

下面演示如何将无序的列表元素构造成符合堆序的结构(小顶堆)。假设列表初始为[5,6,8,1,2,4,9]。

其初始的堆结构图为:

                                学习笔记-用堆实现优先队列(Python)

然后,从下面开始找到第一个非叶子结点,对其进行向下筛选;紧接着,对其余的非叶子结点也依次进行向下筛选,操作完成后,这时得到的结构就是堆序了。


                             学习笔记-用堆实现优先队列(Python)

                              学习笔记-用堆实现优先队列(Python)

                                  学习笔记-用堆实现优先队列(Python)

                                     学习笔记-用堆实现优先队列(Python)

最后得到的这个结构,已经都满足堆序了。


六、用Python中的heapq模块实现的简单的优先队列:

heapq模块的介绍:点击打开链接

from heapq import *
class Heap_Pri_Queue(object):
    def __init__(self, heap=[]):
        self.heap = heap
        heapify(self.heap)

    def enqueue(self, val):
        heappush(self.heap, val)

    def dequeue(self):
        return heappop(self.heap)

if __name__ == "__main__":
    lst = [5, 6, 8, 1]
    heap = Heap_Pri_Queue(lst)
    print(heap.dequeue()) #1
    heap.enqueue(3)
    print(heap.heap)  #[3, 5, 8, 6]

相关文章: