常見排序算法的整理
冒泡排序
原理
每次比較相鄰的兩個數,如果順序錯誤,就交換
最好時間複雜度:,數組原本有序
最壞時間複雜度:,數組倒序
平均時間複雜度:
空間複雜度:
穩定性:穩定
static int[] BubbleSort(int[] a) {
for (int i = 0; i < a.length; i++) {
boolean flag = true;
for (int j = 0; j < a.length - i-1; j++) {
if (a[j] > a[j + 1]) {
int[] swap = swap(a[j], a[j + 1]);
a[j] = swap[0];
a[j + 1] = swap[1];
flag = false;
}
}
if (flag) return a;
}
return a;
}
選擇排序
原理
每次遍歷數組,找到最小的,然後排到數組開頭,依次進行n遍
最好時間複雜度:
最壞時間複雜度:
平均時間複雜度:
空間複雜度:
穩定性:不穩定
static int[] Selectsort(int[] a) {
for (int i = 0; i < a.length; i++) {
int tmpId = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[tmpId]) tmpId = j;
}
int[] swap = swap(a[i], a[tmpId]);
a[i] = swap[0];
a[tmpId] = swap[1];
}
return a;
}
直接插入排序
原理
它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入
最好時間複雜度: 數組有序
最壞時間複雜度: 數組倒序
平均時間複雜度:
空間複雜度:
穩定性:穩定
static int[] insertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int preIndex = i - 1;
int current = a[i];
while (preIndex >= 0 && a[preIndex] > current) {
a[preIndex + 1] = a[preIndex];
preIndex--;
}
a[preIndex + 1] = current;
}
return a;
}
希爾排序
原理
原則上來說,排序的目的就是消除逆序對,對於冒泡,選擇這些 的算法來說,本質上每次比較最多隻會消除1對逆序對,所以更快的排序方法本質上是交換相隔比較遠的元素,使得一次交換能消除一個以上的逆序。
回到希爾排序,每次選擇一個step,可以理解爲以step個元素爲一行,也就是有step列。每次就對每一列進行插入排序。然後縮小step,到step=1的時候就變成了普通的插入排序,但是這個時候數據已經基本有序了,只需交換較少的次數即可完成排序。
最好時間複雜度: 數組有序
最壞時間複雜度:取決於step
平均時間複雜度:取決於step
空間複雜度:
穩定性:不穩定
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GNoiymAG-1585122534553)(https://i.loli.net/2020/03/25/FN4DkKgU63ocs8Z.png)]
數據取自維基百科希爾排序
希爾排序的時間複雜度的下界是 。希爾排序對中等大小規模的數組表現良好,在最壞情況下和平均情況下執行效率差不多。
static int[] shellSort(int[] a) {
int length = a.length;
int temp;
for (int step = length / 2; step >= 1; step /= 2) {
for (int i = step; i < length; i++) {
temp = a[i];
int j = i - step;
while (j >= 0 && a[j] > temp) {
a[j + step] = a[j];
j -= step;
}
a[j + step] = temp;
}
}
return a;
}
歸併排序
原理
採用分治的思想,將已有序的子序列合併,得到完全有序的序列
最好時間複雜度:
最壞時間複雜度:
平均時間複雜度:
空間複雜度:
穩定性:穩定
static void mergeSortRecursive(int[] a, int[] result, int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
int st1 = l;
int st2 = mid + 1;
mergeSortRecursive(a, result, st1, mid);
mergeSortRecursive(a, result, st2, r);
int index = l;
while (st1 <= mid && st2 <= r) {
if (a[st1] < a[st2]) {
result[index++] = a[st1++];
} else {
result[index++] = a[st2++];
}
}
while (st1 <= mid) {
result[index++] = a[st1++];
}
while (st2 <= r) {
result[index++] = a[st2++];
}
for (int i = l; i <= r; i++) a[i] = result[i];
}
static int[] mergeSort(int[] a) {
int len = a.length;
int[] result = new int[len];
mergeSortRecursive(a, result, 0, len - 1);
return a;
}
快速排序
原理
通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
優化
- 空間的優化,樸素的快排的可以像歸併一樣申請空間存放比基準值大的值和存放比基準值小的元素,那麼這裏的空間複雜度就是的,但是可以使用原地分割的技術,大概就是指把基準值暫時移到數組的最後,然後維護一個index,表示比基準小的元素有幾個,那麼就將比基準值小的元素往前移,最後再把基準值移回來即可。這個的空間複雜度可以優化到,主要依賴於遞歸的深度。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eyl4ARCm-1585122534556)(https://i.loli.net/2020/03/25/yaWhTNqE7v3xL9m.png)]
-
對於選擇基準值的優化。
- 固定位置基準值
- 隨機選擇基準值
- 三數取中選擇基準值
對於固定選擇,在極端數據下很容易退化成
對於隨機選擇,減少了極端情況的發生,一般不會退化很嚴重,當然有極小的概率會有問題。
對於三數取中,意思是選擇一個數組中,最左邊,中間,最右邊的三個數,選擇大小爲中間的值作爲基準值。相比於隨機選擇,最大好處在於處理升序數組效率比較高。
-
重複數據的優化
對於數組中出現大量重複數據的,可以將數組分成三段,而不是兩端。
意思是分成小與基準值,等於基準值,大於基準值三類。
這樣子在大量重複數據下的性能會比較好。
-
小範圍的數據優化
對於很小和部分有序的數組,快排不如插排好。當待排序序列的長度分割到一定大小後,繼續分割的效率比插入排序要差,此時可以使用插排而不是快排
最好時間複雜度:
最壞時間複雜度: 根據基準值的確定 ,每次左右分佈極其不均衡
平均時間複雜度:
空間複雜度:取決於棧的深度,也就是之間
穩定性:不穩定
//這裏就選擇使用最基礎的固定基準值
static void quickSortRecursive(int[] a, int l, int r) {
if (l >= r) return;
int val = a[l];
int i = l, j = r;
while (i < j) {
while (i < j && a[j] >= val) j--;
if (i < j) a[i++] = a[j];
while (i < j && a[i] < val) i++;
if (i < j) a[j--] = a[i];
}
a[i] = val;
quickSortRecursive(a, l, i - 1);
quickSortRecursive(a, i + 1, r);
}
static int[] quickSort(int[] a) {
quickSortRecursive(a, 0, a.length - 1);
return a;
}
堆排序
原理
堆這種數據結構,他的任意一個非葉節點的值都會比他的兩個子節點的值大。
所以我們只需要構建出大根堆,每次移除大根堆頂,就能確定一個最大值,將這個值放在最後,然後只需要再次調整堆結構,就又能獲得一個值。
最好時間複雜度:
最壞時間複雜度:
平均時間複雜度:
空間複雜度:
穩定性:不穩定
static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
static int[] heapSort(int[] a) {
int len = a.length;
for (int i = len / 2 - 1; i >= 0; i--) {
adjustHeap(i, a, len);
}
for (int i = len - 1; i >= 0; i--) {
swap(a, i, 0);
adjustHeap(0, a, i);
}
return a;
}
static void adjustHeap(int i, int[] a, int len) {
int l = 2 * i + 1;
int r = 2 * i + 2;
int largest = i;
if (l < len && a[l] > a[largest]) largest = l;
if (r < len && a[r] > a[largest]) largest = r;
if (largest == i) return;
swap(a, largest, i);
adjustHeap(largest, a, len);
}
計數排序
原理
將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中,需要確定最大值和最小值
注:n是數組長度,k是max-min的大小
最好時間複雜度:
最壞時間複雜度:
平均時間複雜度:
空間複雜度:
穩定性:穩定
static int[] countSort(int[] a) {
int b[] = new int[a.length];
int max = a[0];
int min = a[0];
for (int i = 1; i < a.length; i++) {
max = Math.max(max, a[i]);
min = Math.min(min, a[i]);
}
int k = max - min + 1;
int[] bucket = new int[k];
for (int i = 0; i < a.length; ++i) {
bucket[a[i] - min]++;
}
for (int i = 1; i < bucket.length; ++i) {
bucket[i] = bucket[i] + bucket[i - 1];
}
//保證穩定性
for (int i = a.length - 1; i >= 0; --i) {
b[--bucket[a[i] - min]] = a[i];
}
return b;
}
桶排序
原理
將數組分到有限數量的桶裏。每個桶再個別排序。上述的基數排序可以理解爲特殊的桶排。
使用場景
一般只適用於數據分佈均勻的場景。
時間複雜度和空間複雜度取決於桶的設置。
穩定性:穩定
基數排序
原理
基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位
也可以理解爲每次有10個桶,然後把數字放到桶裏面。
時間複雜度:,但是帶有不確定的常數(即位數)
空間複雜度:,n是數組長度,k是基數
穩定性:穩定