js使用小顶堆构建优先级队列

news2025/1/15 13:03:54

什么是优先级队列?

优先级队列是队列的一个变种,队列是一个先进先出的结构,在头部出队元素在尾部入队元素,

优先级队列顾名思义就是给每个元素具备了优先级,优先级决定了元素在队列中的存储位置,优先级越高的越靠前越先出队

小顶堆又是什么?

小顶堆是堆结构的一个分支,堆分为大顶堆和小顶堆,一般数组实现就是由一个序列组成的二叉树,每个叶子节点都比子节点要大/小,最小值/最大值就是头部元素,所以堆很适合获取最值

堆的常见操作:

上浮(siftUp): 构建堆的一种方式,从堆的尾部开始挨个将节点和父节点比较,如果比父节点大/小则交换位置,重复这个过程直到符合堆的性质(父节点大于/小于子节点)后停止

下沉(siftDown):构建堆的另一种方式,从根节点开始每个节点和左右子节点中较小/大的一个交换位置,重复这个过程直到满足堆的性质时停止

添加节点(push):将节点添加到堆的尾部,然后上浮重新构建堆直到满足堆性质

删除节点(pop):将堆的最后一个节点移除掉覆盖根节点然后下沉重新构建堆直到满足堆性质并返回根节点

小顶堆实现优先级队列:

function defaultCompares(a, b) {
        return a - b
      }
      // 小顶堆构建优先级队列
      class PriorityQueue {
        constructor(arr = [], compares = defaultCompares) {
          if (compares) {
            this.compareFn = compares
          }
          this.queue = arr
          this.init()
        }

        // 构建小顶堆
        init() {
          // 从倒数第一个非叶子节点(从下往上,从右往左进行构建) 完成每一棵叶子结点子树的递归下沉 直到根节点停止 依次重新排列节点顺序
          let lastParentNodeIndex = (this.queue.length >>> 1) - 1
          for (let i = lastParentNodeIndex; i >= 0; i--) {
            this.buildChildNodes(i) // 递归每个非叶子结点
          }
        }
        // 指定索引处开始下沉
        buildChildNodes(index) {
          // 在数组中某个节点的左右子节点分别为 2*i+1 和 2*i+2
          let arr = this.queue
          let left = (index << 1) + 1
          let right = (index << 1) + 2
          let minIndex = index // 记录较小值
          let length = arr.length // 获取队列的长度
          // 找出值较小节点下标
          if (left < length && this.compares(left, minIndex) < 0) {
            minIndex = left
          }

          if (right < length && this.compares(right, minIndex) < 0) {
            minIndex = right
          }
          // 如果不相等则产生交换
          if (minIndex != index) {
            // 如果最小值不是父节点的话,需要交换父子节点位置
            // 并且对父节点交换后的子树递归地进行重新排列
            this.swap(index, minIndex)
            this.buildChildNodes(minIndex)
          }
        }
        compares(a, b) {
          return this.compareFn(this.queue[a], this.queue[b])
        }
        peak() {
          return this.queue[0] // 获取队列的头部
        }

        push(val) {
          // 添加一个元素然后重新构建堆
          this.queue.push(val)
          this.init()
        }

        pop() {
          // pop操作,就是将根节点拿出后,用最后一个节点填充根节点然后重新构建
          let val = this.queue[0] // 这样重新构建前的操作就是O(1) 如果直接把根节点shift出去再重构建则,重新构建前的操作就是O(n) shift操作会将整个数组前移一位 相当于访问元素的次数是O(n) 下标访问就是O(1)
          this.queue[0] = this.queue.pop() // pop出当前最小值
          this.init() // 重新构建堆 维护最新的最小值
          return val
        }
        // 排序
        sort() {
          // 排序的话就是把队列一个个pop出来
          var res = []
          var length = this.queue.length
          for (var i = 0; i < length; i++) {
            res.push(this.pop()) // 每次都会得到一个最小值
          }
          this.queue = res // 排好序的数组更新回队列
          return res
        }
        // 交换两个索引位置的值
        swap(index1, index2) {
          var arr = this.queue
          ;[arr[index1], arr[index2]] = [arr[index2], arr[index1]]
        }
        size() {
          return this.queue.length
        }
      }

