Python - 深夜数据结构与算法之 Heap Binary Heap

news/2024/5/20 7:02:53 标签: 数据结构, 算法, , 二叉堆

目录

一.引言

二.与二叉介绍

1.Heap

2.Binary Heap 二叉

3.HeapifyUp 添加节点

4.HeapifyDown 删除节点

5.Heap 时间复杂度

6.Insert & Delete 代码实现

三.经典算法实战

1.Smallest-K [M14]

2.Sliding-Window-Max [239]

3.Ugly-Number [264]

4.Top-K-Freq-Ele [347]

四.总结


一.引言

前面介绍了树和二叉树的概念,接下来介绍 Heap 和 Binary Heap 二叉,这里是一个抽象的数据结构,二叉只是其实现的一种形式,并不代表所有都使用二叉树实现。

二.与二叉介绍

1.Heap

主要分为两种表现形式,最大 or 最小,也有叫大根 or 小根的,其可以在常数时间 o(1) 获取到最大 or 最小的元素,其一般包含 3 个基础 API:

◆ find-max - 寻找中的最大值

◆ delete-max - 删除中的最大值

◆ insert - 向中插入一个新元素

注意 📢 delete 和 insert 操作都会导致的结构破坏,所以需要重新调整父子节点位置从而维护的原始性质。

2.Binary Heap 二叉

二叉是基于二叉树实现的 Heap,不过有一点需要注意这里的二叉树是完全二叉树,假设其深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。也就是除了最后一层叶子 🍃 节点外,其余层的节点都是满的且没有 None。 同时二叉一般基于 List 实现,以下述列表为例:

[110, 100, 90, 40, 80, 20, 60, 10, 30, 50, 70]

完全二叉树可以看作是满二叉树从尾部叶子节点开始清退,即数组尾部清退,剩下的都是满足完全二叉树的,这样做主要是为了提高效率,如果过多节点为 None,二叉树的深度 h 增加,那么搜索的时间复杂度也会逐渐增加且浪费空间。 基于完全二叉树形态,其具备以下性质:

通过索引 i 我们可以获取对应索引的左右孩子节点和父节点的位置 [如果有的话],假设是 k 叉树的话,索引 i 的 k 个孩子索引为 k*i + 1、k*i + 2、k*i+3 以此类推,父节点索引则为 floor((i-1)/k)。 

3.HeapifyUp 添加节点

基于二叉 index 及其父子节点的关系,insert 操作首先将节点插入数组尾部,然后依次与父节点比较向上移动直到无法移动,增加元素结束。

◆ 插入 85 到 Heap

◆ 元素添加到队尾 

◆ 持续向上移动并交换

4.HeapifyDown 删除节点

删除元素首先将 root 节点移出,随后将尾节点替换至 root,与左右节点中较大的节点替换位置,如此循环下去,直到不能移动并恢复的性质。 

5.Heap 时间复杂度

再次重申下是一个抽象的结构,其有多种实现方式,就像是接口一样。这里最常见的实现方法为二叉,而随着实现方法的复杂,其 insert 和 delete 的复杂度也会更加友好。这里 Binaey Heap 的 find Min/Max 的时间复杂度为 o(1),其余时间复杂度为 o(logn)。

6.Insert & Delete 代码实现

◆ HeapifyUp

这里用了指针的方式,不断将较小的 Parent 节点与 Insert 节点互换,最终停止循环。

◆ HeapifyDown

增加元素只需要与 Parent 节点比较,但是删除节点需要和左右子节点比较,所以需要使用 maxChild 函数寻找更大的节点进行交换,这样才能满足得性质。 

三.经典算法实战

1.Smallest-K [M14]

最小的 K 个数: https://leetcode.cn/problems/smallest-k-lcci/

题目分析

既然是在的讲解下,所以我们可以直接使用 Heap,由于是最小的 k 个数,所以可以使用最小,每次返回顶元素并更新,执行 k 次即可。

小根

class Solution(object):
    def smallestK(self, arr, k):
        """
        :type arr: List[int]
        :type k: int
        :rtype: List[int]
        """

        heapq.heapify(arr)
        res = []
        for i in range(k):
            res.append(heapq.heappop(arr))
        return res

