java timsort_TimSort排序算法及一个问题分析

java timsort_TimSort排序算法及一个问题分析TimSort 排序算法及一个问题分析摘要排序算法简析代码入口排序算法获取两个有序数组 A 和 B 找到待归并区间准备操作归并操作 TimSort 的优化归并操作问题解析问题解析问题原因解决方案参考摘要简单介绍了传统归并排序算法 以及 JavaAPI 提供的 TimSort 优化后的归并排序算法 并且分析了代码中出现的一个问题原因与解决方案 敬请忽略文中的灵魂画风 排序算法简析代码入口 Collections sort

TimSort排序算法及一个问题分析摘要

排序算法简析代码入口

排序算法获取两个有序数组A和B

找到待归并区间

准备操作

归并操作

TimSort的优化归并操作

问题解析问题解析

问题原因

解决方案

参考

摘要

简单介绍了传统归并排序算法,以及Java API提供的TimSort优化后的归并排序算法。

并且分析了代码中出现的一个问题原因与解决方案。

敬请忽略文中的灵魂画风。

排序算法简析

代码入口

Collections.sort、List.sort、Arrays.sort方法是逐级调用的关系,最终的底层是Arrays.sort方法,其代码如下(这几个方法的javadoc都蔚然可观,大家不妨看一看):

publicstaticvoidsort(T[] a, Comparator
c) {

if(c ==null) {

sort(a);

}else{

if(LegacyMergeSort.userRequested)

legacyMergeSort(a, c);

else

TimSort.sort(a,0, a.length, c,null,0,0);

}

}

其中的legacyMergeSort(a,c)方法已经被标记废弃;使用的主要是TimSort.sort()方法。这个方法是一个优化版的归并排序。

排序算法

这里简单描述一下核心算法,略过一些细节。

这里的描述出自个人理解(看代码和网上搜到的综合理解),可能与实际情况有出入。欢迎指正。

获取两个有序数组A和B

传统的归并排序中,A和B都是从“一个元素”开始的;“一个元素”天然有序。TimSort会通过插入排序等方法,先构建一些小的有序数组,以提高一点性能。

另外,在TimSort中,A和B都是入参a[]中的一个片段。这样可以节省一些内存空间。

e17caf89350b5f81c4778fdc1f517001.png

找到待归并区间

从数组A中,找到A[n-1]B[0],以及A[m]B[length-1]。

此时,待归并区间就是A[n,m]和B。

747dcdc4a33ce32e590b5b7797dd4dbc.png

准备操作

在正式开始归并之前,会做一些准备操作。包括将非待归并区间的数据移动到合适的位置上;准备一个临时数组、初始化一些指针数据等。如下图。

数组B被复制到了临时数组temp中。因为数组B的空间会被其他元素覆盖。

原数组A中最后一个元素“12”被放到了原数组B的最后一个位置上。因为这个元素比待归并区间所有元素都更大。

指针B1指向数组A’的第一个元素;C1指向A’的最后一个元素;B2指向B’的第一个元素;C2指向B’的最后一个元素。这四个指针是用来确定两个数组中待比较和待移动的数据范围的

指针D指向的位置,是下一个“已排序”元素的位置。也就是从A’和B’中找到的最大的元素,将会放到这个位置上。

02fd0709eb2366eb786540f652c82a85.png

归并操作

归并操作相对比较简单。依次比较A'[C1]和B'[C2],将较大的数值移动到D的位置上,并将D和对应的C1/C2向左移动一位。重复执行此操作,直到C1

下图是示例数据从准备操作(左上角标记0)到四次排序(左上角标记依次从1到4)的归并步骤。

f9c2826fa877e2481d856bb07095dc3c.png

TimSort的优化归并操作

TimSort在某些情况(触发条件待考)下,会对上述归并操作做一个优化。主要的优化点在于:不是一次一个元素的移动,而是尝试着一次移动多个元素。

下图是按优化后的逻辑,同样的示例数据从准备操作(左上角标记0)到完成排序的归并步骤。注意第一步和第二步每次都移动了两个元素。这里只用了5步就完成了归并;而优化前需要7步。

170c2e065e6f1a8732caf523d4ba39e4.png

问题解析

问题现象

代码中有一处排序逻辑,代码是这样的:List batchList = batchs.stream()

