冒泡排序
依次比較相鄰的兩個數,將小數放在前面,大數放在後面。即首先比較第1個和第2個數,將小數放前,大數放後。然後比較第2個數和第3個數,將小數放前,大數放後,如此繼續,直至比較最後兩個數,將小數放前,大數放後。重複以上過程,仍從第一對數開始比較(因爲可能由於第2個數和第3個數的交換,使得第1個數不再大於第2個數),將小數放前,大數放後,一直比較到最小數前的一對相鄰數,將小數放前,大數放後,第二趟結束,在倒數第二個數中得到一個新的最小數。如此下去,直至最終完成排序。
由於在排序過程中總是小數往前放,大數往後放,相當於氣泡往上升,所以稱作冒泡排序。
冒泡排序動態圖:
冒泡排序核心思路代碼:
public static void sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int minVal = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = minVal;
}
}
}
}
全部代碼:
package com.ggsddu.domain.sort;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class BubbleSort {
public static void main(String[] args) {
System.out.print("輸入數組長度:");
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] arr = new int[n];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(100);
}
scanner.close();
System.out.println("排序前:" + Arrays.toString(arr));
System.out.println("--------------------- 排序結束 ---------------------");
sort(arr);
System.out.println("--------------------- 排序結束 ---------------------");
System.out.println("排序後:" + Arrays.toString(arr));
}
public static void sort(int[] arr) {
/**
* 假設數組有10個元素,外層循環9次,每輪排序將此輪參與比較元素的最大值“冒泡”出去
* ----- 排序開始
* 第一輪排序:從10個元素中比較出最大值,將最大值“冒泡”到數組arr[9]的位置
* 第二輪排序:從9個元素中(已“冒泡”元素不參與後續排序)比較出最大值,“冒泡”到數組arr[8]的位置
* 第三輪排序:從8個元素中比較出最大值,“冒泡”到最後數組arr[7]的位置
* ...
* 第八輪排序:從3個元素中比較出最大值,“冒泡”到最後數組arr[2]的位置
* 第九輪排序:從2個元素中比較最大值,“冒泡”到數組arr[1]的位置,則arr[0]最小不動,排在arr[0]
* ----- 排序結束
*/
for (int i = 0; i < arr.length - 1; i++) {
/**
* 內層循環控制當前輪排序的參與比較的元素(已“冒泡”元素不再參與後續排序)
* 第一輪排序,參與排序的元素個數爲arr.length - 1 - 0
* 第二輪排序,參與排序的元素個數爲arr.length - 1 - 1
* 第三輪排序,參與排序的元素個數爲arr.length - 1 - 2
* ...
* 第i輪排序,參與排序的元素個數爲arr.length - 1 - i
*/
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int minVal = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = minVal;
}
}
System.out.println("第 " + (i + 1) + " 次排序:" + Arrays.toString(arr));
}
}
}
控制檯打印結果:
輸入數組長度:10
排序前:[64, 89, 36, 57, 82, 70, 40, 61, 81, 15]
--------------------- 排序開始 ---------------------
第 1 次排序:[64, 36, 57, 82, 70, 40, 61, 81, 15, 89]
第 2 次排序:[36, 57, 64, 70, 40, 61, 81, 15, 82, 89]
第 3 次排序:[36, 57, 64, 40, 61, 70, 15, 81, 82, 89]
第 4 次排序:[36, 57, 40, 61, 64, 15, 70, 81, 82, 89]
第 5 次排序:[36, 40, 57, 61, 15, 64, 70, 81, 82, 89]
第 6 次排序:[36, 40, 57, 15, 61, 64, 70, 81, 82, 89]
第 7 次排序:[36, 40, 15, 57, 61, 64, 70, 81, 82, 89]
第 8 次排序:[36, 15, 40, 57, 61, 64, 70, 81, 82, 89]
第 9 次排序:[15, 36, 40, 57, 61, 64, 70, 81, 82, 89]
--------------------- 排序結束 ---------------------
排序後:[15, 36, 40, 57, 61, 64, 70, 81, 82, 89]
Process finished with exit code 0
快速排序
快速排序由
C. A. R. Hoare
在1962年提出。它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
借一張大佬的圖,傳送門:http://blog.csdn.net/it_zjyang/article/details/53406764
初始數組:[3, 7, 2, 9, 1, 4, 6, 8, 10, 5]
流程模擬:
初始數組 arr
:[3, 7, 2, 9, 1, 4, 6, 8, 10, 5],期望結果:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
選定數組的最後一個元素
5
作爲基準值,也就是最終排序結果
應該是以5
爲界限劃分爲左右兩邊且有序從左邊開始,尋找比
5
大的值,然後與5
進行調換(比5
小的值保持原狀,排在5
前面,比5
大的值與5
調換位置排5
的後面),此輪找到7
,將7
與5
調換,結束此次遍歷。從右邊開始,由於
7
已經是上一輪排好序的便不再動它,此輪從10
開始,向左遍歷,尋找比5
小的值,然後與5
進行調換(比5
大的值保持原狀,排在5
後面,比5
小的值與5
調換位置排5
的後前面),此輪找到4
,將4
與5
調換,結束此次遍歷。從左邊開始,由於
3
和4
都是前兩輪已經排好序的便不再動它,從2
開始,向右遍歷,尋找比5
大的值,然後與5
進行調換(道理同步驟1
),此輪找到9
,將9
與5
調換位置,結束此次遍歷。從右邊開始,從
1
開始,向右遍歷,尋找比5
小的值,然後與5
進行調換(道理同步驟2
),此輪找到1
,將1
與5
調換,結束此次遍歷。這個時候,
5
的左右兩側符合左側值<=5
,右側值>=5
,所以結束此輪排序。5
的左右兩側繼續抽出來各自進行下一輪的排序,規則同上,直到無法再拆分下去,即完成了整體的快速排序。
快速排序核心之一:
public static int divide(int[] arr, int start, int end) {
int base = arr[end];
while (start < end) {
while (start < end && arr[start] <= base) {
start++;
}
if (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
end--;
}
while (start < end && arr[end] >= base) {
end--;
}
if (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
}
}
int borderPostion = start == end ? start : -1;
return borderPostion;
}
快速排序核心之二:
public static void sort(int[] arr, int start, int end) {
if (start > end) {
return;
} else {
int borderPostion = divide(arr, start, end);
sort(arr, start, borderPostion - 1);
sort(arr, borderPostion + 1, end);
}
}
完整代碼如下:
package com.ggsddu.domain.sort;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[]{3, 7, 2, 9, 1, 4, 6, 8, 10, 5};
System.out.println("排序前:" + Arrays.toString(arr));
sort(arr, 0, arr.length - 1);
System.out.println("排序後:" + Arrays.toString(arr));
}
/**
* 快速排序
*
* @param arr
* @param start
* @param end
*/
public static void sort(int[] arr, int start, int end) {
if (start > end) {
return;
} else {
// 如果不止一個元素,繼續劃分兩邊遞歸排序下去
int borderPostion = divide(arr, start, end);
System.out.print(borderPostion + " ");// 以某個基準值爲界每一次劃分元素排序後,此基準值所在位置
System.out.println(Arrays.toString(arr));
sort(arr, start, borderPostion - 1);// 基準值左側元素遞歸排序
sort(arr, borderPostion + 1, end);// 基準值右側元素遞歸排序
}
}
/**
* 對數組某一段元素進行劃分
* 以基準值爲界,左側數都大於基準值,右側數都大於基準值
* 形象表示爲:[{A|num<=基準值} 基準值 {B|num>=基準值}]
*
* @param arr
* @param start 開始遍歷的位置
* @param end 基準值的初始位置
* @return 每一次劃分元素,基準值所在位置
*/
public static int divide(int[] arr, int start, int end) {
int base = arr[end];// 默認以最右邊的元素作爲基準值
// 一旦start等於end,就說明左右兩個指針合併到了同一位置,可以結束此輪循環
while (start < end) {
while (start < end && arr[start] <= base) {
start++;// 從左邊開始向右遍歷,如果如果遍歷的值比基準值小,下標start就繼續向右走
}
// 上面的while循環結束時,就說明當前的arr[start]的值比基準值大,應與基準值位置進行交換
if (start < end) {
//交換
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
// 此值與基準值交換位置後,被“排序”,並且“佔領”了原end的位置,排序範圍縮小,end向前移動一位
end--;
}
while (start < end && arr[end] >= base) {
end--;// 從右邊開始遍歷,如果遍歷的值比基準值大,下標end就繼續向左走
}
// 上面的while循環結束時,就說明當前的arr[end]的值比基準值小,應與基準值進行交換
if (start < end) {
// 交換
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
// 此值與基準值交換後,被“排序”,並且“佔領”原start的位置,排序範圍縮小,所以start同時向後移動一位
start++;
}
}
// 返回start或end皆可,因爲此時start和end都爲基準值所在的位置
int borderPostion = start == end ? start : -1;
return borderPostion;
}
}
控制檯:
排序前:[3, 7, 2, 9, 1, 4, 6, 8, 10, 5]
4 [3, 4, 2, 1, 5, 9, 6, 8, 10, 7]
0 [1, 4, 2, 3, 5, 9, 6, 8, 10, 7]
2 [1, 2, 3, 4, 5, 9, 6, 8, 10, 7]
1 [1, 2, 3, 4, 5, 9, 6, 8, 10, 7]
3 [1, 2, 3, 4, 5, 9, 6, 8, 10, 7]
6 [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]
5 [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]
8 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
7 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
9 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
排序後:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Process finished with exit code 0
算法優缺點
快速排序最“快”的地方在於左右兩邊能夠快速同時遞歸排序下去,所以最優的情況是基準值剛好取在無序區的中間,這樣能夠最大效率地讓兩邊排序,同時最大地減少遞歸劃分的次數。此時的時間複雜度僅爲 O(NlogN)
。
快速排序也有存在不足的情況,當每次劃分基準值時,得到的基準值總是當前無序區域裏最大或最小的那個元素,這種情況下基準值的一邊爲空,另一邊則依然存在着很多元素(僅僅比排序前少了一個),此時時間複雜度爲 O(N*N)
。
選擇排序
動圖:
插入排序
動圖: