概述
常用排序算法
- 冒泡排序
- 插入排序
- 選擇排序
- 歸并排序
- 快速排序
冒泡排序
步驟
- 比較相鄰元素,如果前面元素比后面大,調換位置
- 對每一對相鄰元素執行同樣操作,從開始第一對到最后一對。這樣最后的元素是最大值
- 針對所有元素重復上述步驟,除了最后一個元素
- 持續每次對越來越少的元素重復上述步驟,直到沒有任何一對元素需要比較
public static void swap(int[] A,int l,int r){
int temp =A[l];
A[l]=A[r];
A[r]=temp;
}
/**
* 冒泡排序
* 最差時間復雜度 ---- O(n^2)
* 最優時間復雜度 ---- 如果能在內部循環第一次運行時,使用一個旗標來表示有無需要交換的可能,可以把最優時間復雜度降低到O(n)
* 平均時間復雜度 ---- O(n^2)
* 所需輔助空間 ------ O(1)
* 穩定性 ------------ 穩定
*
* @param A
* @param n
*/
public static void bubbleSort(int[] A, int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (A[j] > A[j + 1]) {
swap(A, j, j + 1);
}
}
}
}
插入排序
步驟
- 從第一個元素開始,該元素可以認為已經被排序
- 取出下一個元素,在已經排序的元素序列中從后向前掃描
- 如果該元素(已排序)大于新元素,將該元素移到下一位置
- 重復步驟3,直到找到已排序的元素小于或者等于新元素的位置
5.將新元素插入到該位置后 - 重復步驟2~5
/**
* 插入排序
* 最差時間復雜度 ---- 最壞情況為輸入序列是降序排列的,此時時間復雜度O(n^2)
* 最優時間復雜度 ---- 最好情況為輸入序列是升序排列的,此時時間復雜度O(n)
* 平均時間復雜度 ---- O(n^2)
* 所需輔助空間 ------ O(1)
* 穩定性 ------------ 穩定
* @param A
* @param n
*/
public static void insertSort(int[] A,int n){
for(int i=1;i<n;i++){
for(int j=i;j>0;j--){
if(A[j-1]>A[j]){
swap(A, j-1, j);
}
}
}
}
選擇排序
步驟
- 從第一個元素開始,選擇最小的元素
- 把最小的元素與第一個元素交換,這樣第一個元素就是最小
- 下一次從第二個元素開始找最小的元素,重復此步驟直到沒有元素需要選擇
/**
* 選擇排序 最差時間復雜度 ---- O(n^2)
* 最優時間復雜度 ---- O(n^2)
* 平均時間復雜度 ---- O(n^2)
* 所需輔助空間 ------ O(1)
* 穩定性 ------------ 不穩定
*
* @param A
* @param n
*/
public static void selectSort(int[] A, int n) {
for (int i = 0; i < n - 1; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if (A[min] > A[j]) {
min = j;
}
}
if (min != i) {
swap(A, min, i);
}
}
}
歸并排序
步驟
- 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合并后的序列
- 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
- 比較兩個指針所指向的元素,選擇相對小的元素放入到合并空間,并移動指針到下一位置
- 重復步驟3直到某一指針到達序列尾
- 將另一序列剩下的所有元素直接復制到合并序列尾
/**
* 歸并排序
* 最差時間復雜度 ---- O(nlogn)
* 最優時間復雜度 ---- O(nlogn)
* 平均時間復雜度 ---- O(nlogn)
* 所需輔助空間 ------ O(n)
* 穩定性 ------------ 穩定
*
* @param A
* @param left
* @param right
*/
public static void mergeSort(int A[], int left, int right) // 遞歸實現的歸并排序(自頂向下)
{
if (left == right) // 當待排序的序列長度為1時,遞歸開始回溯,進行merge操作
return;
int mid = (left + right) / 2;
mergeSort(A, left, mid);
mergeSort(A, mid + 1, right);
merge(A, left, mid, right);
}
public static void merge(int[] A, int left, int mid, int right) {
int len = right - left + 1;
int[] temp = new int[len]; // 輔助空間O(n)
int index = 0;
int i = left; // 前一數組的起始元素
int j = mid + 1; // 后一數組的起始元素
while (i <= mid && j <= right) {
temp[index++] = A[i] <= A[j] ? A[i++] : A[j++]; // 帶等號保證歸并排序的穩定性
}
while (i <= mid) {
temp[index++] = A[i++];
}
while (j <= right) {
temp[index++] = A[j++];
}
for (int k = 0; k < len; k++) {
A[left++] = temp[k];
}
}
快速排序
步驟
- 從序列中挑出一個元素,作為"基準"(pivot).
- 把所有比基準值小的元素放在基準前面,所有比基準值大的元素放在基準的后面(相同的數可以到任一邊),這個稱為分區(partition)操作。
- 對每個分區遞歸地進行步驟1~2,遞歸的結束條件是序列的大小是0或1,這時整體已經被排好序了。
public static int Partition(int A[], int left, int right) // 劃分函數
{
int pivot = A[right]; // 這里每次都選擇最后一個元素作為基準
int tail = left - 1; // tail為小于基準的子數組最后一個元素的索引
for (int i = left; i < right; i++) // 遍歷基準以外的其他元素
{
if (A[i] <= pivot) // 把小于等于基準的元素放到前一個子數組末尾
{
swap(A, ++tail, i);
}
}
swap(A, tail + 1, right); // 最后把基準放到前一個子數組的后邊,剩下的子數組既是大于基準的子數組
// 該操作很有可能把后面元素的穩定性打亂,所以快速排序是不穩定的排序算法
return tail + 1; // 返回基準的索引
}
/**
* 快速排序
* 最差時間復雜度 ---- 每次選取的基準都是最大(或最小)的元素,導致每次只劃分出了一個分區,需要進行n-1次劃分才能結束遞歸,時間復雜度為O(n^2)
* 最優時間復雜度 ---- 每次選取的基準都是中位數,這樣每次都均勻的劃分出兩個分區,只需要logn次劃分就能結束遞歸,時間復雜度為O(nlogn)
* 平均時間復雜度 ---- O(nlogn)
* 所需輔助空間 ------ 主要是遞歸造成的棧空間的使用(用來保存left和right等局部變量),取決于遞歸樹的深度,一般為O(logn),最差為O(n)
* 穩定性 ---------- 不穩定
*
* @param A
* @param left
* @param right
*/
public static void quickSort(int A[], int left, int right) {
if (left >= right)
return;
int pivot_index = Partition(A, left, right); // 基準的索引
quickSort(A, left, pivot_index - 1);
quickSort(A, pivot_index + 1, right);
}
總結
排序.png