java timsort_Timsort算法浅析

java timsort_Timsort算法浅析上一篇谈到的双轴快排 是 Arrays 对八种基本类型进行排序的算法 针对其它的对象类型 JDK1 6 及以前的版本使用的是归并排序 从 JDK1 7 开始 默认情况下会采用 Timsort 排序算法 而 Collections sort 实际上也是调用 Arrays sort 方法 现实中的大多数据通常是有部分已经排好序的 该算法利用这一特点提升了排序效率 下面将跟随 JDK1 8 源码 对 Timsort 的实现进行分析

上一篇谈到的双轴快排,是Arrays对八种基本类型进行排序的算法,针对其它的对象类型,JDK1.6及以前的版本使用的是归并排序,从JDK1.7开始,默认情况下会采用Timsort排序算法,而Collections.sort实际上也是调用Arrays.sort方法。现实中的大多数据通常是有部分已经排好序的,该算法利用这一特点提升了排序效率,下面将跟随JDK1.8源码,对Timsort的实现进行分析。

排序的核心代码从TimSort.sort方法开始,首先判断需要排序的元素个数,如果小于一个阈值(在Tim的C语言实现中默认为64,JDK中为32),先找出从起点位置开始的最大升序或严格降序列长度,并对降序列进行翻转,然后进行二分插入排序。

// If array is small, do a “mini-TimSort” with no merges

if (nRemaining < MIN_MERGE) {

int initRunLen = countRunAndMakeAscending(a, lo, hi, c);

// Binary insertion sort

binarySort(a, lo, hi, lo + initRunLen, c);

return;

}

如果长度大于这个阈值,开始从左往右遍历数组,找出一个升序列或严格降序列,同样需要对降序列进行翻转,得到的序列这里称为一个run。如果这个run的长度太小,用二分插入排序进行增补,再入栈并尝试合并,然后继续往右寻找下一个run。

/

* March over the array once, left to right, finding natural runs,

* extending short natural runs to minRun elements, and merging runs

* to maintain stack invariant.

*/

TimSort ts = new TimSort<>(a, c, work, workBase, workLen);

int minRun = minRunLength(nRemaining);

do {

// Identify next run

int runLen = countRunAndMakeAscending(a, lo, hi, c);

// If run is short, extend to min(minRun, nRemaining)

if (runLen < minRun) {

int force = nRemaining <= minRun ? nRemaining : minRun;

binarySort(a, lo, lo + force, lo + runLen, c);

runLen = force;

}

// Push run onto pending-run stack, and maybe merge

ts.pushRun(lo, runLen);

ts.mergeCollapse();

// Advance to find next run

lo += runLen;

nRemaining -= runLen;

} while (nRemaining != 0);

每次有新的run入栈,都会对栈里相邻的两个run按照一定规则尝试进行合并,使得对于任意相邻的三个run,它们的长度X、Y、Z满足两个条件:

X > Y + Z

Y > Z

具体做法是,从栈顶的三个run开始判断,如果没有同时满足两个条件,就通过合并把两个run变为一个更大的run,然后继续循环对栈顶三个run进行判断。

/

* Examines the stack of runs waiting to be merged and merges adjacent runs

* until the stack invariants are reestablished:

*

* 1. runLen[i – 3] > runLen[i – 2] + runLen[i – 1]

* 2. runLen[i – 2] > runLen[i – 1]

*

* This method is called each time a new run is pushed onto the stack,

* so the invariants are guaranteed to hold for i < stackSize upon

* entry to the method.

*/

private void mergeCollapse() {

while (stackSize > 1) {

int n = stackSize – 2;

if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {

if (runLen[n – 1] < runLen[n + 1])

n–;

mergeAt(n);

} else if (runLen[n] <= runLen[n + 1]) {

mergeAt(n);

} else {

break; // Invariant is established

}

}

}

为了提高效率,在合并两个相邻run的时候,采用了一个叫Galloping的模型进行优化。例如现在要合并较小的A和较大的B两个run,因为A和B已经分别是排好序的,用二分查找找到B的第一个元素在A中何处插入,找到以后,A在该素左边的部分就不需要比较了。同样,找到A的最后一个元素在B的何处插入,B在该元素之后的元素也不需要比较了。

