插入排序
插入排序的基本原則是,將一個待排序的元素,按照排序規則,插入到前面已經排好序的一組元素的適當位置,直到元素全部插入爲止。根據尋找插入位置的不同方式,可將插入排序分爲直接插入排序
和折半插入排序
,還有一種對直接插入排序的優化方案希爾排序
。
直接插入排序
直接插入排序的是,將一組待排序的元素第一個元素看做是有序的,然後從第二個元素開始,將他插入到前面排好序的一組元素的合適的位置。結合下面 的一組數據說吧
第一步:從元素8開始,往前查找,發現8比34小,則將8插入到34前面
第二步:從元素64開始,往前查找,發現沒有比64小的元素,則64不動
第三步:從元素51開始,往前查找,發現沒有比64小的元素,則64不動
。。。依次類推
代碼實現
/**
* 插入排序
*
* @Author HXY
* @Date 2020/2/25
*/
public class InsertionSort {
public static void sort(int[] arr) {
int j = 0;
for (int i = 1; i < arr.length; i++) {
int tmp = arr[i];
for (j = i; j > 0 && tmp < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = tmp;
}
}
}
直接插入排序算法在最好的情況下,也就是這組數據已經排好序的情況下,內層for循環的條件每次判定失敗,只執行外層的fort循環,時間複雜度可以看做O(N)。而在最壞情況下,也就是一組要從小到大排序的數據,初始化恰恰是從大到小排序的,這個時候時間複雜度是O(N2),一般情形下也是O(N2)。
折半插入排序
折半插入排序就是在直接插入排序的尋找插入點
這個步驟進行了優化,將原來的遍歷優化成折半查找。代碼如下:
public static void sort(int[] arr) {
int j = 0;
for (int i = 1; i < arr.length; i++) {
int low = 0, high = i - 1, mid = 0;
int tmp = arr[i];
// 尋找插入點
while (low <= high) {
mid = (low + high) / 2;
if (arr[i] < arr[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
// 將插入點後的元素統一後移,然後將元素插入插入點
for (j = i; j > high + 1; j--) {
arr[j] = arr[j - 1];
}
arr[high + 1] = tmp;
}
}
當數據較多時,折半插入排序平均性能優於直接插入排序,但是比直接插入排序的最好情況是要差的。因此當初始化序列接近有序時,用直接插入排序性能更好。這兩個都是穩定
的排序方式
希爾排序
結合上面的討論我們得知,當數據量相對較小或者序列基本有序時,插入排序的效率是比較高的。因此希爾大佬在這兩個點上對插入排序進行了優化,發明了希爾排序。希爾排序的主要思想就是
- 將一個大的序列通過一個整數
gap(增量)
拆分成幾組數據量小的子序列,先對這一個個子序列進行插入排序 - 經過上面這一步後,這個大的序列基本有序了,這個時候在執行一遍插入排序
具體的過程參見下圖
我們採取gap=3這個增量將初始化序列在邏輯上分爲三個子序列,然後對這三個子序列進行插入排序,這一步後這個序列基本有序,我們在控制gap=1,這時候就相當於對這個大的序列進行一次插入排序。
代碼如下:
// 希爾排序
public static void shellSort(int[] arr) {
int j = 0;
// 1. 分組
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 2. 相當於遍歷分出來的幾個組
for (int i = gap; i < arr.length; i++) {
int tmp = arr[i];
// 對組內的元素排序
for (j = i; j >= gap && tmp < arr[j - gap]; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = tmp;
}
}
}
希爾排序的時間複雜度與這個增量的取用關係較大,至今無人能對一般情形下的時間複雜度給出證明,我這種菜鳥就暫不研究了。另外這是一種不穩定
的排序方式。