【UCB CS 61B SP24】Lecture 21: Data Structures 5: Priority Queues and Heaps 学习笔记

news2025/3/5 19:12:16

本文介绍了优先队列与堆,分析了最小堆的插入与删除过程,并用 Java 实现了一个通用类型的最小堆。

1. 优先队列

1.1 介绍

优先队列是一种抽象数据类型,其元素按照优先级顺序被处理。不同于普通队列的先进先出(FIFO),优先队列每次取出优先级最高(或最低)的元素。

在 Java 中,PriorityQueue 是基于(Heap)实现的,默认使用自然排序(最小堆),也可以通过自定义 Comparator 调整优先级顺序:

package CS61B.Lecture21;

import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        PriorityQueue<Integer> Q = new PriorityQueue<>();  // 默认为小根堆
        PriorityQueue<Integer> RQ = new PriorityQueue<>(Collections.reverseOrder());  // 大根堆

        for (int i = 5; i > 0; i--)
            Q.add(i);  // 也可以用 offer() 方法
        System.out.println(Q);  // [1, 2, 4, 5, 3],直接输出不一定有序

        while (!Q.isEmpty())
            System.out.print(Q.poll() + " ");  // 也可以用 remove() 方法,输出 1 2 3 4 5
        System.out.println();

        RQ.addAll(List.of(8, 6, 7, 10, 9));

        while (RQ.peek() != null)
            System.out.print(RQ.remove() + " ");  // 10 9 8 7 6
        System.out.println();
    }
}

堆是一种完全二叉树,完全二叉树是指除了最后一层外,其他层的节点都必须是满的(所有可能的节点都存在),且最后一层的节点必须尽可能靠左排列。最后一层可以不满,但所有节点必须集中在左侧,中间不能有空缺。完全二叉树的高度为 ⌊ l o g n ⌋ + 1 \lfloor log n\rfloor + 1 logn+1,在相同节点数的二叉树中高度最小。

堆又分为最小堆和最大堆:

  • 最小堆:父节点的值 ≤ 子节点的值,堆顶元素为最小值;
  • 最大堆:父节点的值 ≥ 子节点的值,堆顶元素为最大值。

堆通常用数组实现,利用完全二叉树的性质能够简化父子节点索引计算,优先级最高的元素一定在堆顶,也就是索引为 0 的节点:

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

下图是一个最小堆的示例:

在这里插入图片描述

1.2 插入

我们以最小堆为例,插入步骤如下:

  • 将新元素插入到堆的末尾(数组的最后一个位置)。
  • 上浮(Swin)调整:从新元素的位置开始,与其父节点比较。
    • 如果新元素的值小于父节点,则交换两者的位置。
    • 重复此过程,直到新元素到达根节点,或满足父节点 ≤ 当前节点的条件。

我们用图片来展示一下最小堆的插入与上浮过程,首先在末尾插入节点 3,然后判断与其父节点的大小关系,小于父节点 5,因此与父节点交换位置,然后继续判断还是小于其父节点 5,和父节点交换,最后判断该节点大于等于其父节点 1,完成上浮调整:

在这里插入图片描述

1.3 删除

还是以最小堆为例,删除步骤如下:

  • 移除堆顶元素(最小值),将其返回。
  • 将堆的最后一个元素移动到堆顶(填补空缺,同时保持完全二叉树的状态)。
  • 下沉(Sink)调整:从堆顶开始,与左右子节点中的较小者比较。
    • 如果当前节点的值大于较小子节点,则交换两者的位置。
    • 重复此过程,直到当前节点到达叶子节点,或满足父节点 ≤ 任意子节点的条件。

同样用图片展示一下最小堆的删除与下沉过程,我们在前面的最小堆上删除堆顶元素(最小值),删除后先将最后一个元素 5 移动到堆顶,然后进行下沉调整,判断 5 大于较小子节点 1,因此与 1 进行交换,接着继续判断还是小于较小子节点 3,因此与 3 进行交换,交换后已经为叶子节点,完成下沉调整:

在这里插入图片描述

