0

0

javascript数组如何实现优先级队列

幻夢星雲

幻夢星雲

发布时间:2025-07-30 11:08:01

|

242人浏览过

|

来源于php中文网

原创

使用数组实现优先级队列的核心原因是其内存连续性和索引计算的直观性,能通过数学公式直接定位父子节点,提升缓存命中率并简化操作;2. 优先级队列常见于任务调度、图算法(如dijkstra和prim)、事件模拟、霍夫曼编码和网络数据包处理等需按重要性排序的场景;3. 处理相同优先级元素时,标准堆不保证顺序稳定性,若需稳定应引入序列号作为次要比较依据,在比较器中优先级相同时按插入顺序排序,从而实现稳定出队。

javascript数组如何实现优先级队列

JavaScript数组实现优先级队列,核心在于模拟堆(Heap)的数据结构特性,通过巧妙地管理数组元素的插入和删除,确保优先级最高的元素总能被快速访问。这就像我们把一个无序的列表,用一套规则整理成了一个半有序的树形结构,只不过这个“树”是扁平化地存储在数组里的。

javascript数组如何实现优先级队列

解决方案

class PriorityQueue {
    constructor(comparator = (a, b) => a[0] - b[0]) { // 默认比较器:基于元素的第一个值(优先级)
        this.heap = [];
        this.comparator = comparator; // 允许自定义比较函数
    }

    // 获取队列大小
    size() {
        return this.heap.length;
    }

    // 检查队列是否为空
    isEmpty() {
        return this.heap.length === 0;
    }

    // 查看优先级最高的元素(不移除)
    peek() {
        if (this.isEmpty()) {
            return undefined;
        }
        return this.heap[0];
    }

    // 插入元素
    enqueue(element) {
        this.heap.push(element);
        this._bubbleUp(); // 元素上浮以维护堆的性质
    }

    // 移除并返回优先级最高的元素
    dequeue() {
        if (this.isEmpty()) {
            return undefined;
        }
        const min = this.heap[0];
        const last = this.heap.pop();
        if (!this.isEmpty()) {
            this.heap[0] = last;
            this._sinkDown(); // 元素下沉以维护堆的性质
        }
        return min;
    }

    // 内部方法:元素上浮
    _bubbleUp() {
        let index = this.heap.length - 1;
        while (index > 0) {
            let parentIndex = Math.floor((index - 1) / 2);
            // 如果当前元素优先级高于父元素,则交换
            if (this.comparator(this.heap[index], this.heap[parentIndex]) < 0) {
                [this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];
                index = parentIndex;
            } else {
                break; // 堆性质已满足
            }
        }
    }

    // 内部方法:元素下沉
    _sinkDown() {
        let index = 0;
        const length = this.heap.length;
        const element = this.heap[0];

        while (true) {
            let leftChildIndex = 2 * index + 1;
            let rightChildIndex = 2 * index + 2;
            let swap = null; // 记录需要交换的子节点索引

            // 检查左子节点
            if (leftChildIndex < length) {
                if (this.comparator(this.heap[leftChildIndex], element) < 0) {
                    swap = leftChildIndex;
                }
            }

            // 检查右子节点
            if (rightChildIndex < length) {
                // 如果右子节点存在,且优先级比当前元素高
                // 并且(如果左子节点存在且优先级比右子节点低)或者(左子节点不存在)
                if (this.comparator(this.heap[rightChildIndex], element) < 0 &&
                    (swap === null || this.comparator(this.heap[rightChildIndex], this.heap[leftChildIndex]) < 0)) {
                    swap = rightChildIndex;
                }
            }

            if (swap === null) {
                break; // 没有更小的子节点,堆性质已满足
            }

            [this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]];
            index = swap;
        }
    }
}

// 示例用法:
// 创建一个最小优先级队列,元素可以是 [优先级, 值]
// const pq = new PriorityQueue();
// pq.enqueue([3, '任务C']);
// pq.enqueue([1, '任务A']);
// pq.enqueue([2, '任务B']);
// console.log(pq.dequeue()); // 输出 [1, '任务A']
// console.log(pq.peek());    // 输出 [2, '任务B']

