排序算法 - Collections.sort涉及的排序算法源码分析

    前面涉及到了那么多时间复杂度分类的排序算法,冒泡排序、插入排序、选择排序;归并排序、快速排序; 桶排序、计数排序、基数排序,可能常用的排序可能还有堆排序等。了解了每种排序算法的最好最坏平均时间复杂度,空间复杂度(是否原地排序),是否稳定排序等,这些可以理解为都是理论,当我们真正落地使用时会使用哪些排序,现在分析一下Java中涉及的排序算法,进行源码分析。源码分析会比较散,看起来会比较乱,所以下面先进行整体总结再分析源码,方便把控。

    Java中使用的排序方法主要有 Collections.sort、List.sort,而Collections.sort会调用List.sort, 再调到Arrays.sort。即Java的排序算法的真正实现是Arrays.sort,并且提供了两个重载方法是否传入比较器Comparator,虽然画图显示分成了多条路径,但是传入的Comparator本身仅仅是在具体排序算法的比较动作时使用【排序算法很重要的动作就是比较和交换,此处只是使用我们自己写的代码来进行比较动作】。比较器本身是@FunctionalInterface,即一个回调接口,即将我们写的代码与排序算法的比较动作解耦合。
        排序方法会根据Arrays的内部类LegacyMergeSort的userRequested属性进行判断是否调用老的排序方法,其实就是对早起版本的兼容,后续版本进行删除。我查看的Jdk7/8的版本中还存在,我们可以在启动Java main方法是,通过设置启动参数java.util.Arrays.useLegacyMergeSort进行设置。
        比较老的版本中会判断待排序数组的长度,如果小于7则直接插入排序,否则使用归并排序。 
        新版本中则会使用TimSort,其排序考虑到了待排序数组的有序度和逆序度子单元,具体的直接看看TimSort类的注释说明:TimSort是一种稳定的、自适应的、迭代的合并排序,它所需要的时间远远少于O(N*logN),在对随机数组排序时与传统的归并排序有相当的性能提升。与传统的归并排序相比其最坏时间复杂度还是O(N*logN),只是最坏空间复杂度是O(N/2),最佳情况下只占用少量的固定空间【传统归并排序的空间复杂度是O(N)】。java的TimSort改编自Tim peters的Python版本。数据量比较大(长度大于32)时使用了优化的归并排序算法,否则会使用二分插入排序算法。

    总结:比较老的排序版本已经废弃,所以java排序工具调用时都会使用TimSort进行排序。当长度小于32时会使用二分插入排序,否则会使用优化过的归并排序,其时间复杂度远小于O(N*logN),空间复杂度也基本只是传统归并排序的一半。

 

开始梳理源码:入口 Collections.sort, Jdk版本8.

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null); // 调的是list本身的方法
}
default void sort(Comparator<? super E> c) {
	Object[] a = this.toArray();
	// 调用Arrays.sort进行排序
	Arrays.sort(a, (Comparator) c);
	// 最后将排序结构赋值给原对象
	ListIterator<E> i = this.listIterator();
	for (Object e : a) {
		i.next();
		i.set((E) e);
	}
}

所以实现的入口都是Arrays的sort方法:根据是否传入比较器分成了两条路径,其实最终会回到一条线,比较器只是一个回调函数,在具体排序算法的比较、交换的比较阶段回调。

public static <T> void sort(T[] a, Comparator<? super T> c) {
	if (c == null) {
        // 具体的调用在下面的方法,也是会先判断是否调用早期的版本
		sort(a);
	} else {
		if (Arrays.LegacyMergeSort.userRequested)
			legacyMergeSort(a, c);
		else
			TimSort.sort(a, 0, a.length, c, null, 0, 0);
	}
}

public static void sort(Object[] a) {
	if (Arrays.LegacyMergeSort.userRequested)
		legacyMergeSort(a);
	else
		ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}

1、早期版本,长度小于7时使用传统的插入排序,否则使用传统的归并排序

private static void legacyMergeSort(Object[] a) {
	Object[] aux = a.clone();
	mergeSort(aux, a, 0, a.length, 0);
}

// 当数据长度小于7时使用传统插入排序,否则使用传统归并排序
private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) {
	int length = high - low;

	// 当数据规模小时使用插入排序,INSERTIONSORT_THRESHOLD = 7
	if (length < INSERTIONSORT_THRESHOLD) {
		for (int i=low; i<high; i++)
			for (int j=i; j>low &&
					((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
				swap(dest, j, j-1);
		return;
	}

	// Recursively sort halves of dest into src
	int destLow  = low;
	int destHigh = high;
	low  += off;
	high += off;
	int mid = (low + high) >>> 1;
	mergeSort(dest, src, low, mid, -off);
	mergeSort(dest, src, mid, high, -off);

	// If list is already sorted, just copy from src to dest.  This is an
	// optimization that results in faster sorts for nearly ordered lists.
	if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
		System.arraycopy(src, low, dest, destLow, length);
		return;
	}

	// Merge sorted halves (now in src) into dest
	for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
		if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
			dest[i] = src[p++];
		else
			dest[i] = src[q++];
	}
}

2、否则调用ComparableTimSort或者TimSort类型进行排序(类似),所以只看 TimSort.sort 方法:长度小于32时使用,mini-TimSort【一种优化的二分插入排序算法】,否则使用优化的归并排序算法。

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
					 T[] work, int workBase, int workLen) {
	assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

	int nRemaining  = hi - lo;
	if (nRemaining < 2) // 长度小于2不用进行排序
		return;

	//如果数据长度小于32,使用"mini-TimSort"进行排序,可以认为是二分插入排序的优化
	if (nRemaining < MIN_MERGE) {
		int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
		binarySort(a, lo, hi, lo + initRunLen, c);
		return;
	}

	// 使用TimSort 优化的归并排序,归并本身就是分治和合并的过程,刚好符合栈的特性
	TimSort<T> 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);

	// Merge all remaining runs to complete sort
	assert lo == hi;
	ts.mergeForceCollapse();
	assert ts.stackSize == 1;
}

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值