/*

* Find where the first element of run2 goes in run1. Prior elements

* in run1 can be ignored (because they’re already in place).

*/

int k = gallopRight(a[base2], a, base1, len1, 0, c);

assert k >= 0;

base1 += k;

len1 -= k;

if (len1 == 0)

return;

/*

* Find where the last element of run1 goes in run2. Subsequent elements

* in run2 can be ignored (because they’re already in place).

*/

len2 = gallopLeft(a[base1 + len1 – 1], a, base2, len2, len2 – 1, c);

assert len2 >= 0;

if (len2 == 0)

return;

接下来合并中间的部分,用一个临时存储空间,大小是待合并的两部分中较小的部分的大小。先将较小的部分复制到这个临时存储空间,然后用原先存储这2个部分的空间来存储合并后的元素。剩下就是简单归并排序:依次从左到右或从右到左,不断比较两个序列的第一个元素并插入到相应位置。

// Merge remaining runs, using tmp array with min(len1, len2) elements

if (len1 <= len2)

mergeLo(base1, len1, base2, len2);

else

mergeHi(base1, len1, base2, len2);

总结:Timsort结合了归并排序和插入排序,很好地利用了无序数列中有序的部分,按照升序和降序子序列进行分区,减少了升序部分的回溯和降序部分的性能倒退。Galloping模型的引入也降低了不必要的比较次数,大大优化了两个相邻run的合并效率。

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

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

(0)
上一篇 2026年3月19日 下午10:04
下一篇 2026年3月19日 下午10:04


相关推荐

  • 用户 不在 sudoers 文件中。此事将被报告。

    用户 不在 sudoers 文件中。此事将被报告。文章目录背景解决方案背景普通linux用户使用sudo命令执行只有root用户才可以执行的命令时出现了该错误,如下图示:简单说明一下操作。命令$ll/etc/sudoers表示查看文件的属性,属性包括有:文件拥有者、文件所属组以及其他用户组对该文件拥有的读写权限和文件的类型等,上图的/etc/sudoers文件表示拥有者和所属组都是root且只能读取,其他用户组的没有任何读写权限。命…

    2022年6月20日
    46
  • 刚刚,微信被曝打造绝密 AI 智能体

    刚刚,微信被曝打造绝密 AI 智能体

    2026年3月13日
    3
  • 清除定时器的方法

    清除定时器的方法清除定时器的方法定时器一般有两个 1 setTimeout n 毫秒后执行一次 2 setInterval 每隔 n 秒执行一次这两个方法都有个返回值 返回一个定时器 id 可以定义一个变量接收清除定时器方法 setTimeout 对应的是 clearTimeout id setInterval 对应的是 clearInterva id 例如 vartime

    2026年3月19日
    3
  • 邪修安装:用 OpenClaw 一键部署本地 Claude Code(纯命令版)

    邪修安装:用 OpenClaw 一键部署本地 Claude Code(纯命令版)

    2026年3月13日
    4
  • pycharm中如何导入库_如何把手变成手控

    pycharm中如何导入库_如何把手变成手控大家都知道,Python是一个极其方便的由库构建的编程语言。比如机器学习的库sklearn,文件读取pandas,文件读写xlwt,xlrt,矩阵运算numpy等等等等等等等等等等,多到你无法想象!那到底如何导入Python库呢?我们今天就来学习一下~点击File->NewProject,创建一个PyCharm项目,然后点击File->Settings->P…

    2022年8月26日
    8
  • blob对象介绍[通俗易懂]

    blob对象介绍[通俗易懂]一个Blob对象表示一个不可变的,原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式blob对象本质上是js中的一个对象,里面可以储存大量的二进制编码格式的数据。创建blob对象创建blob对象本质上和创建一个其他对象的方式是一样的,都是使用Blob()的构造函数来进行创建。构造函数接受两个参数:第一个参数为一个数据

    2025年6月19日
    4

发表回复

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

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