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

news2024/10/6 18:28:55

发生情况

定位到问题代码如下(脱敏处理过后),意思是集合排序,如果第一个元素大于第二个元素,比较结果返回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.coloradmin.cn/o/1847186.html

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

相关文章

【Android14 ShellTransitions】(六)SyncGroup完成

这一节的内容在WMCore中&#xff0c;回想我们的场景&#xff0c;是在Launcher启动某一个App&#xff0c;那么参与动画的就是该App对应Task&#xff08;OPEN&#xff09;&#xff0c;以及Launcher App对应的Task&#xff08;TO_BACK&#xff09;。在确定了动画的参与者后&#x…

C#.net6.0语言+B/S架构+前后端分离 手术麻醉信息管理系统源码

C#.net6.0语言&#xff0b;B/S架构前后端分离 手术麻醉信息管理系统源码 什么是手术麻醉信息管理系统 满足医院等级评级需求 满足电子病历评级需求 满足科室需求 术前 1、患者术前评估/诊断 2、术前讨论制定手术方案 3、手术准备 4、术前准备 术中 1、送手术室 2、麻…

openlayers 轨迹回放(历史轨迹),实时轨迹

本篇介绍一下使用openlayers轨迹回放&#xff08;历史轨迹&#xff09;&#xff0c;实时轨迹 1 需求 轨迹回放&#xff08;历史轨迹&#xff09;实时轨迹 2 分析 主要是利用定时器&#xff0c;不断添加feature 轨迹回放&#xff08;历史轨迹&#xff09;&#xff0c;一般是…

Ubuntu安装qemu-guest-agent

系列文章目录 Ubuntu-24.04-live-server-amd64安装界面中文版 Ubuntu-24.04-live-server-amd64启用ssh Ubuntu乌班图安装VIM文本编辑器工具 文章目录 系列文章目录前言一、安装二、启用服务三、效果总结 前言 QEMU Guest Agent&#xff08;简称QEMU GA或QGA&#xff09;在虚拟…

什么是NLP-自然语言处理

什么是NLP-自然语言处理 什么是NLP开通NLP新建项目创建模型 什么是NLP NPL是面向算法小白用户的行业自适应标注、训练和服务平台。该产品支持文本实体抽取、文本分类、关键短语抽取、情感分析、关系抽取、短文本匹配、商品评价解析等 NLP 定制化算法能力&#xff0c;用户无需拥…

御龙掘宝挂机零撸修仙类游戏定制开发源码部署

随着移动游戏的普及&#xff0c;御龙掘宝挂机零撸修仙类游戏定制开发源码部署应运而生。这款游戏结合了传统的修仙元素、挂机游戏的核心玩法以及零撸掘金的商业模式&#xff0c;为玩家提供了一个全新的游戏体验。本文将探讨御龙掘宝挂机零撸修仙类游戏定制开发源码部署的核心技…

linux 安装sftp及使用sftp工具类上传和下载

一、centos7 安装sftp 1.安装 OpenSSH 服务&#xff1a; sudo yum install openssh-server2.启动 SSH 服务&#xff0c;并设置为开机启动&#xff1a; sudo systemctl start sshd sudo systemctl enable sshd3.创建一个新用户&#xff0c;用于SFTP连接&#xff08;替换your_…

Linux:多线程中的互斥与同步

多线程 线程互斥互斥锁互斥锁实现的原理封装原生线程库封装互斥锁 死锁避免死锁的四种方法 线程同步条件变量 线程互斥 在多线程中&#xff0c;如果存在有一个全局变量&#xff0c;那么这个全局变量会被所有执行流所共享。但是&#xff0c;资源共享就会存在一种问题&#xff1…

Ilya出走记:SSI的超级安全革命