可以看到堆的上浮与下沉操作最坏情况下就是遍历树的高度,因此添加和删除操作的时间复杂度最坏情况下均为 O ( l o g n ) O(log n) O(logn)。优先队列与其他数据结构的时间复杂度对比如下:

数据结构插入查看最值删除最值空间复杂度适用场景
有序数组 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n ) O(n) O(n)静态数据、极少插入
平衡搜索树 O ( l o g n ) O(log n) O(logn) O ( l o g n ) O(log n) O(logn) O ( l o g n ) O(log n) O(logn) O ( n ) O(n) O(n)需范围查询或全局有序
哈希表 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n ) O(n) O(n) O ( n ) O(n) O(n)快速查找某个键、无顺序要求
二叉堆 O ( l o g n ) O(log n) O(logn) O ( 1 ) O(1) O(1) O ( l o g n ) O(log n) O(logn) O ( n ) O(n) O(n)动态数据、频繁插入和提取

2. Java 实现最小堆

相信看完上面的讲解也能感觉到堆的实现并不复杂,Java 实现最小堆代码如下:

package CS61B.Lecture21;

public class MinHeap<T extends Comparable<T>> {
    private T[] heap;
    private int size;
    private static final int DEFAULT_CAPACITY = 2;  // 默认容量

    public MinHeap() {
        this(DEFAULT_CAPACITY);
    }

    public MinHeap(int capacity) {
        heap = (T[]) new Comparable[capacity];
        size = 0;
    }

    /** 核心操作:添加 */
    public void add(T x) {
        if (size >= heap.length) resize();
        heap[size] = x;
        swim(size);  // 从末尾开始上浮
        size++;
    }

    /** 核心操作:删除并返回堆顶元素(最小值) */
    public T remove() {
        if (size == 0) return null;
        T root = heap[0];
        heap[0] = heap[--size];
        heap[size] = null;  // 清除引用,防止内存泄漏
        sink(0);  // 从根节点开始下沉
        return root;
    }

    /** 核心操作:返回堆顶元素(最小值) */
    public T peek() {
        return heap[0];
    }

    /** 获取大小 */
    public int size() {
        return size;
    }

    /** 是否为空 */
    public boolean isEmpty() {
        return size == 0;
    }

    /** 核心操作:上浮 */
    private void swim(int idx) {
        int parent = idx - 1 >> 1;
        while (parent >= 0 && heap[idx].compareTo(heap[parent]) < 0) {
            swap(idx, parent);
            idx = parent;
            parent = idx - 1 >> 1;
        }
    }

    /** 核心操作:下沉 */
    private void sink(int idx) {
        int left = (idx << 1) + 1;  // 左子节点的索引
        while (left < size) {  // 当存在子节点即当前节点还不是叶子节点时循环
            int right = left + 1;
            int minChild = left;  // 先假设最小的子节点为左子节点
            if (right < size && heap[right].compareTo(heap[left]) < 0) minChild = right;
            if (heap[idx].compareTo(heap[minChild]) <= 0) break;  // 如果已经小于等于最小子节点则完成下沉
            swap(idx, minChild);
            idx = minChild;
            left = (idx << 1) + 1;
        }
    }

    /** 将容量扩容至原来的两倍 */
    private void resize() {
        int newCapacity = heap.length * 2;
        T[] newHeap = (T[]) new Comparable[newCapacity];
        System.arraycopy(heap, 0, newHeap, 0, size);
        heap = newHeap;
    }

    /** 交换 heap 中两个位置的元素 */
    private void swap(int idx1, int idx2) {
        T temp = heap[idx1];
        heap[idx1] = heap[idx2];
        heap[idx2] = temp;
    }

    /** 测试 */
    public static void main(String[] args) {
        MinHeap<Integer> minHeap = new MinHeap<>();
        for (int i = 5; i > 0; i--) minHeap.add(i);
        while (!minHeap.isEmpty())
            System.out.print(minHeap.remove() + " ");
        System.out.println();
    }
}

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

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

相关文章

