本文測試資料:
private static int[] arr = {8, 3, 5, 55, 7, 22, 32, 99};
複製程式碼
一、氣泡排序
1.第一版:
迴圈28次----移動6次
private static void bubbleSort(int arr[]) {
int n = arr.length - 1;
int i, j, t;
for (i = 0; i < n; i++) {//遍歷陣列
for (j = i + 1; j <= n; j++) {//內層遍歷,從i的下一個
if (arr[i] > arr[j]) {// 陣列i元素大,則交換
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
}
}
複製程式碼
i=0,第一趟
j 從 1 開始,arr[0]>arr[1],交換位置,保證大的數在後面,a[0]=3
j +1 = 2 , 比較a[2]和a[0],arr[0]<arr[2],繼續
然後j +1 =3 ,就這樣依次將j右移和a[0]比較,這樣到最後一個可保證a[0]是最小的
複製程式碼
i=1,第二趟
j 從 2 開始,arr[1]>arr[2],交換位置,保證大的數在後面,a[1]=5
j +1 = 2 , 比較a[3]和a[1],arr[1]<arr[3],繼續
然後j +1 =3 ,就這樣依次將j右移和a[0]比較,這樣到最後,可保證a[0],a[1]正確排序
複製程式碼
然後同理,經過n趟,小的數移到了前面
2.相鄰元素冒泡
迴圈28次----移動6次
private static void bubbleSort1(int[] arr) {
int n = arr.length - 1;
int i, j, t;
for (i = 0; i < n; i++) {
for (j = n; j > i; j--) {
if (arr[j - 1] > arr[j]) {// 相鄰元素兩兩對比,如果前者大,交換位置
t = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = t;
}
}
}
}
複製程式碼
i=0,第一趟
相鄰兩個元素比較,小的往左走
i=1,第二趟
相鄰兩個元素比較,小的往左走
3.優化版冒泡
迴圈22次----移動6次
// 平均T複雜度 穩定 最好 最壞 輔助空間
//氣泡排序: O(n^2) YES O(n) O(n^2) O(1)
private static void bubbleSort2(int[] arr) {
int n = arr.length - 1;
int i, j, t;
boolean flag = true;
for (i = 0; i < n && flag; i++) {
//如果退出j迴圈時flag=false,說明本迴圈沒有元素交換,說明已排序完成
for (j = n; j > i; j--) {
flag = false;
if (arr[j - 1] > arr[j]) {// 相鄰元素兩兩對比,如果前者大,交換位置
t = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = t;
flag = true;
}
}
}
}
複製程式碼
二、選擇排序
迴圈28次----移動6次
// 平均T複雜度 穩定 最好 最壞 輔助空間
//選擇排序: O(n^2) YES O(n^2) O(n^2) O(1)
public static void selectSort(int[] arr) {
int n = arr.length - 1;
int i, j, t, min;
for (i = 0; i < n; i++) {
min = i;
for (j = i + 1; j <= n; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if (min != i) {//如果min和i相同,沒必要交換
t = arr[i];//交換
arr[i] = arr[min];
arr[min] = t;
}
}
}
複製程式碼
在j迴圈中記錄發現的最小值所在索引,如果min不是i,交換元素
三、插入排序
1.具體演算法
// 平均T複雜度 穩定 最好 最壞 輔助空間
//插入排序: O(n^2) YES O(n) O(n^2) O(1)
public static int[] insertSort(int[] arr) {
int i, j, t;
int n = arr.length;
for (i = 1; i < n; i++) {//從i點開始遍歷陣列
t = arr[i];//使用temp變數記錄i索引所在值
for (j = i; j > 0 && t < arr[j - 1]; j--) {//從i點向左遍歷
arr[j] = arr[j - 1];//當遇到比temp大的數,就將前一個元素後推
}
arr[j] = t;//否則j位變成t
}
return arr;
}
複製程式碼
2.分析
第一輪排序
i,j從索引1開始, t=3 ,
t與j前一個元素比較, 3<8 , j處值變成較大值 8 , j 向左指一位
跳出j的for迴圈後,將j索引置為剛才儲存的臨時變數t=3,此時前兩個元素排序完成。
複製程式碼
第二輪排序
i索引右移一格到2,t= 5 ,j 從索引2開始
t與j前一個元素比較, 5 < 8 , j處值變成較大值 8 , j 向左指一位
t與j前一個元素比較, 5 > 3 , 跳出j迴圈
將j索引置為剛才儲存的臨時變數t=3,此時前3個元素排序完成。
複製程式碼
第三輪排序
i索引右移一格到3,t= 55 ,j 從索引3開始
t與j前一個元素比較, 55 > 8 , 跳出j迴圈
將j索引置為剛才儲存的臨時變數t=55,此時前4個元素排序完成。
複製程式碼
第四輪排序
i索引右移一格到4,t= 7 ,j 從索引4開始
t與j前一個元素比較, 7 <55 , j處值變成較大值 55 , j 向左指一位 為3
t與j前一個元素比較, 7 < 8 , j處值變成較大值 8 , j 向左指一位 為2
t與j前一個元素比較, 7 > 5 , 跳出j迴圈
將j索引置為剛才儲存的臨時變數t=7,此時前5個元素排序完成。
複製程式碼
插入排序第n輪排序後將前n+1個元素是順序的
四、希爾排序
試了一下開篇中100W的隨機整數,果然不愧為N*logN的演算法, 0.365秒
// 平均T複雜度 穩定 最好 最壞 輔助空間
//希爾排序:O(nlogn) NO O(n^1.3) O(n^2) O(1)
public static void shellSort(int[] arr) {
int i, j, t , gap;
int n = arr.length;
for (gap = n / 2; gap > 0; gap /= 2) {
for (i = gap; i < n; i++) {
t = arr[i];
for (j = i; j >= gap && t < arr[j - gap]; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = t;
}
}
}
複製程式碼
希爾排序先將資料歸整,最後gap=1時,相當於歸整後的插入排序
開始gap取4,圖中相同顏色進行比較,用的是插入排序交換的套路
然後gap減半=2,圖中相同顏色進行比較,用的是插入排序交換的套路
然後gap減半=1,圖中相同顏色進行比較,用的是插入排序交換的套路
複製程式碼
五、堆排序
1.具體演算法
試了一下開篇中100W的隨機整數,果然不愧為N*logN的演算法, 0.286秒
// 平均T複雜度 穩定 最好 最壞 輔助空間
//堆排序:O(nlogn) NO O(nlogn) O(nlogn) O(1)
private static void heapSort(int[] arr) {//左半的元素
for (int i = arr.length / 2 - 1; i >= 0; i--) {
perDownHeap(arr, i, arr.length);//形成堆
}
for (int i = arr.length - 1; i > 0; i--) {
swapRef(arr, 0, i);//交換首
perDownHeap(arr, 0, i);//維持堆形狀
}
}
private static void swapRef(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
private static void perDownHeap(int[] arr, int i, int n) {
int child;
int t;
for (t = arr[i]; leftChild(i) < n; i = child) {
child = leftChild(i);
if (child != n - 1 && arr[child] < arr[child + 1]) {
child++;
}
if (t < arr[child]) {
arr[i] = arr[child];
} else {
break;
}
}
arr[i] = t;
}
複製程式碼
2.分析
第一次執行的是:perDownHeap(arr, 3, arr.length);
private static void perDownHeap(int[] arr, int i, int n) {
int child;//0
int t;//55
for (t = arr[i]; leftChild(i) < n; i = child) {
child = leftChild(i);//7 arr[child]=99 即 55的左子是99
if (child != n - 1 && arr[child] < arr[child + 1]) {//此時條件不滿足
child++;
}
if (t < arr[child]) {//走這裡 55<99 條件滿足
arr[i] = arr[child];//arr[3]=99
} else {
break;
}
}
arr[i] = t;//跳出迴圈, i = child ,arr[7]=t=55
}
第二次執行的是:perDownHeap(arr, 2, arr.length);
private static void perDownHeap(int[] arr, int i, int n) {
int child;//0
int t;//5
for (t = arr[i]; leftChild(i) < n; i = child) {
child = leftChild(i);//5 arr[child]=22 即 5的左子是22 ,右子是arr[child + 1]=32
if (child != n - 1 && arr[child] < arr[child + 1]) {//此時條件滿足
child++;//child = 6
}
if (t < arr[child]) {//走這裡 5<32 條件滿足
arr[i] = arr[child];//arr[2]=32
} else {
break;
}
}
arr[i] = t;//跳出迴圈, i = child ,arr[6]=t=5
}
>其他同上...繪圖如下
複製程式碼
接下來通過交換根節點和i,再對堆進行沉浮,形成小頂堆
for (int i = arr.length - 1; i > 0; i--) {
swapRef(arr, 0, i);
perDownHeap(arr, 0, i);
}
複製程式碼
六、歸併排序
1.具體演算法
private static void mergeSort(int[] arr) {
int[] temArr = new int[arr.length];//臨時陣列
mergeSort(arr, temArr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int[] temArr, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(arr, temArr, left, center);//左歸併,使得左子序列有序
mergeSort(arr, temArr, center + 1, right);//右歸併,使得右子序列有序
merge(arr, temArr, left, center + 1, right);//兩個子序列合併
}
}
private static void merge(int[] arr, int[] temArr, int leftPos, int rightPos,
int leftEnd = rightPos - 1;//左起點比右終點大1
int tempPos = leftPos;//臨時陣列索引
int count = rightEnd - leftPos + 1;//此次合併的元素個數
while (leftPos <= leftEnd && rightPos <= rightEnd) {//說明到頭了
boolean rightBig = arr[leftPos] <= arr[rightPos];//左邊大?
//temArr[tempPos]取較小者,tempPos和較小者索引++
temArr[tempPos++] = rightBig ? arr[leftPos++] : arr[rightPos++];
}
while (leftPos <= leftEnd) {
temArr[tempPos++] = arr[leftPos++];
}
while (rightPos <= rightEnd) {
temArr[tempPos++] = arr[rightPos++];
}
for (int i = 0; i < count; i++, rightEnd--) {//將臨時陣列元素放到原陣列
arr[rightEnd] = temArr[rightEnd];
}
}
複製程式碼
2.分析
|--- 第一次merge: merge(arr, temArr, 0, 1, 1)
leftPos:0
leftEnd:0
arr[leftPos]:8
rightPos:1
rightEnd:1
arr[rightPos]:3
temArr[0] = arr[leftPos]和arr[rightPos]的較小者:arr[rightPos]=3
temArr[1] = arr[leftPos++] = 8
將臨時陣列元素放到原陣列
|--- 第二次merge: merge(arr, temArr, 2, 3, 3)
leftPos:2
leftEnd: 2
arr[leftPos]:5
rightPos:3
rightEnd:3
arr[rightPos]:55
temArr[0] = arr[leftPos]和arr[rightPos]的較小者 arr[leftPos]=5
temArr[1] = arr[rightPos++] = 55
將臨時陣列元素放到原陣列
|--- 第三次merge: merge(arr, temArr, 0, 2, 3)
leftPos:0
leftEnd: 1
arr[leftPos]:3
rightPos:2
rightEnd:3
arr[rightPos]:5
見下圖...
複製程式碼
第三次 merge
七、快速排序
1.具體演算法
// 平均T複雜度 穩定 最好 最壞 輔助空間
//快速排序:O(nlogn) NO O(nlogn) O(n^2) O(n)~O(nlogn)
public static void fastSort(int[] arr) {
fastSort(arr, 0, arr.length - 1);
}
public static void fastSort(int[] arr, int start, int end) {
int i, j, key, t;
if (start >= end) {
return;
}
i = start + 1;
j = end;
key = arr[start];//基準位
while (i < j) {//保證i比j大
//當j處值<=key,j向←--走 :說明從右找到到第一個大於key的數
while (key <= arr[j] && i < j) j--;
//當i處值>=key,i向--→走 :說明從右找到到第一個小於key的數
while (key >= arr[i] && i < j) i++;
if (i < j) {//交換
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
arr[start] = arr[i];//交換基準位
arr[i] = key;
fastSort(arr, start, j - 1);//左半
fastSort(arr, j + 1, end);//右半
}
複製程式碼
分析:
Q1:第一趟sort
此時呼叫了:
sort(arr, 0, 2);//對左半元素快速排序
複製程式碼
Q1.1:Q1左半sort
Q1.1.1:Q1.1左半sort
-
Q1.1.2:Q1.1右半sort
:一個元素不用排序,直接return -
Q1.2:Q1右半sort
:套路一樣,就不分析了
小結:沒有最好的排序,只有最適合的排序。
條目 | 平均T複雜度 | 穩定 | 最好 | 最壞 | 輔助空間 |
---|---|---|---|---|---|
氣泡排序 | O(n^2) | YES | O(n) | O(n^2) | O(1) |
選擇排序 | O(n^2) | NO | O(n^2) | O(n^2) | O(1) |
插入排序 | O(n^2) | YES | O(n) | O(n^2) | O(1) |
希爾排序 | O(nlogn) | NO | O(n^1.3) | O(n^2) | O(1) |
堆排序 | O(nlogn) | NO | O(nlogn) | O(nlogn) | O(1) |
歸併排序 | O(nlogn) | YES | O(nlogn) | O(nlogn) | O(n) |
快速排序 | O(nlogn) | NO | O(nlogn) | O(n^2) | O(n)~O(nlogn) |
資料量較小時:氣泡排序,選擇排序,插入排序就可以了
資料量非常大時:最好用O(nlogn)複雜度的演算法
如果資料基本是有序的:插入排序
資料隨機性很大:快速排序
需要要求穩定性且O(nlogn):歸併排序是唯一的選擇,代價是需要多消耗一倍空間
如果不想出現最壞情況,也就是想一切在預期之內:堆排序是你不二的選擇
複製程式碼
排序的穩定性:大小相同的元素在排序前後順序有沒有可能改變
設:Ki=Kj (1<=i<=n,1<=j<=n,i!=j)且在排序前ri先於rj(即i<j)
|--- 排序後ri仍先於rj,則排序方法穩定,反之,不穩定
複製程式碼
下面用七張圖記錄一下:
ok,就先這樣,以後有想到什麼再補充