【数据结构与算法】堆

定义

堆是是一个完全二叉树,其中每个节点的值都大于等于或小于等于其子节点的值。这取决于是最大堆还是最小堆。

  • 小根堆:每个根都小于子节点。

  • 大根堆:每个根都大于子节点。

以下部分图例说明来源:【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆_哔哩哔哩_bilibili

image.png

基本操作

堆的上滤(heapify up)和下滤(heapify down)是用于维护堆的性质的两种重要操作,它们通常在插入和删除元素时被调用。

上滤(Heapify Up)

上滤是指将新插入的元素从底部向上移动,直到满足堆的性质为止。复杂度:O(logn)

当新元素被插入到堆的末尾时,它可能破坏了堆的性质,因为它可能比其父节点更大(对于最大堆)或更小(对于最小堆)。

上滤操作会持续比较新插入节点与其父节点的值,如果满足堆的性质,则停止;否则,交换它们的位置,然后继续向上比较,直到满足堆的性质。

上滤的步骤:

  1. 比较新插入节点与其父节点的值。
  2. 如果新插入节点的值比父节点的值大(对于最大堆)或小(对于最小堆),则交换它们的位置。
  3. 继续向上重复上述步骤,直到满足堆的性质或到达堆的根节点。

JavaScript 实现上滤的示例:

function heapifyUp(heap, index) {
    let parentIndex = Math.floor((index - 1) / 2);
    while (index > 0 && heap[index] > heap[parentIndex]) { // 最大堆的情况,若为最小堆则将判断条件改为 heap[index] < heap[parentIndex]
        [heap[index], heap[parentIndex]] = [heap[parentIndex], heap[index]]; // 交换节点值
        index = parentIndex;
        parentIndex = Math.floor((index - 1) / 2);
    }
}

下滤(Heapify Down)

下滤是指将堆顶元素从上向下移动,直到满足堆的性质为止。复杂度:O(logn)

当堆顶元素被删除或替换时,堆的性质可能被破坏,因为新的堆顶元素可能不再满足堆的性质。

下滤操作会持续比较堆顶元素与其子节点的值,如果满足堆的性质,则停止;否则,选择较大(对于最大堆)或较小(对于最小堆)的子节点进行交换,然后继续向下比较,直到满足堆的性质或到达堆的叶子节点。

下滤的步骤:

  1. 比较堆顶元素与其子节点的值,选择较大(对于最大堆)或较小(对于最小堆)的子节点。
  2. 如果堆顶元素比子节点的值小(对于最大堆)或大(对于最小堆),则交换它们的位置。
  3. 继续向下重复上述步骤,直到满足堆的性质或到达堆的叶子节点。

JavaScript 实现下滤的示例:

function heapifyDown(heap, index, size) {
    let largest = index;
    const left = 2 * index + 1;
    const right = 2 * index + 2;

    if (left < size && heap[left] > heap[largest]) { // 最大堆的情况,若为最小堆则将判断条件改为 heap[left] < heap[largest]
        largest = left;
    }

    if (right < size && heap[right] > heap[largest]) { // 最大堆的情况,若为最小堆则将判断条件改为 heap[right] < heap[largest]
        largest = right;
    }

    if (largest !== index) {
        [heap[index], heap[largest]] = [heap[largest], heap[index]]; // 交换节点值
        heapifyDown(heap, largest, size);
    }
}

在堆中,常见的基本操作包括:

  1. 插入节点: 将新节点插入到堆的末尾,然后通过一系列比较与交换操作,将它与其父节点比较并移动,直到满足堆的性质为止。

  2. 删除节点: 通常是删除堆顶元素。删除后,为了保持堆的性质,将堆的末尾元素移到堆顶,然后通过一系列比较与交换操作,将它与其子节点比较并移动,直到满足堆的性质为止。

  3. 堆化: 将一个无序的数组调整为堆的过程。分为从下往上的“自底向上堆化”和从上往下的“自顶向下堆化”。

建堆

建堆是将一个无序的数组转换成一个堆的过程。

自底向上建堆算法步骤:

  1. 从最后一个非叶子节点开始,逐个向上对每个节点进行下滤操作,确保以该节点为根的子树满足堆的性质。复杂度:O(n)。
  2. 重复步骤 1,直到根节点,即整个数组被转换成一个堆。

JavaScript 实现自底向上建堆的示例:

