Collections 工具类
Collections 工具类中有自带的sort()排序方法,比较好奇是怎么实现的,然后查看源码,发现不得不叹服这个方法,用尽了各种优化,使得能够自适应不同的特殊序列的排序,最好的情况时间复杂度O(n),最坏O(nlogn),目前最优的排序算法,而且还是稳定排序
用到了Array类中的sort方法
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);
}
}
当使用setProperty()方法设置LegacyMergeSort.userRequested为true时才会使用legacyMergeSort()传统的合并排序(该方法在以后版本会被弃用)
因此我们着重来看下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)
return; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
//当数组大小小于32时,使用后以下方法(优化的二分插入排序)
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
/**
* 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<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;
}
if (nRemaining < MIN_MERGE)
其中countRunAndMakeAscending()源码如下:作用是返回从lo开始连续升序(降序则会反转为升序)的元素个数,如1,2,3,8,1,2,3则返回3;降序同理
然后进行二分插入排序,binarySort()源码如下:
private static <T> void binarySort(T[] a, int lo, int hi, int start,
Comparator<? super T> c) {
assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
T pivot = a[start];
// Set left (and right) to the index where a[start] (pivot) belongs
int left = lo;
int right = start;
assert left <= right;
/*
* Invariants:
* pivot >= all in [lo, left).
* pivot < all in [right, start).
*/
while (left < right) {
int mid = (left + right) >>> 1;
if (c.compare(pivot, a[mid]) < 0)
right = mid;
else
left = mid + 1;
}
assert left == right;
/*
* The invariants still hold: pivot >= all in [lo, left) and
* pivot < all in [left, start), so pivot belongs at left. Note
* that if there are elements equal to pivot, left points to the
* first slot after them -- that's why this sort is stable.
* Slide elements over to make room for pivot.
*/
int n = start - left; // The number of elements to move
// Switch is just an optimization for arraycopy in default case
switch (n) {
case 2: a[left + 2] = a[left + 1];
case 1: a[left + 1] = a[left];
break;
default: System.arraycopy(a, left, a, left + 1, n);
}
a[left] = pivot;
}
}
start = lo + runlen
数组分为两段[lo, runlen]为升序,[runlen+1, hi]为乱序,然后降乱序的元素与升序元素比较,再排序
下面到minRunlength():
private static int minRunLength(int n) {
assert n >= 0;
int r = 0; // Becomes 1 if any 1 bits are shifted off
while (n >= MIN_MERGE) {
r |= (n & 1);
n >>= 1;
}
return n + r;
}
- 如果数组大小为2的N次幂,则返回16(MIN_MERGE / 2)
- 其他情况下,逐位向右位移(即除以2),直到找到介于16和32间的一个数
下面来看数组大小>32的情况
进入do…while循环,获取已为升序的元素个数
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);
在判断是否小于minRun,若小于使用binarySort来插入补足元素,runLen记录当前区块大小
再将[lo, runLen]区块压入栈中,然后stackSize+1
再进入mergeCollapse()方法中
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
}
}
}
当栈中的区块数>1时,进入while循环,令A,B, C分别为 = 区块1,区块2,区块3元素个数
- n>0(即区块数为3),且A <= B + C时,如果A < C,n–,不小于的话再进入mergeAt(n),对A,B,C进行合并
- 区块数为2的时候,如果A < B,将A和B merge
具体合并的方法如下:
private void mergeAt(int i) {
assert stackSize >= 2;
assert i >= 0;
assert i == stackSize - 2 || i == stackSize - 3;
int base1 = runBase[i];
int len1 = runLen[i];
int base2 = runBase[i + 1];
int len2 = runLen[i + 1];
assert len1 > 0 && len2 > 0;
assert base1 + len1 == base2;
/*
* Record the length of the combined runs; if i is the 3rd-last
* run now, also slide over the last run (which isn't involved
* in this merge). The current run (i+1) goes away in any case.
*/
runLen[i] = len1 + len2;
if (i == stackSize - 3) {
runBase[i + 1] = runBase[i + 2];
runLen[i + 1] = runLen[i + 2];
}
stackSize--;
/*
* Find where the first element of run2 goes in run1. Prior elements
* in run1 can be ignored (because they're already in place).
*/
//寻找区块1的第一个元素应当插入区块0中哪个位置,
//然后就可以忽略之前区块0的元素因为都比区块1的第一个元素小
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;
// 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);
}
以下通过一个示例来说明,如[1,4,5,8] (run1) , [2,6,7,9,12,13] (run2),
gallopRight():寻找run2的第一个元素应当插入run1中哪个位置
base:插入后的起始位置
len1:run1的长度
[base, len1]即为run1需要归并的区间
run2中的run2[0]应当插入run1[0]之后,所以k = 1,base = 1, len1 = len1 - k = 3
gallopLeft():寻找run1的最后一个元素应当插入run2中哪个位置
len2 = 插入的位置
[0,len2]为run2需要归并的区间
最后,根据两个需要归并的区间大小来使用相应的合并方法(用于节省空间)
/*
先把整个TimSort流程看完再来看该合并方法(主要原因是太长了)
*/
再回来看sort()方法里面最后几条语句
// Merge all remaining runs to complete sort
assert lo == hi;
ts.mergeForceCollapse();
assert ts.stackSize == 1;
合并最后剩下的单独的区间,完成排序
参考:
https://www.jianshu.com/p/892ebd063ad9
https://www.coder4.com/archives/4092