数据结构--堆

news2024/11/18 22:37:25

一. 堆

1. 堆的概念

堆(heap):一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值。
堆在物理层面上,表现为一组连续的数组区间:long[] array ;将整个数组看作是堆。
堆在逻辑结构上,一般被视为是一颗完全二叉树。
满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆;反之,则是小堆,或者小根堆,或者最小堆。当一个堆为大堆时,它的每一棵子树都是大堆。
在这里插入图片描述

2. 堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储;
假设 i 为结点在数组中的下标,则有:
在这里插入图片描述
如果 i 为 0,则 i 表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2;
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子;
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子。

二. 堆的基本操作

1. 创建堆,向下调整与向上调整

创建堆只有两种堆可以创建,要不就是大根堆,要不就是小根堆。而要满足大根堆还是小根堆的逻辑,就要向下调整的操作才能实现。要想自己实现堆,堆本身就是一个数组,因此创建一个数组来创建堆。
对于集合 { 27,15,19,18,28,34,65,49,25,37 } 中的数据,如果将其创建成堆呢?
在这里插入图片描述
仔细观察上图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。 向下过程(以小堆为例):

  1. 让 parent 标记需要调整的节点,child 标记 parent 的左孩子(注意:parent 如果有孩子一定先是有左 孩子)
  2. 如果 parent 的左孩子存在,即: child < size, 进行以下操作,直到 parent 的左孩子不存在:
    • 看 parent 右孩子是否存在,存在找到左右孩子中最小的孩子,让 child 进行标
    • 将 parent 与较小的孩子 child 比较,如果:
    • parent 小于较小的孩子 child,调整结束;
    • 否则:交换 parent 与较小的孩子 child,交换完成之后,parent 中大的元素向下移动,可能导致子树不满足对的性质,因此需要 继续向下调整,即 parent = child;child = parent*2+1;然后继续 2

在这里插入图片描述