function heapifyDown(heap, index, size) {
    let largest = index;
    const left = 2 * index + 1;
    const right = 2 * index + 2;

    if (left < size && heap[left] > heap[largest]) { // 最大堆的情况,若为最小堆则将判断条件改为 heap[left] < heap[largest]
        largest = left;
    }

    if (right < size && heap[right] > heap[largest]) { // 最大堆的情况,若为最小堆则将判断条件改为 heap[right] < heap[largest]
        largest = right;
    }

    if (largest !== index) {
        [heap[index], heap[largest]] = [heap[largest], heap[index]]; // 交换节点值
        heapifyDown(heap, largest, size);
    }
}

function buildHeap(arr) {
    const n = arr.length;
    // 从最后一个非叶子节点开始,自底向上进行堆化
    for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
        heapifyDown(arr, i, n);
    }
}

let arr = [4, 10, 3, 5, 1];
buildHeap(arr);
console.log(arr); // 输出结果为 [10, 5, 3, 4, 1]

在上面的示例中,buildHeap 函数将数组 [4, 10, 3, 5, 1] 转换成了一个最大堆。它从最后一个非叶子节点(即节点 10)开始,逐个向上对每个节点进行下滤操作,直到根节点。最终,数组变成了 [10, 5, 3, 4, 1],满足了最大堆的性质。

自底向上建堆的时间复杂度为 O(n),其中 n 是数组的长度。这种方法通常比自顶向下建堆更高效,因为它减少了不必要的比较和交换次数。

自顶向下建堆算法步骤:

  1. 从数组的第一个元素开始遍历。复杂度:O(nlogn)。
  2. 将当前元素插入到堆中。
  3. 对插入后的堆进行上滤操作,确保堆的性质被维护。

JavaScript 实现自顶向下建堆的示例:

function heapifyUp(heap, index) {
    let parentIndex = Math.floor((index - 1) / 2);
    while (index > 0 && heap[index] > heap[parentIndex]) { // 最大堆的情况,若为最小堆则将判断条件改为 heap[index] < heap[parentIndex]
        [heap[index], heap[parentIndex]] = [heap[parentIndex], heap[index]]; // 交换节点值
        index = parentIndex;
        parentIndex = Math.floor((index - 1) / 2);
    }
}

function buildHeap(arr) {
    const n = arr.length;
    for (let i = 0; i < n; i++) {
        heapifyUp(arr, i);
    }
}

let arr = [4, 10, 3, 5, 1];
buildHeap(arr);
console.log(arr); // 输出结果为 [10, 5, 3, 4, 1]

在上面的示例中,buildHeap 函数将数组 [4, 10, 3, 5, 1] 转换成了一个最大堆。它遍历数组中的每个元素,并将每个元素插入到堆中,并调用 heapifyUp 函数来确保堆的性质。最终,数组变成了 [10, 5, 3, 4, 1],满足了最大堆的性质。

自顶向下建堆的时间复杂度为 O(nlogn),其中 n 是数组的长度。尽管这种方法不如自底向上建堆的效率高,但它仍然是一种有效的方法来构建堆。

堆排序

堆排序是一种利用堆的性质进行排序的算法。基本思路是先建立一个最大堆,然后逐步将堆顶元素与末尾元素交换,并将交换后的堆调整为最大堆,最终得到一个有序数组。以下是 JavaScript 实现:

function heapSort(arr) {
    // 建立最大堆
    heapify(arr);

    let n = arr.length;
    // 逐步将堆顶元素与末尾元素交换,并调整堆
    for (let i = n - 1; i > 0; i--) {
        // 将堆顶元素与末尾元素交换
        [arr[0], arr[i]] = [arr[i], arr[0]];
        // 调整堆
        heapifyDown(arr, 0, i);
    }
}

let arr = [4, 10, 3, 5, 1];
heapSort(arr);
console.log(arr); // 输出结果为 [1, 3, 4, 5, 10]

优先队列

优先队列是一种数据结构,支持插入和取出具有优先级的元素。在堆的基础上,我们可以实现一个最大堆来构建优先队列。JavaScript 中可以通过堆来实现优先队列。

class PriorityQueue {
    constructor() {
        this.heap = [];
    }

    // 插入元素
    insert(val) {
        this.heap.push(val);
        this.heapifyUp();
    }

    // 取出优先级最高的元素
    extractMax() {
        if (this.isEmpty()) {
            return null;
        }
        const max = this.heap[0];
        const last = this.heap.pop();
        if (this.heap.length > 0) {
            this.heap[0] = last;
            this.heapifyDown(0);
        }
        return max;
    }

