数据结构-堆(heap)与堆的Python实现

版权声明:欢迎交流学习,转载请注明出处。 https://blog.csdn.net/qq_23869697/article/details/82735088

<Python 算法与数据结构视频教程> 学习笔记

1.什么是堆

数据结构-树介绍了什么是树,以及二叉树的实现。还记得树的三种特殊结构吗?完美二叉树,满二叉树和完全二叉树。这里介绍的堆结构就是一种完全二叉树。堆可分为最大堆和最小堆,区别就是父节点是否大于所有子节点。最大堆的父节点大于它的子节点,而最小堆中子节点大于父节点。看图有个清晰的认识:



2. 堆的表示

堆可以使用list实现,就是按照层序遍历顺序将每个节点上的值存放在数组中。父节点和子节点之间存在如下的关系:

parent = int((i-1) / 2)    # 取整
left = 2 * i + 1
right = 2 * i + 2

其中i表示数组中的索引,如果left、right的值超出了数组的索引,则表示这个节点是不存在的。



3.堆的操作

(1)往堆中插入值,sift-up操作:



往最大堆里添加一个元素,我们在使用数组实现的时候直接使用append()方法将值添加到数组的最后。这时候我们需要维持最大堆的特性,如下图。添加的新值90首先被放到堆的最后,然后与父节点的值作比较,如果比父节点值大,则交换位置。
这里涉及到的问题是子节点与父节点之间的关系。

# 堆中父节点i与子节点left、right的位置关系
parent = int((i-1) / 2)    # 取整
left = 2 * i + 1
right = 2 * i + 2

# 已知子节点的位置j,求父节点的位置
parent = int((j-1)/2)

使用递归的方式,向上比较,直到根节点。

(2)获取或删除根节点,sift-down操作;



当我们把最大或者最小的值从堆中弹出,为了维持堆的特性,要使用sift-down操作。因为最大堆、最小堆的最值都在根节点,当弹出并返回根节点的值后,为了维持堆的特性,我们先将最后一个位置上的值放到根节点中。然后比较它与它的两个子节点中三个值的大小,选择最大的值放到父节点上。同理,我们这里也是使用递归的方式向下比较。这里涉及到两个问题:

根据父节点确定子节点的位置:

	# 父节点的位置,left,rigth为左右子节点的位置
    left = 2 * ndx + 1
    right = 2 * ndx + 2

交换位置要满足几个条件条件,比如跟左子节点交换的条件:

  • 存在左子节点,
  • 左子节点大于右子节点,
  • 左子节点大于父节点

4. 堆的实现

使用Python实现堆结构,这里实现的是最大堆,里面用到了自己实现的数组,把它理解为list就行。
因为在看一个视频教程,复制粘贴了一下老师的代码【这里】。

最大堆

class Array(object):
    """
    Achieve an Array by Python list
    """
    def __init__(self, size = 32):
        self._size = size
        self._items = [None] * size

    def __getitem__(self, index):
        """
        Get items
        :param index: get a value by index
        :return: value
        """
        return self._items[index]

    def __setitem__(self, index, value):
        """
        set item
        :param index: giving a index you want to teset
        :param value: the value you want to set
        :return:
        """
        self._items[index] = value

    def __len__(self):
        """
        :return: the length of array
        """
        return self._size

    def clear(self, value=None):
        """
        clear the Array
        :param value: set all value to None
        :return: None
        """
        for i in range(self._size):
            self._items[i] = value

    def __iter__(self):
        for item in self._items:
            yield item
            
class MaxHeap(object):
    def __init__(self, maxsize=None):
        self.maxsize = maxsize
        self._elements = Array(maxsize)
        self._count = 0

    def __len__(self):
        return self._count

    def add(self, value):
        if self._count >= self.maxsize:
            raise Exception('full')
        self._elements[self._count] = value
        self._count += 1
        self._siftup(self._count-1)  # 维持堆的特性

    def _siftup(self, ndx):
        if ndx > 0:
            parent = int((ndx-1)/2)
            if self._elements[ndx] > self._elements[parent]:    # 如果插入的值大于 parent,一直交换
                self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]
                self._siftup(parent)    # 递归

    def extract(self):
        if self._count <= 0:
            raise Exception('empty')
        value = self._elements[0]    # 保存 root 值
        self._count -= 1
        self._elements[0] = self._elements[self._count]    # 最右下的节点放到root后siftDown
        self._siftdown(0)    # 维持堆特性
        return value

    def _siftdown(self, ndx):
        left = 2 * ndx + 1
        right = 2 * ndx + 2
        # determine which node contains the larger value
        largest = ndx
        if (left < self._count and     # 有左孩子
                self._elements[left] >= self._elements[largest] and
                self._elements[left] >= self._elements[right]):  # 原书这个地方没写实际上找的未必是largest
            largest = left
        elif right < self._count and self._elements[right] >= self._elements[largest]:
            largest = right
        if largest != ndx:
            self._elements[ndx], self._elements[largest] = self._elements[largest], self._elements[ndx]
            self._siftdown(largest)