爬虫系列之发送请求与响应《一》

一、请求组成 1.1 请求方式&#xff1a;GET和POST请求 GET:从服务器获取&#xff0c;请求参数直接附在URL之后&#xff0c;便于查看和分享&#xff0c;常用于获取数据和查询操作 POST&#xff1a;用于向服务器提交数据&#xff0c;其参数不会显示在URL中&#xff0c;而是包含在…

小米手机如何录制屏幕?手机、电脑屏幕录制方法分享

大家最近有没有遇到想记录手机屏幕操作的情况&#xff1f; 比如精彩的游戏瞬间、有趣的视频教程&#xff0c;或者需要录制屏幕来制作演示材料。小米手机在这方面可是个好帮手&#xff0c;今天就来给你好好唠唠&#xff0c;小米手机如何录制屏幕&#xff0c;以及后续如何处理这…

3D Web轻量化引擎HOOPS Communicator的核心优势解析:高性能可视化与灵活部署!

在当今数字化时代&#xff0c;工业领域的工程应用不断向基于Web的方向发展&#xff0c;而HOOPS Web平台作为一款专为构建此类工程应用程序打造的软件开发套件集&#xff0c;正发挥着日益重要的作用&#xff0c;成为构建强大工程应用的基石。 一、HOOPS Web平台概述 HOOPS Web…

从 JVM 源码(HotSpot)看 synchronized 原理

大家好&#xff0c;我是此林。 不知道大家有没有这样一种感觉&#xff0c;网上对于一些 Java 框架和类的原理实现众说纷纭&#xff0c;看了总是不明白、不透彻。常常会想&#xff1a;真的是这样吗&#xff1f; 今天我们就从 HotSpot 源码级别去看 synchronized 的实现原理。全…

深入探索Python机器学习算法:模型调优

深入探索Python机器学习算法&#xff1a;模型调优 文章目录 深入探索Python机器学习算法&#xff1a;模型调优模型调优1. 超参数搜索方法1.1 网格搜索&#xff08;Grid Search&#xff09;1.2 随机搜索&#xff08;Random Search&#xff09;1.3 贝叶斯优化&#xff08;Bayesia…

postman请求后端接受List集合对象

后端集合 post请求&#xff0c;即前端请求方式

Kimi“撞车”DeepSeek!新一代注意力机制的极限突破!

近期&#xff0c;各方大佬在注意力机制上又“打起来了”。首先登场的是顶流DeepSeek&#xff0c;新论文梁文锋署名&#xff0c;提出了一种新的注意力机制NSA。同天&#xff0c;Kimi杨植麟署名的新注意力架构MoBA开源。紧接着&#xff0c;华为诺亚提出高效选择注意力架构ESA。 …

计算机网络---SYN Blood(洪泛攻击)

文章目录 三次握手过程SYN Flood攻击原理防御措施协议层优化网络层拦截系统配置调整 TCP协议是 TCP/IP 协议栈中一个重要的协议&#xff0c;平时我们使用的浏览器&#xff0c;APP等大多使用 TCP 协议通讯的&#xff0c;可见 TCP 协议在网络中扮演的角色是多么的重要。 TCP 协议…

Ollama存在安全风险的情况通报及解决方案

据清华大学网络空间测绘联合研究中心分析&#xff0c;开源跨平台大模型工具Ollama默认配置存在未授权访问与模型窃取等安全隐患。鉴于目前DeepSeek等大模型的研究部署和应用非常广泛&#xff0c;多数用户使用Ollama私有化部署且未修改默认配置&#xff0c;存在数据泄露、算力盗…

健康医疗大数据——医疗影像

一、 项目概述 1.1 项目概述 1.2 项目框架 1.3 项目环境 1.4 项目需求 二、项目调试与运行 2.1需求分析 2.2具体实现 三、项目总结 项目概述 项目概述 本项目旨在应用大数据技术于医疗影像领域&#xff0c;通过实训培养团队成员对医疗大数据处理和分析的实际…

学生管理信息系统的需求分析与设计

