Comparison method violates its general contract! 神奇的报错

发生情况

定位到问题代码如下(脱敏处理过后),意思是集合排序,如果第一个元素大于第二个元素,比较结果返回1,否则返回-1,这里粗略的认为小于和等于是一样的结果

List<Integer> list = Arrays.asList(2213, 2214, 2235, 2211, 228, 2233, 2215, 2229, 2212, 0, 2245, 2220, 225,2237, 2241, 229,2221, 227, 2236, 226, 2246, 2222, 2247, 2232, 2240, 225, 2218, 2227, 2248, 2216, 2217, 2223);
list.sort((o1, o2) -> o1 > o2 ? 1 : -1);

之前没问题啊,对 这个问题不是百分之百复现的,只需要将上述集合删除一些元素,报错就不会发生了看了一些人的文章,基本上意思是Collections.sort()在JDK6中使用的时MergeSort排序,在JDK1.7之后默认排序方式做了修改,使用TimeSort排序算法来排序,查看了一下Arrays.java源码,果然如此

public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
  }
那么TimSort排序有啥说法呢?此排序算法比老版本的算法多了如下几个限制条件,如果不注意,排序可能会抛异常
  1. 自反性,x,y 的比较结果和 y,x 的比较结果相反,即compare(x, y) = - compare(y, x)
  2. 传递性,如果compare(x, y) > 0, compare(y, z) > 0, 则必须保证compare(x, z) > 0
  3. 对称性, 如果compare(x, y) == 0, 则必须保证compare(x, z) == compare(y, z)
上面的表达式(o1, o2) -> o1 > o2 ? 1 : -1,显然不满足 自反性,例如o1=5,o2=5,那么compare(o1,o2)返回的-1,根据自反型原则compare(o2,o1)应该返回的1 ,实际上返回的确还是-1

基本解决

正确写法是排序方法要考虑返回每种情况 -1,0,1 三种
list.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer integer, Integer anotherInteger) {
        return integer.compareTo(anotherInteger); 
        //或者写成 return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
});

或者直接用stream 流方式来排序就好,我也比较喜欢这种方式

 // 升序
list=list.stream().sorted().collect(Collectors.toList());
// 降序
list=list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

探索底层报错原因

简述Timsort算法:

       Timsort排序算法是一种结合了归并排序(merge sort)和插入排序(insertion sort)的高效排序算法,专为现实世界的数据排序需求而设计。TimSort 算法为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。

基本思想:

  • 利用现实数据中通常存在的部分已排序的特点,将数组拆分成多个已排序的分区(称为“run”)和未排序的分区。
  • 对未排序的分区使用插入排序或其他方法进行排序,使其成为新的已排序分区。
  • 最后,通过归并排序的思想将这些已排序的分区合并成一个完全有序的数组。

分区方法:

  • 假定数据长度为n。
  • 如果n小于某个阈值(如32或64,具体值可能因不同编程语言实现而异),则直接对整个数组使用插入排序或其他简单排序算法。
  • 如果n大于或等于阈值,则先找到已排序的分区(run),并对这些分区进行标记。
  • 如果分区是降序的,则将其翻转为升序。

合并规则:

  • 使用一个栈来保存已排序的分区(run)。
  • 从栈顶开始,按照归并排序的合并策略,将两个相邻的分区合并成一个更大的已排序分区。
  • 合并的结果(新的已排序分区)继续压入栈中。
  • 重复这个过程,直到栈中只剩下一个分区,此时整个数组已经排序完成。

特殊优化:

  • Galloping模式:在合并过程中,使用一种称为“Galloping”的策略来快速跳过相等或接近相等的元素,从而提高合并效率。
  • 二分插入排序:对于较小的分区(如长度小于阈值的分区),使用二分插入排序算法来提高排序效率

拆分RUN

那么首先看我们要排序的例子,正好32个,所以分为两个RUN,暂时成为 ra 和rb【划分的块数最好是完整二叉树的叶子节点的数量 即二的整数次幂(2,4,8...)  并且最好每个块要大小均匀而且维持在16到32之间的小块 这样效率最高 】

分别排序

