五種常見的排序算法實現
算法描述
1.插入排序

從第一個元素開始,該元素可以認為已經被排序
取出下一個元素,在已經排序的元素序列中從后向前掃描
如果該元素(已排序)大於新元素,將該元素移到下一位置
重復步驟3,直到找到已排序的元素小於或者等於新元素的位置
將新元素插入到該位置后
重復步驟2~5
在這個基礎上有衍生出提高效率的二分插入排序
2.冒泡排序

比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。
針對所有的元素重復以上的步驟,除了最后一個。
持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
最簡單的排序,名字很形象,排序元素會呈現上浮或者下沉的特點,
3.希爾排序

先取一個正整數 d1(d1 < n),把全部記錄分成 d1 個組,所有距離為 d1 的倍數的記錄看成一組,然后在各組內進行插入排序
然后取 d2(d2 < d1)
重復上述分組和排序操作;直到取 di = 1(i >= 1) 位置,即所有記錄成為一個組,最后對這個組進行插入排序。一般選 d1 約為 n/2,d2 為 d1 /2, d3 為 d2/2 ,…, di = 1。 希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
但插入排序一般來說是低效的, 因為插入排序每次只能將數據移動一位
4.堆排序

堆的特點
通常堆是通過一維數組來實現的。在數組起始位置為0的情形中:
父節點i的左子節點在位置(2*i+1);
父節點i的右子節點在位置(2*i+2);
子節點i的父節點在位置floor((i-1)/2);
在堆的數據結構中,堆中的最大值總是位於根節點(在優先隊列中使用堆的話堆中的最小值位於根節點)。堆中定義以下幾種操作:
最大堆調整(Max_Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點
創建最大堆(Build_Max_Heap):將堆所有數據重新排序
堆排序(HeapSort):移除位在第一個數據的根節點,並做最大堆調整的遞歸運算
5.歸並排序

迭代法
申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
重復步驟3直到某一指針到達序列尾
將另一序列剩下的所有元素直接復制到合並序列尾
遞歸法
原理如下(假設序列共有n個元素):
將序列每相鄰兩個數字進行歸並操作,形成 {\displaystyle floor(n/2)} floor(n/2)個序列,排序后每個序列包含兩個元素
將上述序列再次歸並,形成 {\displaystyle floor(n/4)} floor(n/4)個序列,每個序列包含四個元素
重復步驟2,直到所有元素排序完畢
6.快速排序

從數列中挑出一個元素,稱為”基准”(pivot),
重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。
遞歸地(recursive)把小於基准值元素的子數列和大於基准值元素的子數列排序。
性能測試的結果
少量數據
insertionSort正序數列:808000ns
insertionSort倒序數列:793900ns
insertionSort隨機數列:448300ns
insertionSort周期數列:92400ns
bubbleSort正序數列:461500ns
bubbleSort倒序數列:856000ns
bubbleSort隨機數列:539500ns
bubbleSort周期數列:140100ns
shellSort正序數列:34000ns
shellSort倒序數列:55600ns
shellSort隨機數列:67900ns
shellSort周期數列:35700ns
heapSort正序數列:175700ns
heapSort倒序數列:89500ns
heapSort隨機數列:37100ns
heapSort周期數列:33800ns
mergeSort正序數列:123100ns
mergeSort倒序數列:118400ns
mergeSort隨機數列:112100ns
mergeSort周期數列:55100ns
quickSort正序數列:62100ns
quickSort倒序數列:80200ns
quickSort隨機數列:78900ns
quickSort周期數列:65200ns
大量數據
insertionSort正序數列:1599364000ns
insertionSort倒序數列:1586157400ns
insertionSort隨機數列:869768700ns
insertionSort周期數列:593637800ns
bubbleSort正序數列:499915700ns
bubbleSort倒序數列:957839500ns
bubbleSort隨機數列:3328836500ns
bubbleSort周期數列:495209600ns
shellSort正序數列:3259400ns
shellSort倒序數列:4043100ns
shellSort隨機數列:6653900ns
shellSort周期數列:741600ns
heapSort正序數列:3603700ns
heapSort倒序數列:4112400ns
heapSort隨機數列:5584600ns
heapSort周期數列:3804700ns
mergeSort正序數列:5240900ns
mergeSort倒序數列:2643600ns
mergeSort隨機數列:6001700ns
mergeSort周期數列:2989600ns
quickSort正序數列:2126000ns
quickSort倒序數列:3228400ns
quickSort隨機數列:5726800ns
quickSort周期數列:2121000ns
/*
* Sort.java
* Version 1.0.0
* Created on 2017年4月27日
* Copyright ReYo.Cn
*/
package reyo.sdk.utils.test.map;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Sort {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Sort s1 = new Sort();
System.out.println("少量數據");
int arraySize = 200;
s1.test("insertionSort", arraySize);
s1.test("bubbleSort", arraySize);
s1.test("shellSort", arraySize);
s1.test("heapSort", arraySize);
s1.test("mergeSort", arraySize);
s1.test("quickSort", arraySize);
System.out.println("大量數據");
arraySize = 50000;
s1.test("insertionSort", arraySize);
s1.test("bubbleSort", arraySize);
s1.test("shellSort", arraySize);
s1.test("heapSort", arraySize);
s1.test("mergeSort", arraySize);
s1.test("quickSort", arraySize);
}
// 測試的腳手架
public void test(String methodName, int size) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method m1 = this.getClass().getMethod(methodName, int[].class);
int[] test1 = new int[size];
int[] test2 = new int[size];
int[] test3 = new int[size];
int[] test4 = new int[size];
Sort sorttest = new Sort();
// 正序
for (int i = 0; i < size; i++) {
test1[i] = i;
}
// 倒序
for (int i = 0; i < size; i++) {
test2[i] = size - i;
}
// 亂序,隨機,基本無重復元素
for (int i = 0; i < size; i++) {
test3[i] = (int) (Math.random() * size);
}
// 大量重復元素
for (int i = 0; i < size; i++) {
test4[i] = i + 50 % 50;
}
long startTime = System.nanoTime();
long endTime = System.nanoTime();
m1.invoke(sorttest, test1);
startTime = System.nanoTime();
m1.invoke(sorttest, test1);
endTime = System.nanoTime();
System.out.println(methodName + "正序數列:" + (endTime - startTime) + "ns");
startTime = System.nanoTime();
m1.invoke(sorttest, test2);
endTime = System.nanoTime();
System.out.println(methodName + "倒序數列:" + (endTime - startTime) + "ns");
startTime = System.nanoTime();
m1.invoke(sorttest, test3);
endTime = System.nanoTime();
System.out.println(methodName + "隨機數列:" + (endTime - startTime) + "ns");
startTime = System.nanoTime();
m1.invoke(sorttest, test4);
endTime = System.nanoTime();
System.out.println(methodName + "周期數列:" + (endTime - startTime) + "ns");
System.out.println();
}
// 插入排序,穩定排序
// 插入排序由N-1趟排序組成,時間最好O(n),最壞O(n2),平均O(n2)
// 空間O(1)
public void insertionSort(int[] nums) {
int j, p;
int tmp;
for (p = 1; p < nums.length; p++) {
tmp = nums[p];
for (j = p; j > 0; j--) {
if (nums[j - 1] > tmp)
nums[j] = nums[j - 1];
}
nums[j] = tmp;
}
}
// 冒泡排序,穩定排序
// 時間最好O(n),最壞O(n2),平均O(n2)
// 空間O(1)
public void bubbleSort(int[] nums) {
int j, p;
int tmp;
// 沉水,大數被移動到尾段
for (p = 0; p < nums.length - 1; p++) {
for (j = 0; j < nums.length - 1 - p; j++) {
if (nums[j] > nums[j + 1]) {
tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
// //氣泡,小數浮動到首段
// for(p = 0; p < nums.length - 1;p++){
// for(j = nums.length - 1;j > p;j--){
// if(nums[j-1] > nums[j]){
// tmp = nums[j];
// nums[j] = nums[j-1];
// nums[j-1] = tmp;
// }
// }
// }
}
// 希爾排序(縮小增量排序),不穩定排序
// 屬於插入排序,時間最好O(n),最壞O(n2),平均O(n1.3)
// 空間O(1)
public void shellSort(int[] nums) {
int gap = 1; // 增量
int i, j;
int len = nums.length;
int tmp;
// 初始增量,shell排序的效率與增量設定有很大關系
while (gap < len / 3)
gap = gap * 3 + 1;
for (; gap > 0; gap /= 3) {
for (i = gap; i < len; i++) {
tmp = nums[i];
for (j = i - gap; j >= 0 && nums[j] > tmp; j -= gap)
nums[j + gap] = nums[j];
nums[j + gap] = tmp;
}
}
}
// 堆排序,不穩定排序
// 時間 平均2nlogn - O(nlogn)
// 空間O(1)
// 排序方式
// 創建一個堆H[0..n-1]
// 把堆首(最大值)和堆尾互換
// 把堆的尺寸縮小1,並調用shift_down(0),目的是把新的數組頂端數據調整到相應位置
// 重復步驟2,直到堆的尺寸為1
public void heapSort(int[] nums) {
int i;
// 遍歷所有結點,對不滿足規則節點進行調整
for (i = nums.length / 2; i >= 0; i--) {
PercDown(nums, i, nums.length);
}
for (i = nums.length - 1; i > 0; i--) {
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
PercDown(nums, 0, i);
}
}
// 調整堆序
public void PercDown(int[] nums, int i, int length) {
int child;
int tmp;
for (tmp = nums[i]; 2 * i + 1 < length; i = child) {
child = 2 * i + 1; // 左兒子
if (child != length - 1 && nums[child + 1] > nums[child])
child++; // 選出左右子結點中的較大值
if (tmp < nums[child]) { // 父節點小於子節點,需要進行調整
nums[i] = nums[child];
} else
break;
}
nums[i] = tmp;
}
// 歸並排序
public void mergeSort(int[] nums) {
int[] tmpArray = new int[nums.length];
mSort(nums, tmpArray, 0, nums.length - 1);
}
public void mSort(int[] nums, int[] tmps, int left, int right) {
int center;
if (left < right) {
center = (left + right) / 2;
mSort(nums, tmps, left, center);
mSort(nums, tmps, center + 1, right);
merge(nums, tmps, left, center + 1, right);
}
}
// 空間歸並
public void merge(int[] nums, int[] tmps, int lpos, int rpos, int rightEnd) {
int i, leftEnd, numElements, tmpPos;
leftEnd = rpos - 1;
tmpPos = lpos;
numElements = rightEnd - lpos + 1;
while (lpos <= leftEnd && rpos <= rightEnd) {
if (nums[lpos] <= nums[rpos])
tmps[tmpPos++] = nums[lpos++];
else
tmps[tmpPos++] = nums[rpos++];
}
while (lpos <= leftEnd)
tmps[tmpPos++] = nums[lpos++];
while (rpos <= rightEnd)
tmps[tmpPos++] = nums[rpos++];
for (i = 0; i < numElements; i++, rightEnd--)
nums[rightEnd] = tmps[rightEnd];
}
// 快速排序
// 理論上能保證不出現最壞情況:三數中值分割法
// 小規模排序,快速排序不如插入排序
public void quickSort(int[] nums) {
qSort(nums, 0, nums.length - 1);
}
// 遞歸快速排序
public void qSort(int[] nums, int left, int right) {
int i, j;
int pivot;
if (left + 3 <= right) { // 至少有4個數
pivot = median3(nums, left, right);
i = left;
j = right - 1;
while (true) {
// 先推進索引再判斷,而不是先判斷再決定要不要推進索引
// 這樣避免了a[i]=a[j]=pivot,導致索引不會被推進,也不會跳出循環
while (nums[++i] < pivot) {
}
while (nums[--j] > pivot) {
}
if (i < j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
} else
break;
}
int tmp = nums[i];
nums[i] = nums[right - 1];
nums[right - 1] = tmp;
qSort(nums, left, i - 1);
qSort(nums, i + 1, right);
}
// 少於4個數,不再分割,用插入排序直接排序
else {
int k, p;
int tmp;
for (p = 1; p < right - left + 1; p++) {
tmp = nums[left + p];
for (k = p; k > 0; k--) {
if (nums[left + k - 1] > tmp)
nums[left + k] = nums[left + k - 1];
}
nums[left + k] = tmp;
}
}
}
// 分割數組
public int median3(int[] nums, int left, int right) {
int center = (left + right) / 2;
if (nums[left] > nums[center]) {
int tmp = nums[left];
nums[left] = nums[center];
nums[center] = tmp;
}
if (nums[left] > nums[right]) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
if (nums[center] > nums[right]) {
int tmp = nums[center];
nums[center] = nums[right];
nums[right] = tmp;
}
int tmp = nums[center];
nums[center] = nums[right - 1];
nums[right - 1] = tmp;
return nums[right - 1];
}
}
