python数据结构学习笔记-2016-11-30-01-堆

来源:互联网 发布:阿尔泰神族 知乎 编辑:程序博客网 时间:2024/05/10 19:14

        13.4 堆

        堆(heap)是一个完全二叉树,其结点组织是基于各自结点的数据域。它有两个重要的变种——最大堆和最小堆。

        最大堆(max-heap),具有称为堆序性质(heap order property)。对于其任意一个内结点而言,该结点处的值均大于其两个子结点的值。

        最小堆(min-heap),则具有相反的堆序性质,对于其任意一个内结点而言,该结点处的值均小于其两个子结点的值。

         13.4.1 定义

          插入操作

          向堆中插入新值,堆序性质和堆形性质(heap shape property,即保持其作为完全二叉树的性质)都要保持。

          例如分别向如下两个最大堆插入元素90和元素41。


          我们从堆的底部开始,先创建新结点,再将之添加到堆中,再与其父结点比较,若大于父结点,则两者互换,重复此过程,直至小于父结点为止。向上移动的过程就称为sift-up过程。


                提取

          从堆中提取元素,即从堆中删除元素。通常,我们是从最大堆中提取最大的元素,从最小堆中提取最小的元素。例如从如下最大堆中提取最大值。


        首先是复制根结点,其次是最底层最右边的元素,复制到根结点中,然后与其子结点比较,若小于子结点,则将其与较大的子结点互换,重复此过程,直至其到达叶结点或者碰到比其更小的结点为止。向下移动的过程称为sift-down。


        13.4.2 实现

        虽然堆本质是二叉树,但是几乎不用动态链式结构来实现,其原因是堆涉及到的向上移动和向下移动,即sift-up和sift-down过程。我们使用数组或者动态数组(python列表)来实现堆。

        访问结点

        由于堆是完全二叉树,其中不会有空洞致使内结点缺失。因此可以将根结点放入数组中的索引为0的位置,其两个子结点则分别放入索引为1和2的位置,依次类推。则对于数组中索引为i的位置储存的结点,有

        父结点的索引为 parent = (i - 1) / 2

        左子结点的索引为 left = 2 * i + 1

        右子结点的索引为right = 2 * i + 2

        判读一个结点是否有子结点,则可计算其假设左子结点和右子结点的索引值,若没有超出数组容量,则相应的子结点存在,若超出数组容量,则相应的子结点就不存在。

#-*-coding: utf-8-*-# 用数组实现最大堆class MaxHeap(object):    # 创建最大堆,使用最大容量为maxSize的数组    def __init__(self, maxSize):        self._elements = Array(maxSize)        self._count = 0 # 当前最大堆中的结点个数    # 最大堆中的结点数目    def __len__(self):        return self._count    # 最大堆的容量    def capacity(self):        return len(self._elements)    # 向最大堆中添加元素    def add(self, value):        assert self._count < self.capacity(), "Cannot add to a full heap."        self._elements[self._count] = value        self._count += 1        self._siftUp(self._count-1) # sift-up过程    # 从最大堆中提取最大元素,即根结点    def extract(self):        assert self._count > 0, "Cannot extract from an empty heap."        value = self._elements[0]        self._count -= 1        self._elements[0] = self._elements[self._count]        self._siftDown(0) # sift-down过程    # sift-up过程    def _siftUp(self, ndx):        if ndx > 0:            parent = ndx / 2            if self._elements[ndx] > self._elements[parent]:                self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]                self._siftUp(parent)    # sift-down过程    def _siftDown(self, ndx):        left = 2 * ndx + 1        right = 2 * ndx + 2        # 找出两子结点中的较大者        largest = ndx        if left < count and self._elements[left] >= self._elements[largest]:            largest = left        elif right < count and self._elements[right] >= self._elements[largest]:            largest = right        # 如果ndx结点的值小于两子结点中的较大者,即largest,则将两者互换        if largest != ndx:            self._elements[ndx], self._elements[largest] = self._elements[largest], self._elements[ndx]            self._siftDown(largest)


        分析
        在最坏情况下,向最大堆中添加元素,其需要O(log n)的时间,因为在sift-up过程中,结点可能从叶结点处,移至向上移动至根结点处,总共移动log n次,每一次移动所需时间为O(1),所以向最大堆中添加元素的时间复杂度为O(log n)。类似的,从最大堆中提取最大元素的时间复杂度也是O(log n)。

        13.4.3 优先级队列
        之前的无界优先级队列,也可以使用最小堆来实现。

          使用最小堆来实现的无界优先级最小堆,其出队和入队的时间复杂度均为O(log n)。
实现最坏情况最坏情况均摊成本均摊成本 入队出队入队出队python列表O(n)O(n)O(1)O(n)链表O(1)O(n)--最小堆(数组)O(log n)O(log n)--最小堆(python列表)O(n)O(n)O(log n)O(log n)

0 0
原创粉丝点击