def test_maxheap():
    import random
    n = 5
    h = MaxHeap(n)
    for i in range(n):
        h.add(i)
    for i in reversed(range(n)):
        assert i == h.extract()

最小堆

class Array(object):
    """
    Achieve an Array by Python list
    """
    def __init__(self, size = 32):
        self._size = size
        self._items = [None] * size

    def __getitem__(self, index):
        """
        Get items
        :param index: get a value by index
        :return: value
        """
        return self._items[index]

    def __setitem__(self, index, value):
        """
        set item
        :param index: giving a index you want to teset
        :param value: the value you want to set
        :return:
        """
        self._items[index] = value

    def __len__(self):
        """
        :return: the length of array
        """
        return self._size

    def clear(self, value=None):
        """
        clear the Array
        :param value: set all value to None
        :return: None
        """
        for i in range(self._size):
            self._items[i] = value

    def __iter__(self):
        for item in self._items:
            yield item


class MinHeap(object):
    """
    Achieve a minimum heap by Array
    """

    def __init__(self, maxsize = None):
        self.maxsize = maxsize
        self._elements = Array(maxsize)
        self._count = 0

    def __len__(self):
        return self._count

    def add(self, value):
        """
        Add an element to heap while keeping the attribute of heap unchanged.
        :param value: the value added to the heap
        :return: None
        """
        if self._count >= self.maxsize:
            raise Exception("The heap is full!")
        self._elements[self._count] = value
        self._count += 1
        self._siftup(self._count-1)

    def _siftup(self, index):
        """
        To keep the the attribute of heap unchanged while adding a new value.
        :param index: the index of value you want to swap
        :return: None
        """
        if index > 0:
            parent = int((index - 1) / 2)
            if self._elements[parent] > self._elements[index]:
                self._elements[parent], self._elements[index] = self._elements[index], self._elements[parent]
                self._siftup(parent)

    def extract(self):
        """
        pop and return the value of root
        :return: the value of root
        """
        if self._count <= 0:
            raise Exception('The heap is empty!')
        value = self._elements[0]
        self._count -= 1
        self._elements[0] = self._elements[self._count]
        self._siftdown(0)
        return value

    def _siftdown(self, index):
        """
        to keep the attribute of heap unchanged while pop out the root node.
        :param index: the index of value you want to swap
        :return: None
        """
        if index < self._count:
            left = 2 * index + 1
            right = 2 * index + 2
            if left < self._count and right < self._count \
                and self._elements[left] <= self._elements[right] \
                and self._elements[left] <= self._elements[index]:
                self._elements[left], self._elements[index] = self._elements[index], self._elements[left]
                self._siftdown(left)
            elif left < self._count and right < self._count \
                and self._elements[left] >= self._elements[right] \
                and self._elements[right] <= self._elements[index]:
                self._elements[right], self._elements[index] = self._elements[index], self._elements[right]
                self._siftdown(left)

            if left < self._count and right > self._count \
                and self._elements[left] <= self._elements[index]:
                self._elements[left], self._elements[index] = self._elements[index], self._elements[left]
                self._siftdown(left)

if __name__ == '__main__':
    import random
    n = 5
    h = MinHeap(n)
    for i in range(n):
        h.add(i)
    for i in range(n):
        assert i == h.extract()

5. Python中的heapq模块

这个模块提供了的堆是一个最小堆,索引值从0开始。而很多教材中都使用最大堆作为教学的例子,因为其排序是稳定的,而最小堆排序是不稳定的。
Python中创建一个堆可以直接使用list的创建方式H = [], 或者使用heapify()函数将一个存在的列表转为堆。

这个模块提供了下面几种堆的操作:
heapq.heappush(heap, item)
往堆中插入一个值,同时要保持为最小堆。

heapq.heappop(heap)
返回堆中的最小值,并把它从堆中删除,同时保持为最小堆;如果堆为空,发生 IndexError。直接通过heap[0]可以获取最小值并不从堆中把它删除。

heapq.heappushpop(heap, item)
向堆中插入值后再弹出堆中的最小值,这个函数的速度比直接使用heappush() 和heappop()的效率更加高。

heapq.heapreplace(heap, item)
弹出和返回堆中的最小值再插入一个新的值。堆的大小没有改变。如果堆为空,产生 IndexError。
这一个操作也比直接使用heappush() 和heappop()的效率更加高,尤其适合使用在固定堆大小不变的情况。
与上一个函数相比,这个函数返回的值可能要比将要插入到堆的值大。

heapq.heapify(x)
将一个list转为最小堆,线性时间复杂度,O(n).

6.堆排序

堆已经实现了,下面可以实现堆排序。


Python 算法与数据结构视频教程

猜你喜欢

转载自blog.csdn.net/qq_23869697/article/details/82735088