数据结构与算法python—13.堆及python实现与leetcode总结

本文详细介绍了优先队列的概念及其与普通队列的区别,探讨了堆的两种实现方式——基于链表的跳表和基于数组的二叉堆,重点讲解了二叉堆的实现细节,包括基本框架、添加元素、取出最大元素等操作。此外,还通过LeetCode题目展示了堆在解决动态求极值问题上的应用,如求第k小的值和数据流的中位数等。
摘要由CSDN通过智能技术生成

一、优先队列详解

  什么是优先队列?普通队列:先进先出;后进后出。而优先队列,顾名思义,优先队列:出队顺序和入队顺序无关;和优先级相关。优先队列与普通队列的区别在于出队顺序上。
  那为什么要使用优先队列呢?因为并不是所有任务都是先到先得的,而是会动态选择优先级最高的任务去执行。比如操作系统会动态选择优先级最高的任务去执行。
在这里插入图片描述

1.优先队列的实现

  优先队列与普通队列相比,只会在返回队首元素与出队方面有差别。优先队列可以使用普通线性结构来实现,比如数组与链表,此时出队需要扫描一遍线性结构,找到最大值,时间复杂度为 O ( n ) O(n) O(n),性能上不尽如人意。优先队列也可以使用顺序线性结构来实现,这里的顺序线性结构是指数据结构(数组或链表)本身维持着顺序,从大到小或者从小到大排列,此时出队将变得非常容易,时间复杂度为 O ( 1 ) O(1) O(1),而这种数据结构入队时,最差的情况时将整个数据结构扫描一遍才能找到插入位置,时间复杂度为 O ( n ) O(n) O(n)。这一章介绍的堆数据结构,入队与出队的时间复杂度均为 O ( l o g n ) O(logn) O(logn),这与二叉搜索树的时间复杂度不同,二叉搜索树的平均时间复杂度为 O ( l o g n ) O(logn) O(logn),所以堆数据结构是非常高效的

数据结构 入队 出队(拿出最大元素)
普通线性结构 O(1) O(n)
顺序线性结构 O(n) O(1)
O(logn) O(logn)

二、堆

  堆的核心内容:两种实现,一个中心,三个技巧。

1.堆的两种实现

  这里介绍两种常见的堆实现,一种是基于链表的实现-跳表,另一种是基于数组的实现-二叉堆。

1.1 基于链表的实现-跳表

  跳表的算法实现如果没有经过精雕细琢,性能很不稳定;而且当数据量增加时,跳表的内存占用会明显增加。所以跳表只讲述原理,不详细讲述代码实现。
  基于链表实现的堆中,链表是有序的。
在这里插入图片描述

比如:想在跳表中查找10
一级跳表中7指向节点7,下一个18指向节点18,因此,10在节点7与节点18之间,通过down指针回到原始链表中找到了节点7,节点7的next指针即为节点10.

在上例的基础上,如果数据量继续增大,那么索引层数也会继续增大1-level,2-level,...n-level,最终可以使链表实现二分查找,也就可以获得更好的效率,当然不可避免的增加了空间复杂度。
跳表的时间复杂度为索引的层数 * 平均每层索引遍历的个数,其中索引的层数为二分查找的时间复杂度 O ( l o g n ) O(logn) O(logn),而平均每层索引遍历的个数是个常数,因此跳表的时间复杂度为 O ( l o g n ) O(logn) O(logn)。空间复杂度等同于索引节点的总个数 O ( n ) O(n) O(n)
跳表的入堆与出堆操作:

  1. 入堆操作,只需要根据索引插到链表中,并更新索引
  2. 出堆操作,只需要删除头部(或者尾部),并更新索引

具体实现可以参考leetcode—1206. 设计跳表

1.2 基于数组的实现-二叉堆
  1. 二叉堆是一颗完全二叉树

  2. 二叉堆的性质:
    最大堆:堆中某个节点的值总是不大于其父节点的值(并不要求上一层的值都大于下一层的值)
    最小堆:堆中某个节点的值总是不小于其父节点的值
    在这里插入图片描述

  3. 二叉堆的实现,我们可以使用二叉搜索树的实现方式来实现,同样我们可以用数组的形式来实现完全二叉树。
    在这里插入图片描述
    现在的问题就变成:用数组形式实现完全二叉树时,应该怎么找到每一个父节点的左右孩子?
    很容易发现,在数组中,若父节点的索引为 n n n,则左孩子的索引为 2 n 2n 2n,右孩子的索引为 2 n + 1 2n+1 2n+1。若左或右孩子的索引为 n n n,则父节点的索引为 n 2 \frac{n}2 2n

    p a r e n t ( i ) = i / 2 parent(i) = i / 2 parent(i)=i/2
    l e f t c h i l d ( i ) = 2 ∗ i left child (i) = 2*i leftchild(i)=2i
    r i g h t c h i l d ( i ) = 2 ∗ i + 1 rightchild(i ) =2*i +1 rightchild(i)=2i+1

    若二叉搜索树的索引从0开始,则在这里插入图片描述
    在数组中,若父节点的索引为 n n n,则左孩子的索引为 2 n + 1 2n+1 2n+1,右孩子的索引为 2 n + 2 2n+2 2n+2。若左或右孩子的索引为 n n n,则父节点的索引为 n − 1 2 \frac{n-1}2 2n1

1.2.1 二叉堆的基本框架
# 创建最大堆
class MaxHeap:
    def __init__(self, arr=None, capacity=None):
        

        # 如果数组容量为空
        if not capacity:
            self._data = Array()
        # 如果容量不为空
        else:
            self._data = Array(capacity=capacity)
    
    # 判断堆尺寸
    def size(self):
        return self._data.get_size()
    
    # 判断堆是否为空
    def is_empty(self):
        return self._data.is_empty()

    # 返回完全二叉树数组表示中,一个索引所表示的元素的父亲节点的索引 (i - 1)// 2
    def _parent(self, index):
        if index == 0:
            raise ValueError('index-0 doesn\'t have parent.')
        return (index - 1) // 2

    # 返回完全二叉树数组表示中,一个索引所表示的元素的左孩子节点的索引 2 * i + 1
    def _left_child(self, index):
        return index * 2 + 1

    # 返回完全二叉树数组表示中,一个索引所表示的元素的右孩子节点的索引 2 * i + 2
    def _right_child(self, index):
        return index * 2 + 2
1.2.2 向堆中添加元素和ShiftUp(上浮)
	def add(self, e):
        # 将元素添加到末尾
        self._data.add_last(e)
        # 上浮以满足最大堆的性质
        # self._data.get_size() - 1为添加到末尾的元素的索引
        self._sift_up(self._data.get_size
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值