// 创建一个最大优先级队列(通过修改比较器)
// const maxPQ = new PriorityQueue((a, b) => b[0] - a[0]);
// maxPQ.enqueue([3, '高优先级']);
// maxPQ.enqueue([1, '低优先级']);
// maxPQ.enqueue([2, '中优先级']);
// console.log(maxPQ.dequeue()); // 输出 [3, '高优先级']

为什么选择数组实现优先级队列,而不是其他数据结构?

说实话,用数组来实现优先级队列,也就是我们常说的“堆”,这是一种非常经典且高效的选择。它不像链表那样需要额外的指针开销,也不像红黑树那样在实现上复杂得让人头疼。数组的优点在于它的内存连续性索引计算的直观性

首先,内存连续性意味着更好的缓存局部性。当数据在内存中是连续存放的时候,CPU在读取一个元素时,很可能会把周围的元素也一并预读到缓存里,这对于频繁访问数据的情况来说,能显著提升性能。想象一下,如果你在书架上找书,书都挨着放,总比你每次都要跑去不同的房间找书要快得多。

立即学习Java免费学习笔记(深入)”;

javascript数组如何实现优先级队列

其次,索引计算的直观性是数组作为堆底层结构的杀手锏。在一个完全二叉树中,一个节点的父节点、左右子节点的位置,都可以通过简单的数学公式从当前节点的索引推算出来。比如,对于索引i的节点:

  • 它的父节点是 Math.floor((i - 1) / 2)
  • 它的左子节点是 2 * i + 1
  • 它的右子节点是 2 * i + 2 这种直接的映射关系,省去了维护复杂指针的麻烦,让插入(上浮)和删除(下沉)操作变得高效且易于理解。虽然在JavaScript这种高级语言里,我们不太需要直接关心内存分配,但这种基于数组的结构,其内在的逻辑美和性能优势依然是显而易见的。相比之下,如果用链表来构建堆,每次查找父子节点都需要遍历,效率会大打折扣。而平衡二叉搜索树(如AVL树、红黑树)虽然也能实现优先级队列,但其实现复杂度和维护成本远高于堆,对于仅仅需要“快速获取最大/最小元素”的场景来说,堆的O(log N)时间复杂度已足够优秀,且常数因子更小。

优先级队列在实际开发中有哪些常见的应用场景?

优先级队列这东西,在计算机科学里简直是无处不在,它解决的核心问题就是“谁先来,谁有更高权限”。在实际开发中,我个人觉得它用得最多的地方,大概就是那些需要调度优化的场景。

javascript数组如何实现优先级队列

一个很典型的例子是任务调度。比如操作系统里的进程调度,哪个进程CPU使用率高,哪个I/O密集型,它们可能需要不同的优先级。优先级队列就能确保高优先级的任务能先获得CPU时间片。游戏开发里也常用,比如AI寻路,或者粒子效果的渲染顺序,都可以用优先级队列来管理,确保关键的计算或视觉效果优先处理。

再比如图算法。著名的Dijkstra最短路径算法,或者Prim最小生成树算法,它们的核心逻辑都依赖于一个优先级队列来高效地选择下一个要访问的节点。每次都从队列中取出当前距离最短或权重最小的边,这正是优先级队列的拿手好戏。

唱鸭
唱鸭

音乐创作全流程的AI自动作曲工具,集 AI 辅助作词、AI 自动作曲、编曲、混音于一体

下载

还有一些不那么显眼但同样重要的应用,像事件模拟。在离散事件模拟系统中,所有待发生的事件都会被放入一个优先级队列,按照事件发生的时间点作为优先级排序,这样系统就能按时间顺序精确地处理事件。又比如数据压缩里的霍夫曼编码,构建霍夫曼树的过程就需要一个优先级队列来不断合并频率最低的两个节点。