    // 自底向上调整堆
    heapifyUp() {
        let i = this.heap.length - 1;
        while (i > 0) {
            const parent = Math.floor((i - 1) / 2);
            if (this.heap[parent] < this.heap[i]) {
                [this.heap[parent], this.heap[i]] = [this.heap[i], this.heap[parent]];
                i = parent;
            } else {
                break;
            }
        }
    }

    // 自顶向下调整堆
    heapifyDown(i) {
        const n = this.heap.length;
        let largest = i;
        const left = 2 * i + 1;
        const right = 2 * i + 2;
        if (left < n && this.heap[left] > this.heap[largest]) {
            largest = left;
        }
        if (right < n && this.heap[right] > this.heap[largest]) {
            largest = right;
        }
        if (largest !== i) {
            [this.heap[i], this.heap[largest]] = [this.heap[largest], this.heap[i]];
            this.heapifyDown(largest);
        }
    }

    // 判断优先队列是否为空
    isEmpty() {
        return this.heap.length === 0;
    }
}

// 示例


const pq = new PriorityQueue();
pq.insert(10);
pq.insert(5);
pq.insert(20);
console.log(pq.extractMax()); // 输出结果为 20
console.log(pq.extractMax()); // 输出结果为 10
console.log(pq.extractMax()); // 输出结果为 5

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 TensorFlow 和 Keras 构建 U-Net

原文地址&#xff1a;building-a-u-net-with-tensorflow-and-keras 2024 年 4 月 11 日 计算机视觉有几个子学科&#xff0c;图像分割就是其中之一。如果您要分割图像&#xff0c;则需要在像素级别决定图像中可见的内容&#xff08;执行分类时&#xff09;&#xff0c;或者从像…

模型 SOP(标准操作程序)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。标准化流程&#xff0c;提质增效&#xff0c;保障合规。 1 SOP的应用 1.1 餐厅日常卫生清洁标准操作程序&#xff08;SOP&#xff09; 下面展示一个餐厅如何通过SOP确保清洁工作的标准化&#xff0c…

202209青少年软件编程(Python) 等级考试试卷(一级)

第 1 题 【单选题】 表达式 len(“学史明理增信 , 读史终生受益”) > len(" reading history will benefit you ") 的结果是? ( ) A :0 B :True C :False D :1 正确答案:C 试题解析: 第 2 题 【单选题】 在 turtle 画图中, 常常使用 turtle.color(co…

【doghead】mac构建

先构建libuv libuv ✘ zhangbin@zhangbin-mbp-2  ~/tet/Fargo/zhb-bifrost/Bifrost-202403/worker/third_party/libuv/build   main  cmake .. -DBUILD_TESTING=ON -- The C compiler identification is AppleClang 12.0.5.12050022 -- Check for working C compiler: …

Git的基本操作和使用

git分支指令 列出所有本地分支 git branchmaster是绿的 前面有个 表示当前分支是master* 列出所有远程分支 git branch -r列出所有本地分支和远程分支 git branch -a新建一个分支&#xff0c;但依然停留在当前分支 git branch [branch-name]新建一个分支&#xff0c;并切…

【全网首出】npm run serve报错 Expression: thread_id_key != 0x7777

总结 困扰了一天&#xff01;&#xff01;&#xff01;一直以为是自己哪里配置错了&#xff0c; 结果最后发现是node.js官方的问题&#xff0c; Node.js v16.x版本的fibers.node被弃用 本文阅读大概&#xff1a;3min #npm run serve时就报错 #找了一天的文章&#xff0c;找不…

U盘到底要格式化成什么格式比较好?

前言 前段时间有小伙伴问我&#xff1a;U盘为啥无法粘贴超过4GB的压缩包。 相信这个问题很多人都会遇到&#xff0c;无论是压缩包、镜像文件还是电影&#xff0c;都会有超过4GB的时候。 如果文件超过了4GB&#xff0c;那么就会小伙伴遇到电脑提示&#xff1a;无法粘贴超过4G…

结构体介绍(1)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 结构体&#xff08;1&#xff09; 前言一、struct介绍结构体声明结构体创建和初始化struct 的特殊声明结构体自引用 二、结构体内存对齐2.1.对齐规则 总结 前言 结构体 属于…

npm install digital envelope routines::unsupported解决方法

目录 一、问题描述二、问题原因三、解决方法 一、问题描述 执行命令 npm install 报错&#xff1a;digital envelope routines::unsupported 二、问题原因 Node.js 17 版本引入了 OpenSSL 3.0&#xff0c;它在算法和密钥大小方面实施了更为严格的限制。这一变化导致 npm 的升…

✔ ★Java项目——设计一个消息队列(五)【虚拟主机设计】

虚拟主机设计 创建 VirtualHost实现构造⽅法和 getter创建交换机删除交换机创建队列删除队列创建绑定删除绑定发布消息 ★路由规则1) 实现 route ⽅法2) 实现 checkRoutingKeyValid3) 实现 checkBindingKeyValid4) 实现 routeTopic5) 匹配规则测试⽤例6) 测试 Router 订阅消息1…