对于init方法的图解:

 为啥是从最后一个非叶子结点开始下沉,其实是对每个非叶子节点及其子树的一个递归下沉的过程,从下往上从右至左直到根节点,这样所有的叶子节点及其子树都是满足堆性质的二叉树

代入前k个高频数字题测试

      function topKFrequent(nums = [], k) {
        const map = new Map()
        for (const item of nums) {
          map.set(item, (map.get(item) || 0) + 1)
        }
        const minHeap = new PriorityQueue([], (a, b) => a[1] - b[1])
        for (const entry of map.entries()) {
          minHeap.push(entry)
          if (minHeap.size() > k) {
            minHeap.pop()
          }
        }
        const result = []
        for (let i = minHeap.size() - 1; i >= 0; i--) {
          result[i] = minHeap.pop()[0]
        }
        return result
      }
      console.log(topKFrequent([5, 3, 1, 1, 1, 3, 5, 73, 1], 3)) // [1,5,3]

 

 

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

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

相关文章

浙大MEM提面优秀成功上岸经验分享——完全准备才能“聊”的好

近期元旦放假&#xff0c;终于有时间写一写关于自己浙大MEM提面上岸的一些经验分享了。这篇可能对接下来参加2024年浙大mem考试的考生会有一些作用。因为我是参加了提前批面试&#xff0c;并在面试中取得了优秀的资格&#xff0c;所以这也为我后续的联考和录取环节减轻了不少的…

[JAVA安全]filter 内存马

原理&#xff1a; Servlet 有自己的过滤器 filter &#xff0c; 可以通过自定义的过滤器&#xff0c;来对用户的请求进行拦截等操作。 经过filter 之后才会刀Servlet &#xff0c;如果我们动态创建一个 filter 并且将其放在最前面&#xff0c;我们的filter 就会最先被执行&…

Java多线程案例——线程池及ThreadPoolExecutor类

一&#xff0c;线程池1.为什么会有线程池&#xff1f;线程池和多线程的区别&#xff1f;为了很好的解决高并发问题&#xff0c;提高计算机的运行效率&#xff0c;提出了多线程来取代多进程&#xff08;因为一个线程的创创建、销毁和调度比进程更加“轻量”&#xff0c;所以线程…

杰卡德相似度(Jaccard)详解及在UserCF中的应用

1、杰卡德相似度(Jaccard) 这个是衡量两个集合的相似度一种指标。 两个集合A和B的交集元素在A&#xff0c;B的并集中所占的比例&#xff0c;称为两个集合的杰卡德相似系数&#xff0c;用符号J(A,B)表示 另一种表示的方法&#xff1a; jaccard系数衡量维度相似性 jaccard系数很…

IT运维.服务器常见资质认证

3C证书 强制要求 CCC认证的全称为“中国强制性产品认证“ 它是为保护消费者人身安全和国家安全、加强产品质量管理、依照法律法规实施的一-种产品合格评定制度。, 节能

spring之动态代理

文章目录前言一、JDK动态代理1、业务接口OrderService2、目标对象OrderServiceImpl3、客户端程序Client4、InvocationHandler 的实现类TimeInvocationHandler5、运行结果二、CGLIB动态代理1、先引入依赖2、目标类 UserService3、客户端程序Client4、MethodInterceptor的实现类T…

温振传感器的信号输出方式及应用领域

在振动测量系统中&#xff0c;测量振动的仪器排在前端。温振传感器也称为温度振动传感器&#xff08;变送器&#xff09;&#xff0c;它可以将被测对象的振动量&#xff08;位移、速度&#xff09;准确接受后&#xff0c;并将此机械量转换为电信号显示出来。 在工业生产、食品…

内存对齐(memory align)

0. 内存结构 我们平时所称的内存也叫随机访问存储器&#xff08;random-access memory&#xff09;也叫RAM。而RAM分为两类&#xff1a; 一类是静态RAM&#xff08;SRAM&#xff09;&#xff0c;这类SRAM用于前边介绍的CPU高速缓存L1Cache&#xff0c;L2Cache&#xff0c;L3C…

不求星光灿烂,但愿岁月静好

作者&#xff1a;非妃是公主 专栏&#xff1a;《程序人生》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录不求星光灿烂&#xff0c;但愿岁月静好说一说这一年的自己的收获吧2022年的追求自我学会拒绝尝试表达…