图片&#xff5c;OpenAI官网 ©自象限原创 作者丨罗辑、程心 和OpenAI分道扬镳以后&#xff0c;Ilya“神秘而伟大”的事业终于揭开了面纱。 6月20日&#xff0c;前OpenAI核心创始人 Ilya Stuskever&#xff0c;在官宣离职一个月后&#xff0c;Ilya在社交媒体平台公开了…

opencascade AIS_InteractiveContext源码学习2

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是&#xff0c;对于已经被交互上下文识别的交互对象&#xff0c;必须使用上下文方法进行…

神经网络学习5-非线性激活

非线性激活&#xff0c;即 这是最常用的 inplaceTrue 原位操作 改变变量本身的值&#xff0c;就是是否输入时若原本有值&#xff0c;是否更换 该函数就是表示&#xff1a;输入小于零时输出0&#xff0c;大于零时保持不变 代码如下&#xff1a; import torch from torch imp…

芋道源码 yudao-cloud 、Boot 文档,开发指南 看全部,破解[芋道快速开发平台 Boot + Cloud]

1、文档全部保存本地部署查看&#xff0c;真香 文档已抓取最新版本&#xff0c;2024.06.21。【唯一遗憾&#xff0c;表结构到2024.04月&#xff0c;已被限制放到知识星球】会员中心&#xff0c;支付中心&#xff0c;CRM&#xff0c;ERP&#xff0c;商城&#xff0c;公众号运行…

利氪科技拿下C轮超级融资,国产智能底盘黑马奔向黄金时代

“智能驾驶遗珠&#xff0c;国产替代富矿。” 这是海通证券在最近一期研报中&#xff0c;描述线控底盘产业的用语。它很巧妙地点明了&#xff0c;这个藏在车身之下的部分&#xff0c;拥有何种特征——稳坐技术体系的核心点位&#xff0c;拥有前景广阔的市场。 事实上&#xf…

生成式AI与开发者:威胁还是机遇?

近期&#xff0c;围绕生成式人工智能&#xff08;AI&#xff09;是否能取代程序员的讨论达到了前所未有的高度。百度的创始人李彦宏甚至预言&#xff0c;未来可能不再需要程序员这一职业。这个话题让很多开发者&#xff0c;包括有几年开发经验的我&#xff0c;感到不安。我记得…

【ArcGIS微课1000例】0120:ArcGIS批量修改符号的样式(轮廓)

ArcGIS可以批量修改符号的样式,如样式、填充颜色、轮廓等等。 文章目录 一、加载实验数据二、土地利用符号化三、批量修改符号样式四、注意事项一、加载实验数据 订阅专栏后,从私信查收专栏配套的完整实验数据包,打开0120.rar中的土地利用数据,如下图所示: 查看属性表: …

Python Web实战:Python+Django+MySQL实现基于Web版的增删改查

项目实战 1.创建项目(sms) File->New Project->Django 稍等片刻&#xff0c;项目的目录结构如下图 项目创建后确认是否已安装Django和mysqlclient解释器&#xff0c;如何确认&#xff1f;file->Settings 如果没有请在Terminal终端输入以下命令完成安装 pip instal…

德璞资本:科技股波动解析,三巫日与日元效应下的市场走向

摘要 近期&#xff0c;美国科技股的表现令人担忧&#xff0c;标普500指数在科技股的拖累下出现下跌。亚洲股市也受到影响&#xff0c;特别是日本和韩国股市。随着期权到期日的临近&#xff0c;市场面临更大的波动风险。本文将详细分析科技股失去动能的原因、三巫日的影响及未来…

elementui组件库实现电影选座面板demo

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Cinema Seat Selection</title><!-- 引入E…

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

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

策略模式:applicationContext.getBeansOfType()方法

applicationContext.getBeansOfType() 一般用来获取某个接口的所有实例Bean 方法定义如下&#xff1a; 入参一般是接口&#xff0c;即interface。响应是个Map结构&#xff0c;key bean在容器中的名称&#xff0c;value bean实列 开发步骤&#xff1a; 1.定义接口 2.定义…