def sift(li, low, high):
    """
    建立大根堆
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low  # 最开始指向根节点
    j = 2 * i + 1  # 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    # 只要j位置有数
    while j <= high:
        # 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子大
        if j + 1 <= high and li[j + 1] > li[j]:
            j = j + 1  # 把j指向右孩子
        # 比较堆顶的tmp和j左右孩子大小比较
        if li[j] > tmp:  # 如果孩子比堆顶大
            li[i] = li[j]  # 把孩子大的换到上面父节点
            # 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置
            i = j
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上 结束循环
            li[i] = tmp  # 把tmp放到某一级领导位置上
            break
    # 越界了
    else:
        li[i] = tmp  # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上
def sift(li, low, high):
    """
    建立小根堆
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """

    i = low  # 最开始指向根节点
    j = 2 * i + 1  # 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    # 只要j位置有数
    while j <= high:
        # 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子小
        if j + 1 <= high and li[j + 1] < li[j]:
            j = j + 1  # 把j指向右孩子
        # 比较堆顶的tmp和j左右孩子大小比较
        if li[j] < tmp:  # 如果孩子比堆顶小
            li[i] = li[j]  # 把孩子大的换到上面父节点
            # 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置
            i = j
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上 结束循环
            li[i] = tmp  # 把tmp放到某一级领导位置上
            break
    # 越界了
    else:
        li[i] = tmp  # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上

建堆的时间复杂度是 O(n) ;向下调整的时间复杂度是 O(log(n))。

2. 堆的插入(offer)

堆的插入总共需要两个步骤:

  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
  2. 将最后新插入的节点向上调整,直到满足堆的性质 ;

在这里插入图片描述

3. 堆的删除(poll)

具体如下:( 注意:堆的删除一定删除的是堆顶元素。

  1. 将堆顶元素对堆中最后一个元素交换;
  2. 将堆中有效数据个数减少一个;
  3. 对堆顶元素进行向下调整;
    代码待补充…

三. 堆的应用

1. 堆排序(从小到大排)

一个数组根据从小到大排序,要创建大堆来排;一个数组根据从大到小排序,要创建小堆来排。
此处就以创建大堆为例。首先将堆顶的元素和堆中的最后一个元素交换,交换后再向下调整,调整后再与堆的倒数第二个元素进行交换。

def sift(li, low, high):
    """
    向下调整的一次过程
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """

    i = low  # 最开始指向根节点
    j = 2 * i + 1  # 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    # 只要j位置有数
    while j <= high:
        # 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子大
        if j + 1 <= high and li[j + 1] > li[j]:
            j = j + 1  # 把j指向右孩子
        # 比较堆顶的tmp和j左右孩子大小比较
        if li[j] > tmp:  # 如果孩子比堆顶大
            li[i] = li[j]  # 把孩子大的换到上面父节点
            # 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置
            i = j
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上 结束循环
            li[i] = tmp  # 把tmp放到某一级领导位置上
            break
    # 越界了
    else:
        li[i] = tmp  # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上


# 堆排序过程
def heap_sort(li):
    """
    1. 先建堆  从最后一个子堆开始,小堆到大堆 依次到根节点
    2. 向下调整 得到堆顶元素,为最大元素
    3. 挨个出数 堆顶最大元素和堆最后一个元素交换位置
    4. 重复2-3,直到堆变空
    :param li:待排序的列表
    :return:
    """
    print("开始建大根堆")
    # n 列表长度
    n = len(li)
    # 遍历范围 首先求列表最后一个父元素,最后一个小堆,最后一个子元素下标是n - 1,父下标((n-1)-1))//2,通过左右孩子公式都一样的结果
    # 最后一个父元素开始,最后-1步长是倒着遍历到列表最后一个元素 找到堆顶0(中间-1,步长负数,-1+1=0),倒序遍历
    for i in range((n - 2) // 2, -1, -1):
        sift(li, i, n - 1)
    # for循环结束,建堆完成了

    # 挨个出数
    for i in range(n - 1, -1, -1):  # 倒序 i从最后开始
        # i指向当前堆的最后一个元素
        li[0], li[i] = li[i], li[0]
        # 由于是倒序,挨个出数后,尾部有序区指针high,每次左移一位
        sift(li, 0, i - 1)  # i-1是新的high

li = [9, 6, 3, 5, 7, 2, 1, 8, 4]

print(li)
heap_sort(li)
print(li)
2. top-k问题

若要从N个数字中取得最小的K个数字,则需要创建大小为K的大堆来获取。若要从N个数字中取得最大的K个数字,则需要创建大小为K的小堆来获取。

def sift(li, low, high):
    """
    向上调整的一次过程
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """

    i = low  # 最开始指向根节点
    j = 2 * i + 1  # 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    # 只要j位置有数
    while j <= high:
        # 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子小
        if j + 1 <= high and li[j + 1] < li[j]:
            j = j + 1  # 把j指向右孩子
        # 比较堆顶的tmp和j左右孩子大小比较
        if li[j] < tmp:  # 如果孩子比堆顶小
            li[i] = li[j]  # 把孩子大的换到上面父节点
            # 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置
            i = j
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上 结束循环
            li[i] = tmp  # 把tmp放到某一级领导位置上
            break
    # 越界了
    else:
        li[i] = tmp  # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上

def topk(li, k):
    # 先取列表前k个元素
    heap = li[0:k]
    # 1. 建小根堆
    for i in range((k - 2) // 2, -1, -1):
        sift(heap, i, k - 1)

    print("*" * 80)
    print("小根堆heap建堆完成,", heap)
    print("*" * 80)

    # 2. 遍历 li列表里k后面剩下的元素
    for i in range(k, len(li)):
        # 依次拿k后面的值和小根堆 堆顶的值比较大小
        if li[i] > heap[0]:  # 如果值 大于 堆顶元素值
            heap[0] = li[i]  # 把大的值 放到堆顶
            sift(heap, 0, k - 1)
    # 3. 挨个出数
    for i in range(k - 1, -1, -1):  # 倒序 i从最后开始
        # i指向当前堆的最后一个元素
        heap[0], heap[i] = heap[i], heap[0]
        sift(heap, 0, i - 1)  # i-1是新的high
    return heap

li = [i for i in range(20)]
random.shuffle(li)

print(li)
print(topk(li, 10))
print(li)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode】29. 两数相除

1 问题 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.345 将被截断为 8 &#xff0c;-…

ubuntu 18.04 LTS交叉编译opencv 3.4.16并编译工程[全记录]

一、下载并解压opencv 3.4.16源码 https://opencv.org/releases/ 放到home路径下的Exe文件夹&#xff08;专门放用户安装的软件&#xff09;中&#xff0c;其中build是后期自建的 为了版本控制&#xff0c;保留了3.4.16&#xff0c;并增加了-gcc-arm 二、安装cmake和cmake-g…

nodejs基于vue网上考勤系统

本网上考勤系统是针对目前考勤的实际需求&#xff0c; 采用计算机系统来管理信息&#xff0c;取代人工管理模式&#xff0c;查询便利&#xff0c;信息准确率高&#xff0c;节省了开支&#xff0c;提高了工作的效率。 本网上考勤系统主要包括个人中心、员工请假管理、员工考勤管…

哈希表简介

基本介绍 思路分析 代码实现 # 哈希表 # 员工信息 class Emp:id: int 0name: str next Nonedef __init__(self, id, name):self.id idself.name namedef __str__(self):return f"id{self.id}, name{self.name}"class EmpLinkedList:# 头指针&#xff0c;指向第…

jenkins整合gerrit

背景 公司项目之前使用jenkins整合了gitlab&#xff0c;后面代码迁移到gerrit&#xff0c;所以需要修改jenkins配置。下面就简单的介绍一下jenkins如何整合gerrit。 环境 服务器&#xff1a;linux 环境&#xff1a;docker、jenkins 代码仓库&#xff1a;gerrit 前提 docke…

Flink的ResourceManager详解(一)

ResourceManager 总结 一、概述 1、ResourceManager 管理 Flink 集群中的计算资源&#xff0c;计算资源主要来自 TaskManager 组件。 2、如果集群采用 Native【本地模式】部署&#xff0c;则 ResourceManager 会动态地向集群资源管理器申请 Container 并启动TaskManager&…

depcheck检查项目依赖的安装情况-帮你解决各种项目运行灵异事件

depcheck检查项目缺失的依赖 depcheck介绍与安装介绍安装 depcheck使用基础使用注意 进阶使用 删除多余的依赖注意 depcheck介绍与安装 介绍 工作中&#xff0c;以下的场景恐怕大家都有经历过&#xff1a; 从代码仓库上面 clone 的项目&#xff0c;自己本地一运行就报错… 用…

Ruby和面向对象技术

Ruby和许多极为流行的编程语言都是面向对象的。多数的面向对象编程语言&#xff0c;每个对象都是一个样例或者既定类的实例以及独立对象的行为。 一、创建一个通用对象 创建一个通用对象 obj Object.new定义通用对象的行为 def obj.talk puts "I am an object"p…

MySQL远程连接

一、什么是mysq的远程连接? 1、本地连接 直接在本地使用mysqladmin命令登录 mysql -u root -p 解释如下: mysql:mysql 命令表示要启动 MySQL 客户端。-u root:-u 选项指定要使用的用户名。在这里,我们使用 root 用户名作为示例。-p:-p 选项需要用户输入密码。如果省…

Unexpected mutation of “dialogVisible“ prop.

问题记录&#xff1a; Vue2项目在封装element-ui的dialog组件时&#xff0c;eslint报错 Unexpected mutation of “dialogVisible” prop.eslintvue/no-mutating-props 大致意思是父组件传递过来的 dialogVisible 属性&#xff0c;不允许在子组件中修改父组件的值 解决方法&a…

qtabwidget 样式表

.QWidget{background-color: #ffffff; }/*设置TabWidget中QTabBar的样式*/ QTabWidget{background-color: #E6EBE8; } QTabBar::tab{background-color: #DEDEDE;font-family:Source Han Sans CN; /*设置tab中的文本的字体*/font-size:20pt;font-weight: normal;color:#3D3D3…

HCIP---BGP社团属性

文章目录 目录 文章目录 前言 一.BGP社团属性概述 公有社团属性 配置命令 前言 前文详细介绍了BGP的基础内容&#xff0c;本编将着重于BGP协议的另一个重点内容进行详解。 一.BGP社团属性概述 BGP社区属性是BGP路由协议中的一种特殊属性&#xff0c;可以用于指定一组AS号码&…

线程安全案例 --- 线程池

小王学习录 今日鸡汤什么是线程池为什么需要线程池1. 协程2. 线程池3. 什么是用户态和内核态使用线程池 --- java标准库1. 线程池的创建2. 工厂模式3. 线程池的使用ThreadPollExecutor类构造方法1. corePoolSize, maximumPoolSize 线程2. long keepAliveTime, TimeUnit unit 时…

paddlenlp:社交网络中多模态虚假媒体内容核查(特征篇)

初赛之特征构造 写在前面一、安装paddleOCR二、代码部分三、模型优缺点四、写在最后 写在前面 通过前面两篇文章的介绍&#xff0c;我们可以大致的知道模型用到的特征分为四块&#xff1a;qCap&#xff0c;qImg&#xff0c;captions&#xff0c;imgs。根据这些特征&#xff0c…

第一章 C语言程序设计Pro

考点一 C语言特点&#xff0c;C程序基本构成 数据类型丰富运算符丰富是一种中级语言&#xff08;高级&#xff09; //包含了中级和高级的特性数据类型检查不严格 //int和char通用 对下标的越界不报错 2021.下列属于计算机中高级语言的是&#xff08;…

嵌入式基础——哈弗结构

文章目录 1 什么是哈弗结构&#xff1f;2 哈弗结构单片机的特点3 什么是改进的(Enhanced)哈弗结构?4 写在最后 1 什么是哈弗结构&#xff1f; 哈佛结构是一种将程序指令存储和数据存储分开的存储器结构&#xff0c;如图所示&#xff1a; 哈佛结构是一种并行体系结构&#xf…

Consider using the `--user` option or check the permissions.

ERROR: Could not install packages due to an OSError: [WinError 5] 拒绝访问。: C:\\Users\\luckyli\\anaconda3\\envs\\CV\\Lib\\site-packages\\~orch\\lib\\asmjit.dll Consider using the --user option or check the permissions. 安装pytorch时遇到上述问题 通过以下…

Git Cherry Pick的使用

cherry-pick命令的基本用法 cherry-pick命令的基本语法如下&#xff1a; git cherry-pick <commit>其中&#xff0c;<commit>是要应用的提交的哈希值或分支名。该命令会将指定的提交应用到当前分支上&#xff0c;并创建一个新的提交。 使用场景 cherry-pick命令…

Jmeter-实现图片的上传和下载

图片上传 选中测试计划右键&#xff0c;添加->线程(用户)->线程组 配置线程组 上面分别是总次数&#xff0c;时间&#xff0c;循环次数&#xff0c;就是字面意思 选中你的线程组右键&#xff0c;添加->取样器->HTTP请求 配置HTTP请求 为了方便观看&#xff0c;这…

【C++基础】13. 结构体

文章目录 【 1. 结构体的定义 】【 2. 结构体成员的访问 】【 3. 结构体变量的声明 】【 4. 指向结构体的指针 】 数组与结构体&#xff1a;C/C 数组允许定义可存储相同类型数据项的变量。而结构体是 C 中另一种用户自定义的可用的数据类型&#xff0c;它允许我们存储不同类型的…