Unreal单播委托

单播委托只能注册一个函数:无参无返回值给委托绑定函数:判断如果委托有绑定函数就发起广播:解绑:绑定方式除了BindUObject,还有BindUFunction,通过这种方式绑定需要给函数添加UFUNCTION标记:还有BindLambda匿名函数:BindRaw可以绑定原生C类中的函数:无参有返回值定义委托类型:声…

Linux进程状态与系统负载检测

1.基础知识-进程的5个状态进程可以分为五个状态&#xff0c;分别是&#xff1a;1&#xff09;创建状态一个应用程序从系统上启动&#xff0c;首先就是进入创建状态&#xff0c;需要获取系统资源创建进程管理块&#xff08;PCB&#xff09;完成资源分配。2) 就绪状态在创建状态完…

Dextran-Azide,Dextran-N3结构式;叠氮修饰的葡聚糖 科研用试剂说明

Dextran-N3,叠氮基团葡聚糖 英文名称&#xff1a;Dextran-Azide,Dextran-N3 中文名&#xff1a;叠氮修饰的葡聚糖 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观: 固体或类白色絮状&#xff0c;取决于分子量 溶剂&#xff1a;溶于大部分有机溶剂&…

kafka单节点部署,手把手从零到一

kafka单节点部署 书接上回&#xff1a;zookeeper单节点部署&#xff0c;手把手从零到一 建议配套观看 2、kafka的单节点部署 2.1、下载 这里如果和zookeeper相似的就不再赘述&#xff0c;直接上命令 wget https://archive.apache.org/dist/kafka/2.8.2/kafka_2.12-2.8.2.tgz…

深入了解ArrayBlockingQueue 阻塞队列

1. 前言 开始正式了解阻塞队列之前&#xff0c;我们需要了解什么是队列。 队列有什么作用。其实队列的作用就是解耦&#xff0c;更加确切的说应该是生产者以及消费者 之间的解耦 今天就让我们来看下ArrayBlockingQueue 的实现。虽然通过名称就可以看到&#xff0c;无非是通过数…

Theory for the information-based decomposition of stock price

文章目录MotivationThe potential of Brogaard DecompositionIntuitions for Brogaard decompositionTechnique details in Brogaard decompositionDefine the VAR systemIdentify the VAR systemVariance decompositionSummaryMain ReferencesMotivation Brogaard et al. (20…

1000字带您了解网络设备的接口分类和接口编号规则

通过本文&#xff0c;您可以了解到设备的接口分类和接口编号规则。 文章目录一、接口分类1.1 物理接口1.1.1 管理接口1.1.1 业务接口LAN侧接口WAN侧接口1.2 逻辑接口二、接口编号规则2.1 物理接口编号规则三、总结一、接口分类 接口是设备与网络中的其它设备交换数据并相互作用…

3.3 行列式的几何意义

文章目录二维面积三维体积多维体积行列式是线性代数一个非常重要的内容&#xff0c;也是非常难的领域.行列式在欧几里得空间里还有特殊的几何意义。二维面积 &esmp; 两个向量围成的平行四边形的面积就是这两个向量组成的矩阵的行列式的绝对值。以两个向量(3.−2)T(3.-2)^…

结构体 · 内存对齐

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 在初识C语言中简单介绍了结构体&#xff0c;结构体可以理解为不同类型数据的集合体&#xff0c;但是你想过结构体的大小是如何计算的吗&#xff1f;看完这篇博客&#xff0c;你就能给自己答…

Linux 计算机网络 route 路由表、多网段与 bond 的故事

Linux 计算机网络 route 路由表、多网段与 bond 的故事 序 在之前的章节中&#xff0c;介绍了计算机网络的发展以及各种解析&#xff0c;在之中我们提到了每个主机设备都会维护一张自己的路由表&#xff0c;通过路由表来确定在不同网络之间&#xff0c;怎么将数据规划传输到各…

1988-2020年31省基尼系数数据

1、时间&#xff1a;1988-2020年 2、范围&#xff1a;31省 3、指标&#xff1a;包括省基尼系数年度数据&#xff0c;省城市和农村基尼系数年度 4、来源及计算方法说明附在文件内 5、指标说明&#xff1a; 基尼系数&#xff08;英文&#xff1a;Gini index、Gini Coefficie…