1. 什麼是選擇排序?
首先貼上從wiki上弄下來的關於選擇排序的定義。
選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
更加直白的解釋是,每次都從陣列中選出最大或者最小的元素,然後放到陣列的左邊。
2. 選擇排序的過程展示
老規矩,我們還是通過動圖來看一下選擇排序的過程。以下的gif來自於wiki。
然後我們再通過我製作的gif,配上資料再瞭解一下過程。假設我們的待排序陣列還是[5, 1, 3, 7, 6, 2, 4]。
3. 選擇最小值的演算法
我們使用Java來實現最常見的,選擇最小值的選擇排序,其程式碼如下。
private void selectionSort(int[] arr) {
int min;
int minIndex;
for (int i = 0; i < arr.length - 1; i++) {
min = arr[i];
minIndex = -1;
for (int j = i; j < arr.length; j++) {
if (arr[j] < min) {
min = arr[j];
minIndex = j;
}
}
// 排序結束 交換位置
if (minIndex != -1) {
exchange(arr, i, minIndex);
}
}
}
private void exchange(int arr[], int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int[] arr = new int[]{5, 1, 3, 7, 6, 2, 4};
selectionSort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5, 6, 7]
假設陣列的長度為7,那麼演算法就需要進行6輪。如果陣列的長度為n,則演算法需要進行n - 1輪。
每一輪,演算法都會從剩下的待排序元素中,選出最小的元素,並將其與當前陣列下標為i也就是有序序列的起始位置的元素交換。這樣一來,經過反覆的排序,最終形成有序陣列。
4. 選擇最大值的演算法
上面實現了選擇最小值的程式碼,接下來我們繼續實現選擇最大值的程式碼。
private void selectionSort(int[] arr) {
int max;
int maxIndex;
for (int i = 0; i < arr.length - 1; i++) {
max = Integer.MIN_VALUE;
maxIndex = -1;
for (int j = 0; j < arr.length - i; j++) {
if (max < arr[j]) {
max = arr[j];
maxIndex = j;
}
}
// 排序結束 交換位置
if (maxIndex != -1) {
exchange(arr, maxIndex, arr.length - i - 1);
}
}
}
private void exchange(int arr[], int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int[] arr = new int[]{5, 1, 3, 7, 6, 2, 4};
selectionSort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5, 6, 7]
這個思想與選擇最小值的演算法完全一樣,只不過是選擇了最大值,每次都將剩餘序列的最大值放到陣列的有序序列的最左邊。
那麼到此,選擇排序最常見的兩種寫法我們都已經實現了。有的兄弟可能會想,這篇部落格是不是結束了。其實我們可以從上面兩個演算法中想到可以優化的點。
既然我們有兩個選擇,一種選擇最小值,另外一種選擇最大值。那麼我們為什麼不同時進行兩個操作呢?
下面我們就來實現這種演算法。
5. 同時選擇最大值和最小值
private void selectionSort(int[] arr) {
int min;
int max;
int minIndex;
int maxIndex;
for (int i = 0; i <= arr.length / 2; i++) {
min = Integer.MAX_VALUE;
max = Integer.MIN_VALUE;
minIndex = -1;
maxIndex = -1;
for (int j = i; j < arr.length - i; j++) {
if (arr[j] < min) {
min = arr[j];
minIndex = j;
}
if (arr[j] > max) {
max = arr[j];
maxIndex = j;
}
}
// 排序結束 交換位置
if (minIndex != -1) {
if (maxIndex == i) {
maxIndex = minIndex;
}
exchange(arr, i, minIndex);
}
if (maxIndex != -1) {
exchange(arr, maxIndex, arr.length - 1 - i);
}
}
}
private void exchange(int arr[], int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int[] arr = new int[]{5, 1, 3, 7, 6, 2, 4};
selectionSort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5, 6, 7]
因為選擇最大值和最小值同時進行,相對於上面兩種演算法,同時選擇演算法在執行次數上比前兩種演算法減少了50%。
在執行時間上相對於選擇最小值和最大值分別減少了39.22%和62.20%。
6. 總結
以下是對同一個長度為10000的隨機亂序陣列使用三種演算法的情況。
[0 - 10000] 的亂序陣列 | 取最小值 | 取最大值 | 同時取最大值最小值 |
---|---|---|---|
100次平均執行時間(ms) | 51 | 82 | 31 |
執行次數(次) | 50004999 | 50004999 | 25005000 |
最後我們看一下選擇排序演算法的時間複雜度。
- 最好的情況為O(n ^ 2). 即使整個陣列都是有序的,選擇排序也會執行完選擇最大值或者最小值的過程,只是不會去進行元素交換。
- 最壞的情況為O(n ^ 2). 同上,會執行完選擇最大值或者最小值的過程,並且每次都需要進行元素交換。
其空間複雜度為O(n),上面三種演算法都屬於原地排序演算法,除了交換元素使用了一個輔助空間之外,沒有額外申請空間,同時選擇排序是不穩定排序。
往期文章:
- 你知道和你不知道的氣泡排序
- 聊聊微服務叢集當中的自動化工具
- go原始碼解析-Println的故事
- 用go-module作為包管理器搭建go的web伺服器
- WebAssembly完全入門——瞭解wasm的前世今身
- 小強開飯店-從單體應用到微服務
相關:
- 微信公眾號: SH的全棧筆記(或直接在新增公眾號介面搜尋微訊號LunhaoHu)