那么接下来我们要做的就是分别讲ra 和rb 进行排序:
对整个List开始到结束单调递增或者单调递减的部分下标拿到,如果递增的则不变递减的把单调递减部分List反转 使其变为递增(java.util.ComparableTimSort#countRunAndMakeAscending) 然后以前段的单调递增List为基础 之后的元素一个个与前段基础List二分查找插入位置进行插入(java.util.ComparableTimSort#binarySort),最终结果得出。
对于上述,单调递增数据部分为 2213,2214,2235,所以结束的下标为3,所以得到基础递增部分,一次按着红色部分进行排序

所以ra 最后得到排好的序列为  0       225    228    229    2211    2212    2213    2214    2215    2220    2229    2233    2235    2237    2241    2245

同理rb 最后得到排好的顺序为 225    226    227    2216    2217    2218    2221    2222    2223    2227    2232    2236    2240    2246    2247    2248

合并RUN

 合并2个相邻的run需要临时存储空闲,临时存储空间的大小是2个run中较小的run的大小。Timsort算法先将较小的run复制到这个临时存储空间,然后用原先存储这2个run的空间来存储合并后的run

首先以ra 最大值2245去rb里面寻找位置,rb里面最小值到ra 寻找位置

这时候会发现绿色部分,已经不再需要我们再排序了,以为ra 绿色部分比rb 最小值还小,rb 的绿色部分比ra的最大值还大

那么需要我们进行排序的部分就是中间无底色部分,显然需要排序的rb所占有的元素更少些,那么所以把rb的数据做一个备份 temp 用一个大小为16的数组  因为备份的是rb 所以rb要先覆盖

所以我们得到如下

比较temp的2240 和ra的2245 ,我们得到了ra的2245>temp 的2240,这时候计数ra 连续胜利一次,并且把ra的2245 写入到rb的未排好序位置,排序后如下

比较temp的2240 和ra的2241,我们得到ra的2241> temp 的2240.计数ra 连续胜利一次,并且把ra的2241 写入到rb的未排好序位置,排序后如下

比较temp 的2240 和ra的2237,我们得到temp的2240 大于ra的2237,计数temp 连续胜利一次,并且把temp的2240 写入到rb的未排好序位置,排序后如下

比较temp 的2236 和ra的2237,我们得到ra的2237 大于temp的2236,计数ra 连续胜利一次,并且把ra的2237 写入到rb的未排好序位置,排序后如下

中间省略若干步骤.........

来看关键步骤吧

此时红色下划线部分都是ra原来的元素,所以呢ra七连胜了,此时触发了Galloping模式,这也就是这个程序报错的关键点了,只有触发了这个模式并且数据结构满足有两个相等情况,才会抛出异常Comparison method violates its general contract!

我们来看下为啥会出现矛盾呢?

在Timsort中,Galloping指的是一种在合并两个已排序的子序列(称为run)时,用于跳过不必要比较的策略,当算法试图将一个元素插入到另一个已排序的子序列中时,如果它发现当前元素小于子序列的第一个元素,它会继续向后搜索,直到找到一个合适的插入位置或到达子序列的末尾。这个过程中跳过多个元素的比较就称为Galloping。

在这里因为ra已经连续赢了7次,那么temp 的剩余元素225,226,227就直接用最小的225来对比ra的225了, 因为最初程序里的排序算法是  list.sort((o1, o2) -> o1 > o2 ? 1 : -1)

所以整体的下移到ra 黄色部分 得到

此时temp 全部排完了,但是temp的225确被排序到ra的225的右侧了,按我们之前的排序规则(o1, o2) -> o1 > o2 ? 1 : -1,225 对比225应该是返回-1, 所以temp的225应该是排在ra225的左边,所以程序自相矛盾了,抛出异常Comparison method violates its general contract!

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

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

相关文章

下载caj viewer查看caj论文

前言 CAJ是“中国学术期刊全文数据库”&#xff08;China Academic Journals&#xff09;的英文缩写&#xff0c;同时也是“中国学术期刊全文数据库”中的一种文件格式。我们从CNKI&#xff08;中国知网&#xff09;下载的资料一般都是这种文件格式。 CAJ不同于PDF等&#xff…

智赢选品,OZON数据分析选品利器丨萌啦OZON数据

在电商行业的激烈竞争中&#xff0c;如何快速准确地把握市场动态、洞察消费者需求、实现精准选品&#xff0c;是每个电商卖家都面临的挑战。而在这个数据驱动的时代&#xff0c;一款强大的数据分析工具无疑是电商卖家们的得力助手。今天&#xff0c;我们就来聊聊这样一款选品利…

JSP基础知识概述

目录 JSP一、什么是JSP1.1 概念1.2 创建JSP1.3 JSP编写Java代码1.4 JSP实现原理 二、JSP与HTML集成2.1 普通脚本2.2 声明脚本2.3 输出脚本2.4 JSP指令2.5 动作标签 三、内置对象3.1 四大域对象 JSP 一、什么是JSP 1.1 概念 简化的Servlet设计&#xff0c;在HTMl标签中嵌套Jav…

华为重磅官宣:超9亿台、5000个头部应用已加入鸿蒙生态!人形机器人现身 专注AI芯片!英伟达挑战者Cerebras要上市了

内容提要 华为表示&#xff0c;盘古大模型5.0加持&#xff0c;小艺能力全新升级。小艺智能体与导航条融为一体&#xff0c;无处不在&#xff0c;随时召唤。只需将文字、图片、文档“投喂”小艺&#xff0c;即可便捷高效处理文字、识别图像、分析文档。 正文 据华为终端官方微…

高速公路声光预警定向广播助力安全出行

近年来&#xff0c;高速重大交通事故屡见不鲜&#xff0c;安全管控一直是高速运营的重中之重。如何利用现代化技术和信息化手段&#xff0c;创新、智能、高效的压降交通事故的发生概率&#xff0c;优化交通安全管控质量&#xff0c;是近年来交管部门的主要工作&#xff0c;也是…

如何恢复电脑硬盘删除数据?提供一套实用恢复方案

在数字化时代&#xff0c;电脑硬盘中存储的数据对于个人和企业来说都至关重要。然而&#xff0c;有时我们可能会不小心删除了一些重要文件&#xff0c;或者因为某种原因导致数据丢失。这时候&#xff0c;恢复硬盘上被删除的数据就显得尤为重要。本文将为您提供一套实用的电脑硬…

stable diffusion 模型和lora融合

炜哥的AI学习笔记——SuperMerger插件学习 - 哔哩哔哩接下来学习的插件名字叫做 SuperMerger,它的作用正如其名,可以融合大模型或者 LoRA,一般来说会结合之前的插件 LoRA Block Weight 使用,在调整完成 LoRA 模型的权重后使用改插件进行重新打包。除了 LoRA ,Checkpoint 也…

Starlink全系卫星详细介绍,波段频谱、激光星间链路技术、数据传输速率等等

Starlink全系卫星详细介绍&#xff0c;波段频谱、激光星间链路技术、数据传输速率等等。 Starlink是SpaceX公司开发的一个低轨道&#xff08;LEO&#xff09;卫星网络系统&#xff0c;旨在为全球用户提供高速宽带互联网服务。截至2024年6月&#xff0c;Starlink已经发射并运行…

90 岁老人靠一辆自行车年赚 170 亿,捷安特如何打造山地车极致产品力?

一位富家小开在中年时经商失败&#xff0c;38岁时从零开始创业&#xff0c;最终在自行车整车市场占据了70%的份额&#xff0c;他是怎么做到的&#xff1f; 一家曾为美国自行车品牌代工的台湾工厂&#xff0c;成功从ToB转型为ToC业务&#xff0c;从90%的代工业务转变为全球最大…

创新实训2024.05.01日志:document-loaders

在建立易学知识库的过程中&#xff0c;仅仅有向量数据库以及词嵌入模型、分词器是不够的&#xff0c;因为我们有大量的非结构化文本&#xff08;如doc,pdf&#xff09;或者是图片需要上传&#xff08;例如pdf里面有图片&#xff09;&#xff0c;此时词嵌入无法直接向向量数据库…

Scikit-Learn梯度提升决策树(GBDT)

Scikit-Learn梯度提升决策树 1、梯度提升决策树(GBDT)1.1、Boosting方法1.2、GBDT的原理1.3、GBDT回归的损失函数1.4、梯度下降与梯度提升1.5、随机森林与GBDT1.6、GBDT的优缺点2、Scikit-Learn梯度提升决策树(GBDT)2.1、Scikit-Learn GBDT回归2.1.1、Scikit-Learn GBDT回归…

中国灌溉农田空间分布

针对全国灌溉农田空间分布数据缺失的现状&#xff0c;融合MODIS植被指数和统计数据生成MIrAD-GI临时灌溉数据集&#xff0c;再利用约束统计和协同绘图方法将其与中国区域现有灌溉数据进行集成、整合&#xff0c;生成了2000-2019年中国逐年灌溉农田分布数据集&#xff08;500米空…

【vLLM】核心技术PagedAttention,调度原理

vLLM 简介 来自加州大学伯克利分校、斯坦福大学、加州大学圣迭戈分校的研究人员基于操作系统中经典的虚拟(Virtual)内存和分页(Page)技术&#xff0c;提出了一个新的注意力算法 PagedAttention&#xff0c;并打造了一个LLM服务系统——vLLM&#xff0c;官网为&#xff1a;http…

Anthropic AI模型Claude 3.5 Sonnet在Amazon Bedrock上正式可用

Claude 3.5 Sonnet是Anthropic最先进的Claude系列AI模型的新成员&#xff0c;比Claude 3 Opus更智能且价格只有其五分之一 北京——2024年6月21日 亚马逊云科技宣布&#xff0c;Anthropic最新、最强大的模型Claude 3.5 Sonnet现已在Amazon Bedrock上正式可用&#xff0c;该模型…

在智星云租用算力时,如何选择适合的GPU?

智星云平台分配GPU、CPU、内存的机制为&#xff1a;按租用的GPU数量成比例分配CPU和内存&#xff0c;算力市场显示的CPU和内存均为每GPU分配的CPU和内存&#xff0c;如果租用两块GPU&#xff0c;那么CPU和内存就x2。此外GPU非共享&#xff0c;每个实例对GPU是独占的。 一. CPU…

推动产业数字化转型,六个方面引领变革

从工业经济时代走向数字经济时代&#xff0c;世界经济发生着全方位、革命性的变化&#xff0c;产业数字化便是最显著的表现之一。当前&#xff0c;产业数字化不断深入发展&#xff0c;平台经济、工业互联网、智能制造等新业态、新模式不断涌现&#xff0c;成为了数字经济的重要…

API低代码平台介绍5-数据库记录修改功能

数据库记录修改功能 在上篇文章中我们介绍了如何插入数据库记录&#xff0c;本篇文章会沿用上篇文章的测试数据&#xff0c;介绍如何使用ADI平台定义一个修改目标数据库记录的接口&#xff0c;包括 单主键单表修改、复合主键单表修改、多表修改&#xff08;整合前两者&#xff…

1.接口测试-postman学习

目录 1.接口相关概念2.接口测试流程3.postman基本使用-创建请求&#xff08;1&#xff09;环境&#xff08;2&#xff09;新建项目集合Collections&#xff08;3&#xff09;新建collection&#xff08;4&#xff09;新建模块&#xff08;5&#xff09;构建请求请求URLheader设…

pretender:一款功能强大的红队MitM安全测试工具

关于pretender pretender是一款功能强大的红队MitM安全测试工具&#xff0c;该工具专为红队研究人员设计&#xff0c;该工具不仅能够进行MitM和中继攻击&#xff0c;而且还支持执行DHCPv6 DNS接管以及mDNS、LLMNR和NetBIOS-NS欺骗攻击。在该工具的帮助下&#xff0c;广大研究人…

学生护眼大路灯应该怎么选?五款护眼大路灯对比推荐

我们都知道光线无处不在&#xff0c;想要减少近视隐患&#xff0c;就不得不提一下护眼灯了&#xff0c;特别是经常坐在电脑前码字的上班族以及深夜还在学习的学生党这一类人群&#xff0c;经常用眼光线不好不仅影响视力健康&#xff0c;还会影响效率。而一款护眼灯能够提供柔和…