.sorted(new Comparator() {

@Override

public int compare(BatchData o1, BatchData o2) {

if (o1.getClass().getSimpleName()

.equals(o2.getClass().getSimpleName())) {

return o1.getId() – o2.getId();

}

return o1 instanceof BatchData4Company ? 1 : -1;

}

}).collect(Collectors.toList());

这段代码在某些特殊情况下,会引发这个问题:java.lang.IllegalArgumentException: Comparison method violates its general contract!

at java.util.TimSort.mergeHi(TimSort.java:899)

at java.util.TimSort.mergeAt(TimSort.java:516)

at java.util.TimSort.mergeForceCollapse(TimSort.java:457)

at java.util.TimSort.sort(TimSort.java:254)

at java.util.Arrays.sort(Arrays.java:1512)

问题原因

问题原因是,对某些数据来说,上述代码会导致compare(a,b)<0并且compare(b,a)<0,也就是a

例如,我们假定:a

假定输入数组a[] = {5,a,7,12,4,b,8,8},其中待归并的两个有序数组分别是{5,a,7,12}和{4,b,8,8}

假定b<7&&7>b。这样可以触发“特殊情况”,即:a和b在某一次归并操作后,会同时成为“是否移动元素”的临界条件。

这样,在“特殊情况”下,优化后的归并操作可能陷入死循环。用画图来表示是这样的。

获取两个有序数组A和B

首先,我们有两个有序数组A和B,如下图所示。

90f2709e3c15c941b2b264b8a3f3c5a1.png

找到待归并区间、做好准备操作

这样,在划分完待归并区间后,得到的结果是这样的:

0bbf77493a3775219f669491d92451e1.png

第一次归并操作:C2落在了元素b上

然后,开始第一次归并操作。由于B'[C2]>A'[C1],我们需要从C2开始,在数组B’中找到一个下标n,使得B'[n]

这里需要注意两点:首先,临界点的比较条件是B'[n]

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224140.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午12:37
下一篇 2026年3月17日 下午12:37


相关推荐

  • cuda编程-并行规约

    cuda编程-并行规约

    2022年3月8日
    54
  • ABAP 常用BAPI

    ABAP 常用BAPIABAP常用BAPI

    2022年7月24日
    42
  • java url加密_Java实现url加密处理的方法示例

    java url加密_Java实现url加密处理的方法示例本文实例讲述了Java实现url加密处理的方法。分享给大家供大家参考,具体如下:packagetest;importjava.security.Key;importjava.security.SecureRandom;importjavax.crypto.Cipher;importjavax.crypto.KeyGenerator;importsun.misc.BASE64Decode…

    2025年8月3日
    3
  • 微信开放平台实现扫码登录(java)

    微信开放平台实现扫码登录(java)微信第三方登录准备阶段微信官方文档准备工作在进行第三方授权登录之前,需要在微信开放平台注册开发者账号,拿到相应的AppId和AppSecret以及redirect_uri,即可进行授权接入流程授权流程说明整体流程分:1.第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;2.通过code参数加上AppID和AppSecret等,通过API换取access_token;3.通过access_token进行接口调

    2022年4月29日
    232
  • 图像常识1——1080i、1080p、2K、4K是什么意思?

    图像常识1——1080i、1080p、2K、4K是什么意思?I 指的是 Interlacedsc 隔行扫描 P 指的是 progressives 逐行扫描 I 和 P 是曾经那些大背头电视 需要电子枪时代的产物 现在都是液晶屏幕 基本上已经不再谈论扫描的技术概念了 通常只说 P P 和 K 都是指横向或者纵向的像素行数或列数 通常 P 代表的是纵向的像素行数 通常 K 代表着横向的像素列数 在数字技术领域 通常采用二进制运算 而且用构成图像的像素来描述数字图像的大小 由于构成数字图像的像素数量巨大 通常以 K 来表示 2 的 10 次方即 1024 因此 1K 2 10 10

    2026年3月26日
    2
  • arm架构的安卓模拟器_armv8.3

    arm架构的安卓模拟器_armv8.3Android设备的CPU类型通常称为ABIs问题描述解决方法1解决之前的截图2解决后的截图3解决方法4建议为什么你需要重点关注so文件App中可能出错的地方其他地方也可能出错使用android-21平台版本编译的so文件运行在android-15的设备上混合使用不同C运行时编译的so文件没有为每个支持的CPU架构提供对应的so文件将so文件放在错误的地方只提供arme…

    2026年1月27日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号