1、常见的排序算法
常见的排序算法有选择排序、插入排序、冒泡排序等基本的排序算法
高级一点的排序算法有快速排序算法,核心思想是从数组中挑选一个元素,使这个元素在正确的位置,比如左边的元素都不大于它,右边的元素都不小于它,一次递归,到最后子数组的大小为2或为1的时候,子数组有有序了,整个大数组就有序了。
归并排序算法,核心思想是将两个有序的数组的数组合并成一个数组。
希尔算法,整个算法比较特殊,就是算法的复杂度不是很确定。
详见 我的项目 https://gitee.com/dahaizhenqiang/algorithms-learn4.git
2.jdk中的算法
在jdk排序的时候,常见的就是Collections.sort() 和Arrays.sort()这两个静态方法,首先我们看Collections.sort(),一下jdk源码是jdk1.8
@SuppressWarnings({"unchecked", "rawtypes"})
public static void sort(List list, Comparator super T> c) {
list.sort(c);
}
Comparator接口是需要自己实现的,list中的元素根据compare()方法来排序,该方法调用的的是list.sort();继续跟进
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
发现最终调用的也是Arrays.sort()方法,所以下面我们重点看Arrays.sort()方法,经断点调试,对Arraylist
会进入到一下的方法
public static 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);
}
}
其中传统的legcyMergeSort.userRequest的开关默认为false,我们看看legacyMergeRequest();
@SuppressWarnings({"rawtypes", "unchecked"})
private static void mergeSort(Object[] src,
Object[] dest,
int low, int high, int off,
Comparator c) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; ilow && c.compare(dest[j-1], 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, c);
mergeSort(dest, src, mid, high, -off, c);
// 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 (c.compare(src[mid-1], 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 && c.compare(src[p], src[q]) <= 0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
当需要排序的数组比较小的时候,直接采用Insertion sort on smallest arrays插入排序算法,下面的就是基于递归的归并算法了。
默认情况下调用的不是legacyMergeRequest(),而是调用 TimSort.sort(a, 0, a.length, c, null, 0, 0);代码如下
static 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
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 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;
}
当数组中的元素个数比较小的时候这里是个数小于32时,调用binarySort()方法,注意这个算法并不是二叉树推排序,
而是折半插入排序,
@SuppressWarnings("fallthrough")
private static 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;
}
}
设有一个序列a[0],a[1]...a[n];其中a[i-1]前是已经有序的,当插入时a[i]时,利用二分法搜索a[i]插入的位置,即插入的数组是有一个基本有序的数组。
而TimSort.sort()整体上为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果,总结就是合并算法的插入算法的结合。
更加详细的timSort排序算法的信息查看 https://blog.csdn.net/yangzhongblog/article/details/8184707
3.总结,算法很多,对于实际的程序应用情况,往往不会单纯的只用一种排序算法,而是多个排序算法的优化和结合,这就是算法知识的应用吧