伴随教育的迅猛演进以及学生规模的不断扩增&#xff0c;学生管理信息系统已然成为学校管理的关键利器。此系统能够助力学校管控学生的课程成绩、考勤记载、个人资讯等诸多数据&#xff0c;提升学校的管理效能与服务品质。 一.需求分析 1.1 学生信息管理 学生信息在学校管理体…

基于微信小程序的停车场管理系统的设计与实现

第1章 绪论 1.1 课题背景 随着移动互联形式的不断发展&#xff0c;各行各业都在摸索移动互联对本行业的改变&#xff0c;不断的尝试开发出适合于本行业或者本公司的APP。但是这样一来用户的手机上就需要安装各种软件&#xff0c;但是APP作为一个只为某个公司服务的一个软件&a…

前端小案例——520表白信封

前言&#xff1a;我们在学习完了HTML和CSS之后&#xff0c;就会想着使用这两个东西去做一些小案例&#xff0c;不过又没有什么好的案例让我们去练手&#xff0c;本篇文章就提供里一个案例——520表白信封 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主…

【最后203篇系列】010 关于矩阵的一点思考

说明 今天拿起一本矩阵的书又翻了翻&#xff0c;毕竟AI搞到最后还得是数学。 我是感觉自己高数始终有点学的迷迷糊糊的&#xff0c;就打算这一年慢慢把矩阵部分扫一遍&#xff0c;毕竟这快肯定是实打实有用的。其他高级部分就等我发财之后再说了&#xff0c;哈哈。 内容 今…

Python快捷手册

Python快捷手册 后续会陆续更新Python对应的依赖或者工具使用方法 文章目录 Python快捷手册[toc]1-依赖1-词云小工具2-图片添加文字3-BeautifulSoup网络爬虫4-Tkinter界面绘制5-PDF转Word 2-开发1-多线程和队列 3-运维1-Requirement依赖2-波尔实验室3-Anaconda3使用教程4-CentO…

DeepSeek崛起:如何在云端快速部署你的专属AI助手

在2025年春节的科技盛宴上&#xff0c;DeepSeek因其在AI领域的卓越表现成为焦点&#xff0c;其开源的推理模型DeepSeek-R1擅长处理多种复杂任务&#xff0c;支持多语言处理&#xff0c;并通过搜索引擎获取实时信息。DeepSeek因其先进的自然语言处理技术、广泛的知识库和高性价比…

Linux的用户与权限--第二天

认知root用户&#xff08;超级管理员&#xff09; root用户用于最大的系统操作权限 普通用户的权限&#xff0c;一般在HOME目录内部不受限制 su与exit命令 su命令&#xff1a; su [-] 用户名 -符号是可选的&#xff0c;表示切换用户后加载环境变量 参数为用户名&#xff0c…

Zookeeper 及 基于ZooKeeper实现的分布式锁

1 ZooKeeper 1.1 ZooKeeper 介绍 ZooKeeper是一个开源的分布式协调服务&#xff0c;它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高效可靠的原语集&#xff0c;并以一系列简单易用的接口提供给用户使用。 原语&#xff1a;操作系统或…

Ubuntu20.04双系统安装及软件安装(五):VSCode

Ubuntu20.04双系统安装及软件安装&#xff08;五&#xff09;&#xff1a;VSCode 打开VScode官网&#xff0c;点击中间左侧的deb文件下载&#xff1a; 系统会弹出下载框&#xff0c;确定即可。 在文件夹的**“下载”目录**&#xff0c;可看到下载的安装包&#xff0c;在该目录下…

【计算机网络入门】初学计算机网络(十一)重要

目录 1. CIDR无分类编址 1.1 CIDR的子网划分 1.1.1 定长子网划分 1.1.2 变长子网划分 2. 路由聚合 2.1 最长前缀匹配原则 3. 网络地址转换NAT 3.1 端口号 3.2 IP地址不够用&#xff1f; 3.3 公网IP和内网IP 3.4 NAT作用 4. ARP协议 4.1 如何利用IP地址找到MAC地址…