python 的 heapq 默认为最小,直接将元素通过 o(n) 复杂度构建一个 Heap,随后 pop k 次顶的元素即可。当然 heapq 提供 nsmallest 和 nlarggest (k, nums) 的函数可以直接获取前 k 个最小、最大的元素,其等价于 nums.sort()[:k]。

2.Sliding-Window-Max [239]

最大滑动窗口: https://leetcode.cn/problems/sliding-window-maximum/description/

题目分析

之前 ArrayList 部门我们通过 dequeue 双端队列实现了 k-window-max 的算法,一个技巧就是我们需要记录每个元素的索引,从而判断是否在窗口中。这里 python 默认的 heapq 为小根,通过 -1 * nums 转换为大根,随后遍历 k 窗口,取出顶最大值即可,注意在的维护过程中,排除索引 index 在 k-window 之外的元素。

大根

class Solution(object):
    def maxSlidingWindow(self, nums, k):

        n = len(nums)

        # 构造最大
        q = [(-nums[i], i) for i in range(k)]
        heapq.heapify(q)

        # 先获取前k个元素的 max
        ans = [-q[0][0]]

        for i in range(k, n):
            # 向添加新的元素
            heapq.heappush(q, (-nums[i], i))

            # 顶为窗口外元素则去除该元素
            while q[0][1] <= i - k:
                heapq.heappop(q)
            
            # pop 后仍然保持的结构,弹出顶最大值
            ans.append(-q[0][0])
        
        return ans

对于最大、最小 k 个数的题目,我们都可以想到通过 heap 来实现,工程环境下直接使用对应的库即可。

3.Ugly-Number [264]

丑数: https://leetcode.cn/problems/ugly-number-ii/description/

题目分析 

这个题目主要是理解比较困难,感觉是记忆性题目。1 是丑数,假设 x 是丑数,则 2x、3x、5x 也是丑数,所以我们依次加入中,第 n 次出队时,对应元素即为第 n 个丑数。

最小

#!/usr/bin/python
# -*- coding: UTF-8 -*-

class Solution(object):

    def nthUglyNumber(self, n):
        """
        :type n: int
        :rtype: int
        """

        nums = [2, 3, 5]

        # 去重
        appeared = {1}

        # 将初始化丑数放到队列里
        pq = [1]

        # 每次从队列取最小值,然后将对应数 2x 3x 5x 入队
        for i in range(1, n+1):
            x = heapq.heappop(pq)
            if i == n:
                return x
            for num in nums:
                t = num * x
                if t not in appeared:
                    appeared.add(t)
                    heapq.heappush(pq, t)
    
        return -1

三指针

class Solution(object):
    def nthUglyNumber(self, n):
        if n < 0:
            return 0
        dp = [1] * n
        # 维护3个指针
        index2, index3, index5 = 0, 0, 0
        for i in range(1, n):
            # 每次看哪个位置乘出来的丑数最小
            dp[i] = min(2 * dp[index2], 3 * dp[index3], 5 * dp[index5])
            # 用过的索引 += 1
            if dp[i] == 2 * dp[index2]: index2 += 1
            if dp[i] == 3 * dp[index3]: index3 += 1
            if dp[i] == 5 * dp[index5]: index5 += 1
        return dp[n - 1]

4.Top-K-Freq-Ele [347]

前 k 个高频元素: https://leetcode.cn/problems/top-k-frequent-elements/description/

题目分析 

这题找 k 个最大,还是使用即可,只不过需要预先做一次 word_count 统计词频,这里如果是 scala 可以直接用 tuple-2 再 sortBy(-_.2) 即可,python 的话就直接使用 heqpq.nlargest 即可。

最大 API

class Solution(object):
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """

        if k == 0:
            return []

        # 1.构造 word count 计数
        count = {}
        for i in nums:
            if i not in count:
                count[i] = 0
            count[i] += 1

        # 2.构造 Heap
        heap = [(val, key) for key, val in count.items()]

        # 3.取前 k 个元素
        return [item[1] for item in heapq.nlargest(k, heap)]

这里使用 nlargest 获取前 k 个元素,但是我们构造时使用了全部数据,下面我们自己维护 k 个元素实现 heap。

K-最大

class Solution(object):
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """

        if k == 0:
            return []

        # 1.构造 word count 计数
        count = {}
        for i in nums:
            if i not in count:
                count[i] = 0
            count[i] += 1

        # 2.构造 Heap
        re = []
        heapq.heapify(re)
        for key, val in count.items():
            # 添加元素
            heapq.heappush(re, (val, key))

            # 超过 k 就把顶最小的拿走,最后剩下 K 个最大的
            while len(re) > k:
                heapq.heappop(re)
        
        return [i[1] for i in re]

 因为我们只维护了 k 大小的,遍历数组是 o(n),而内排序是 o(logk),所以时间快一些。

