Scala - PriorityQueue 踩坑之不保序

news/2024/5/19 1:26:37 标签: scala, PriorityQueue, Heap, 不保序

一.引言

继承 Comparator 实现 PriorityQueue 并且添加元素后,遍历 PriorityQueue 发现元素乱序,于是开始踩坑之旅。首先初始化一个容量为 20 的 PriorityQueue 并添加元素 :

    val scoreQueue = new PriorityQueue[(String, String, Double)](20,
      new Comparator[(String, String, Double)]() {
        override def compare(c1: (String, String, Double), c2: (String, String, Double)): Int =
          if (c2._3 - c1._3 > 0) 1
          else if (c2._3 - c1._3 < 0) -1
          else 0
      })

其中元祖分别代表用户,商品以及用户对商品的喜好程度 : 

    scoreQueue.offer("A", "product", -2.38064E-5)
    scoreQueue.offer("B", "product", 0.0165529)
    scoreQueue.offer("C", "product", 0.01504692)
    scoreQueue.offer("D", "product", 0.011048)
    scoreQueue.offer("E", "product", 0.0164001)
    scoreQueue.offer("F", "product", 0.0164731)

二.错误用法

PriorityQueue 优先队列使用前认为他是默认实现排序的,遂直接 foreach 打印发现并不保序:

    scoreQueue.asScala.foreach(x => println(x))

 B 是最大的排在第一,但是从 E 开始顺序就不对了,而且 A 是负数居然排在正数前面 : 

(B,product,0.0165529)
(E,product,0.0164001)
(F,product,0.0164731)
(A,product,-2.383506137528304E-5)
(D,product,0.011048)
(C,product,0.01504692)

三.正确用法

PriorityQueue 如果想要获得保序的结果需要使用 poll 方法,该方法会弹出最大元素

    (0 until scoreQueue.size()).foreach(_ => {
      println(scoreQueue.poll())
    })

四.原因分析

之所以 foreach 会乱序是因为 PriorityQueue 并不是基于数组排序而是基于堆 Heap 实现,所以针对这6个数据其存储样式为一个完全二叉树,采用层序遍历时,遍得到了上面不保序的错误结果:

其源码中针对添加元素和删除元素的操作也是基于堆的方案实现,通过添加的索引与父节点 or 子节点比较大小随后交换位置,达到 offer 添加或删除 poll 的操作,从而完成堆的更新。

    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }
    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

更多使用方法可以参考 API:PriorityQueue (Java Platform SE 8 ) 和源码


http://www.niftyadmin.cn/n/1235869.html

相关文章

使用Jlink通过SWD接口给STM32下载程序连线方式

SWD模式下 JlinkV8 需要5跟线: 3.3V SWDIO SWCLK GND RESET 如下图: 注意&#xff0c;上图中的电源是3.3V 如果STM32主板上接的是5V&#xff0c;如下图&#xff0c; 也就是说&#xff0c;如果图中的VCC是5V,可能出现下载失败的现象&#xff0c; 因此在上图中VCC 最好是…

数据结构与算法:排序

排序介绍 列表排序&#xff1a;将无序列表变成有序列表&#xff08;升序或降序&#xff09; 内置排序函数&#xff1a;sort() 常见排序算法&#xff1a; LB三人组&#xff1a;冒泡排序、选择排序、插入排序 NB三人组&#xff1a;快速排序、堆排序、归并排序 其他排序&…

Python - 两数相加 (有链表 无链表)

一.引言 给定两个 非空 的列表&#xff0c;表示两个非负的整数。它们每位数字都是按照 顺序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。请将两个数相加并返回结果。 Tips: 两个数字均不会以0为开头 示例: nums1 [1,2,3] nums2 [2,3,4] add(nums1, num…

20221211为论文添砖加瓦(1)

文章&#xff1a; A survey on machine learning methods for churn prediction Louis Geiler, Sverine Affeldt, Mohamed Nadif. A survey on machine learning methods for churn prediction. International Journal of Data Science and Analytics, Springer Verlag, 2022…

Python - 两数相除 递进版

一.引言 给定两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数 divisor 得到的商。整数除法的结果应当截去&#xff08;truncate&#xff09;其小数部分&#xff0c;例如&#x…

Python - 最大堆实现与应用

一.引言 前面提到了 PriorityQueue 踩坑&#xff0c;优先队列排序底层原理就是基于 Max Heap&#xff0c;下面捋一捋最大堆的基本实现与用法。首先明确最大堆及其实现的相关概念&#xff1a; A. 最大堆&#xff0c;又称大根堆&#xff08;大顶堆&#xff09;是指根节点&#…

STM32全局变量占用程序存储空间吗?

全局变量是否占用最终程序的存储空间&#xff0c;这个问题其实早在我们学习C语言的时候就已经告诉我们答案了。我隐约记得初学C语言的时候&#xff0c;书本上告诉我们&#xff1a; 全局自动变量——保存在读写数据段 全局静态变量——保存在读写数据段 全局常量——保存在只读数…

Spark - 大规模数据去重

一.引言 场景 : 商品 product 每日总销售记录量级 亿 级别起&#xff0c;去重 product 量大概 万 级别。每个商品有一个 state 标识其状态&#xff0c;该状态共3个值&#xff0c;分别为 "A", "B","C"。 统计&#xff1a; (1) 三个 state 下 p…