[数据结构]图解堆结构及其代码实现

堆是一种图的树形结构,被用于实现“优先队列”(priority queues)

注:优先队列是一种数据结构,可以自由添加数据,但取出数据时要从最小值开始按顺序取出。在堆。在堆的树形结构中,各个顶点被称为“结点”(node),数据就存储在这些结点中。

图解:

        1.结点内的数字就是存储的数据。堆中的每个结点最多有两个子结点。树的形状取决于数据的个数。另外,结点的排列顺序为从上到下,同一行里则为从左到右。

       

        2.在堆中存储数据时必须遵守这样一条规则:子结点必定大于父结点。因此,最小值被存储在顶端的根结点中。往堆中添加数据时,为了遵守这条规则,一般会把新数据放在最下面一行靠左的位置。当最下面一行里没有多余空间时,就再往下另起一行,把数据加在这一行的最左端。

       

         3.我们试试往堆里添加数字5。按照02的说明寻找新数据的位置。该图中最下面一排空着一个位置,所以将数据加在此处。

        4.如果父结点大于子结点,则不符合上文提到的规则,因此需要交换父子结点的位置。这里由于父结点的6大于子结点的5,所以交换了这两个数字。重复这样的操作直到数据都符合规则,不再需要交换为止。

 

        5.现在,父结点的1小于子结点的5,父结点的数字更小,所以不再交换。

这样,往堆中添加数据的操作就完成了。

        6.从堆中取出数据时,取出的是最上面的数据。这样,堆中就能始终保持最上面的数据最小。

        7.由于最上面的数据被取出,因此堆的结构也需要重新调整。按照01中说明的排列顺序,将最后的数据(此处为6)移动到最顶端。

        8.如果子结点的数字小于父结点的,就将父结点与其左右两个子结点中较小的一个进行交换。

        

        9.这里由于父结点的6大于子结点(右)的5大于子结点(左)的3,所以将左边的子结点与父结点进行交换。

        现在,子结点(右)的8大于父结点的6大于子结点(左)的4,需要将左边的子结点与父结点进行交换。

        10.重复这个操作直到数据都符合规则,不再需要交换为止。

这样,从堆中取出数据的操作便完成了。

解说
        堆中最顶端的数据始终最小,所以无论数据量有多少,取出最小值的时间复杂度都为O(1)
        另外,因为取出数据后需要将最后的数据移到最顶端,然后一边比较它与子结点数据的大小,一边往下移动,所以取出数据需要的运行时间和树的高度成正比。假设数据量为n,根据堆的形状特点可知树的高度为log2n,那么重构树的时间复杂度便为O(logn)
        添加数据也一样。在堆的最后添加数据后,数据会一边比较它与父结点数据的大小,一边往上移动,直到满足堆的条件为止,所以添加数据需要的运行时间与树的高度成正比,也是O(logn)。

我们在此使用手动实现和Python中内置的模块heapq两种方式来实现堆

1.手动实现堆数据结构。在Python中,可以通过列表来实现堆,其中父节点和子节点之间的关系可以通过列表的索引来表示。

  • 索引为 i 的节点:
    • 父节点的索引为 (i-1)//2
    • 左子节点的索引为 2*i+1
    • 右子节点的索引为 2*i+2

通过这种方式,可以在列表中轻松地找到任意节点的父节点和子节点。

    注: 在算法实现中我们相比物理地址,更关心的其实是逻辑地址 (这里说的物理地址是列表,因为我们堆是用列表实现的, 下述的所有算法操作实际上都是列表操作)
    因此实际上我们可以在纸上画一个从1到9的堆结构,并添加好索引index, 从堆的角度(逻辑地址的角度)来看代码,更方便理解算法