idea 新建spring maven项目、ioc和依赖注入

文章目录 一、新建Spring-Maven项目二、在Spring-context使用IOC和依赖注入 一、新建Spring-Maven项目 在pom.xml文件中添加插件管理依赖 <build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.1</ver…

漏洞扫描神器:AppScan 保姆级教程(附破解步骤)

一、介绍 AppScan是IBM的一款应用程序安全测试工具&#xff0c;旨在帮助组织发现和修复应用程序中的安全漏洞。它提供了全面的功能和工具&#xff0c;用于自动化应用程序安全测试、漏洞扫描和漏洞管理。 以下是AppScan的一些主要特点和功能&#xff1a; 1. 自动化漏洞扫描&a…

中国市场,到底需要什么样的大模型?

“我是谁&#xff1f;”、“从哪里来&#xff1f;”、“要到哪里去&#xff1f;”。哲学史上&#xff0c;柏拉图提出的灵魂三问&#xff0c;是人们深刻、简明把握事物发展方向的思考路径。 当下&#xff0c;AI大模型热度比酷暑的热浪还高。但在众多大模型里&#xff0c;开一场…

【Unity Shader入门精要 第4章】数学基础(二)

1. Unity中的坐标空间 1.1 五个坐标空间 模型空间 模型自身的3D坐标系空间&#xff0c;左手坐标系是一个相对空间&#xff0c;坐标轴指向随模型旋转变化当物体有父节点时&#xff0c;Transform组件中各属性的值表示的即为该物体在其父物体的模型空间中的值当模型顶点传入顶点…

初始数据类型

注释补充 在我们编写任何代码的时候&#xff0c;都有一个叫做注释的功能 在golang中有两种 单行注释 // 如下图所示 加入了注释的话&#xff0c;代码在执行的时候会自动忽视这段内容 //fmt.Println("天上") //fmt.Println("天下") //fmt.Println("唯…

golang学习笔记(协程的基础知识)

golang的协程 协程是一种轻量级的线程&#xff0c;它可以实现并发执行的并行操作。协程是Go语言中的一个核心特性&#xff0c;它使得程序能够以并发的方式运行&#xff0c;并且非常高效。与传统的线程相比&#xff0c;协程的创建和销毁成本非常低&#xff0c;可以方便地启动大…

PS 2018

软件安装 文件太大&#xff0c;分批上传了&#xff0c;后续下载下来文件目录是这样的&#xff0c; 三个文件夹.7z 分批上传&#xff0c;exe也压缩分批上传&#xff0c; 其中products文件夹太大&#xff0c;里面子目录继续压缩分批上传 都下好了&#xff0c;就exe执行安装就行…

4.3 JavaScript变量

4.3.1 变量的声明 JavaScript是一种弱类型的脚本语言&#xff0c;无论是数字、文本还是其他内容&#xff0c;统一使用关键词var加上变量名称进行声明&#xff0c;其中关键词var来源于英文单词variable&#xff08;变量&#xff09;的前三个字母。 可以在声明变量的同时对其指定…

使用Python实现二维码生成工具

二维码的本质是什么&#xff1f; 二维码本质上&#xff0c;就是一段字符串。 我们可以把任意的字符串&#xff0c;制作成一个二维码图片。 生活中使用的二维码&#xff0c;更多的是一个 URL 网址。 需要用到的模块 先看一下Python标准库&#xff0c;貌似没有实现这个功能的…

Python实现获取网页内容及自动填表单与登录功能

这篇文章主要为大家详细介绍了如何利用Python实现模拟浏览器启动&#xff0c;获取网页内容、自动填表单、自动登录、自动过验证码等功能&#xff0c;需要的可以参考一下 库 源码 知识点补充 食用前准备 python 3.10.10 #二维码的库ddddocr 需要 库 import time import d…