四.总结

本文结合上一篇文章的二叉树介绍了的概念以及常用的的功能应用,取前 k 个最大 or 最小的问题适合用实现,其次常用的二叉我们应该记住父子节点之间的关系。最后是常用的实现代码,这里我们都是调用 heapq 库,如果想要自己了解可以参考下述链接: 

Heap Sort – Data Structures and Algorithms Tutorials


http://www.niftyadmin.cn/n/5284813.html

相关文章

c++11 标准模板(STL)(std::pair)(七)访问 pair 的一个元素

定义于头文件 <utility> std::pair 是一个结构体模板&#xff0c;其可于一个单元存储两个相异对象。 pair 是 std::tuple 的拥有两个元素的特殊情况。 访问 pair 的一个元素 std::get(std::pair) template< size_t I, class T1, class T2 > typename std::tuple…

随记-探究 B站上传流程

主要流程如下 预上传上传 开始上传分片上传上传完成&#xff09; 标签 分区预测获取标签 发布 preupload 预上传 url from urllib.parse import urlencode, quote, unquoteurl_query urlencode(url_query)lk f"https://member.bilibili.com/preupload" lk f&qu…

搜索百度百科官方创建入口,怎么创建更新公司的百度百科词条呢?

在百度搜索百度百科找到百度百科官方创建入口&#xff0c;可以上传并创建公司类的百度百科词条&#xff0c;创建词条后还可以再修改更新百科词条&#xff0c;最终完善好的百度百科词条将会在百度上获得大量曝光。那么百度百科可以怎么创建&#xff0c;下面洛希爱做百科网把十多…

【Android 13】使用Android Studio调试系统应用之Settings移植(五):ActionButtonsPreference和Utils

文章目录 一、篇头二、系列文章2.1 Android 13 系列文章2.2 Android 9 系列文章2.3 Android 11 系列文章三、剩余子模块的创建四、逐个完成AS移植五、ActionButtonsPreference5.1 依赖分析:ActionButtonsPreference的Android.bp5.2 依赖分析:Utils的Android.bp5.3 Utils编译错…

PyTorch官网demo解读——第一个神经网络(3)

上一篇&#xff1a;PyTorch官网demo解读——第一个神经网络&#xff08;2&#xff09;-CSDN博客 上一篇文章我们讲解了第一个神经网络的模型&#xff0c;这一篇我们来聊聊梯度下降。 大佬说梯度下降是深度学习的灵魂&#xff1b;梯度是损失函数&#xff08;代价函数&#xff…

[机器人-3]:开源MIT Min cheetah机械狗设计(三):嵌入式硬件设计

目录 概述&#xff1a; 1、硬件组成 2、通信速率 3、通信协议 4、mbedOS 概述&#xff1a; 以1条腿进行设计&#xff0c;其它腿也一样&#xff1a; 腿部硬件组成 1、硬件组成 1&#xff09;UP board计算机板卡&#xff08;Linux OS&#xff09;&#xff1a; 腿部控制器…

二叉树中的深搜

目录 &#x1f449;&#x1f3fb;计算布尔二叉树的值 &#x1f449;&#x1f3fb;计算布尔二叉树的值 原题链接&#xff1a;计算布尔二叉树的值 mycode: class Solution { public:bool evaluateTree(TreeNode* root) {if(root->leftnullptr){if(root->val0)return fal…

Mac 使用SecureCRT

SecureCRT是一款支持SSH&#xff08;SSH1和SSH2&#xff09;的终端仿真程序&#xff0c;同时支持Telnet和rlogin协议。 SecureCRT是一款用于连接运行包括Windows、UNIX和VMS的远程系统的理想工具。通过使用内含的VCP命令行程序可以进行加密文件的传输。有流行CRTTelnet客户机的…