class MinHeap:
    def __init__(self):
        self.heap = []  # 初始化一个空列表用于存储堆元素

    def heapify_up(self, index):
        # 向上调整堆,保持最小堆性质  while True表示向上递归调整直到遇到break(当前节点大于父节点)或index<=0(到根节点)
        while index > 0:
            parent_index = (index - 1) // 2  # 计算父节点索引
            if self.heap[index] < self.heap[parent_index]:  # 如果当前节点小于父节点,交换位置
                self.heap[index], self.heap[parent_index] = self.heap[parent_index], self.heap[index]
                index = parent_index
            else:
                break

    def heapify_down(self, index):
        # 向下调整堆,保持最小堆性质 while True表示递归调整直到遇到break(当前节点无子节点或者当前节点的值大于左右子节点的值)
        while True:
            left_child_index = 2 * index + 1  # 计算左子节点索引
            right_child_index = 2 * index + 2  # 计算右子节点索引
            min_index = index

            # 找到当前节点与其子节点中的最小值
            if left_child_index < len(self.heap) and self.heap[left_child_index] < self.heap[min_index]:
                min_index = left_child_index
            if right_child_index < len(self.heap) and self.heap[right_child_index] < self.heap[min_index]:
                min_index = right_child_index

            if min_index != index:
                # 如果需要交换节点位置,则进行交换并更新当前节点索引
                self.heap[index], self.heap[min_index] = self.heap[min_index], self.heap[index]
                index = min_index
            else:
                break

    def insert(self, value):
        # 插入新元素并调整堆,保持最小堆性质
        self.heap.append(value)
        self.heapify_up(len(self.heap) - 1)

    def extract_min(self):
        # 提取堆顶元素(最小值)并调整堆,保持最小堆性质
        if not self.heap:
            return None

        min_value = self.heap[0]
        self.heap[0] = self.heap[-1]  # 将最后一个元素移动到根节点
        self.heap.pop()  # 弹出最后一个元素
        self.heapify_down(0)

        return min_value

    def get_min(self):
        # 获取堆顶元素(最小值),但不删除
        if not self.heap:
            return None
        return self.heap[0]

    def size(self):
        # 获取堆的大小(元素个数)
        return len(self.heap)

    @classmethod
    def heapify(cls, lst):
        # 类方法:从已有列表建立最小堆
        heap = cls()  # 创建一个新的最小堆实例
        heap.heap = lst  # 将传入的列表作为堆的底层存储

        # 从最后一个非叶子节点开始到根节点,依次进行向下调整,保持最小堆性质
        for i in range(len(heap.heap)//2, -1, -1):
            heap.heapify_down(i)

        return heap




# 使用自定义的最小堆数据结构
min_heap = MinHeap()
min_heap.insert(3)
min_heap.insert(1)
min_heap.insert(2)
min_heap.insert(4)
min_heap.insert(5)

print(min_heap.size()) # 输出:5
print(min_heap.extract_min())  # 输出:1
print(min_heap.extract_min())  # 输出:2
print(min_heap.extract_min())  # 输出:3
print(min_heap.extract_min())  # 输出:4
    




# 从已有列表建立最小堆
lst = [7, 4, 5, 1, 2, 8, 3, 9]
new_heap = MinHeap.heapify(lst)
print(new_heap.size())# 输出:8
print(new_heap.extract_min())# 输出:1
print(new_heap.extract_min())# 输出:2
print(new_heap.size())# 输出:6

print(new_heap.get_min())  # 输出:3
print(new_heap.size())# 输出:6

2.内置的模块heapq实现

import heapq

# 将列表转换为堆
lst = [3, 1, 2]
heapq.heapify(lst)  # 转换为最小堆

# 插入元素到堆中
heapq.heappush(lst, 4)

# 弹出堆顶元素(最小值)
min_value = heapq.heappop(lst)

print(min_value)  # 输出:1
print(lst)  # 输出:[2, 4, 3]


文章来源:书籍《我的第一本算法书》

书籍链接:

我的第一本算法书 (豆瓣) (douban.com)

作者:宫崎修一 石田保辉

出版社:人民邮电出版社

ISBN:9787115495242

本篇文章仅用于学习和研究目的,不会用于任何商业用途。引用书籍《我的第一本算法书》的内容旨在分享知识和启发思考,尊重原著作者宫崎修一和石田保辉的知识产权。如有侵权或者版权纠纷,请及时联系作者。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值