甚至在一些网络数据包处理中,为了保证某些关键数据包(比如实时语音、视频流)的传输质量,也会用到优先级队列,确保它们能优先被发送。可以说,任何需要“按重要性顺序处理”的场景,优先级队列都是一个非常自然且高效的解决方案。

在实现过程中,如何处理相同优先级的元素?

处理相同优先级的元素,这确实是个很有意思的问题,也是实现一个健壮优先级队列时需要考虑的细节。我的经验是,标准的二叉堆(无论是最小堆还是最大堆)实现,通常并不保证相同优先级元素的相对顺序。这意味着,如果你插入了两个优先级都是5的元素A和B,你无法预知是A先被取出,还是B先被取出。这取决于它们在堆结构中的具体位置,以及在_bubbleUp_sinkDown过程中遇到的比较路径。

在很多场景下,这种不确定性是完全可以接受的。比如,你只是想找出当前最紧急的那个任务,至于有多个同样紧急的任务,哪个先执行都行。

但如果你的业务逻辑对相同优先级元素的稳定性有要求,也就是说,它们必须按照它们被插入时的顺序(先进先出)被取出,那么你就需要对优先级队列的实现做一些小小的修改。

一个常见的策略是引入一个“时间戳”或“序列号”作为次要优先级。当你插入一个元素时,除了它的主优先级,再给它附带一个递增的序列号。这样,当两个元素的主优先级相同时,你的比较器就会去比较它们的序列号,序列号小的(即插入时间更早的)优先级更高。

举个例子,你的元素不再是 [优先级, 值],而是 [优先级, 序列号, 值]。 你的比较器就需要这样调整:

class PriorityQueue {
    constructor(comparator = (a, b) => {
        // 优先比较主优先级
        if (a[0] !== b[0]) {
            return a[0] - b[0];
        }
        // 主优先级相同,比较序列号(次要优先级),确保稳定性
        return a[1] - b[1]; 
    }) {
        this.heap = [];
        this.comparator = comparator;
        this.insertionCount = 0; // 用于生成唯一的序列号
    }

    enqueue(element) {
        // 插入时附带序列号
        this.heap.push([element[0], this.insertionCount++, element[1]]); 
        this._bubbleUp();
    }

    // dequeue, peek 等方法需要注意返回的元素结构是 [优先级, 序列号, 值]
    // 外部使用时可能需要解构或只取值部分
    dequeue() {
        if (this.isEmpty()) {
            return undefined;
        }
        const minWithSerial = this.heap[0];
        const last = this.heap.pop();
        if (!this.isEmpty()) {
            this.heap[0] = last;
            this._sinkDown();
        }
        // 返回原始值,或者带序列号的完整元素
        return minWithSerial; 
    }
    // ... 其他方法保持不变
}

// 示例:
// const stablePQ = new PriorityQueue();
// stablePQ.enqueue([5, '任务A']); // 内部存储为 [5, 0, '任务A']
// stablePQ.enqueue([5, '任务B']); // 内部存储为 [5, 1, '任务B']
// stablePQ.enqueue([3, '任务C']); // 内部存储为 [3, 2, '任务C']
// console.log(stablePQ.dequeue()); // 输出 [3, 2, '任务C']
// console.log(stablePQ.dequeue()); // 输出 [5, 0, '任务A'] (因为序列号0比1小)
// console.log(stablePQ.dequeue()); // 输出 [5, 1, '任务B']

这种做法虽然会增加一点点存储开销,但对于需要稳定性的场景来说,是值得的。如果你的应用对性能要求极致,并且确定不需要稳定性,那么保持默认的不稳定行为会更简单高效。选择哪种方式,最终还是取决于具体的业务需求。

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

537

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

25

2026.01.06

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

395

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

575

2023.08.10

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

407

2023.08.14

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

9

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

107

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

13

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 4.9万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.9万人学习

Git 教程
Git 教程

共21课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号