插入排序---直接插入排序
技術標籤:資料結構與演算法
思路
- 首先將給定陣列的第一個元素看作有序序列,有序序列後續的一個元素看作帶插入資料,帶插入資料後面的序列為無序序列.
- 現在預設是升序排序,接下來的每一步,將待插入資料從有序序列的後面元素開始往前逐一比較,如果遇到比待插入資料大的元素,就將該元素往後移動,騰出來位置,按照這樣的思路將無序序列中的每一個數據都插入到有序序列中。
無哨兵位
無哨兵位就是平常的思路,比如給定一個長度為10的陣列,將其按照直接插入排序, C + + C++ C++程式碼如下:
#include<iostream> using namespace std; void sort(int array[], int n){ for(int i=1;i<n;i++){ int temp = array[i]; int j = i-1; for(;array[j]>temp;j--){ // 無哨兵和有哨兵的區別 // 無哨兵需要判斷j和0的大小,而且要保證0索引也移動 if(j<0) break; array[j+1] = array[j]; } // 判斷j如果小於0,說明全部大於temp,大於等於0說明已經找到待插入位置 if(j>=0) array[j+1] = temp; // j<0說明都是 else array[0] = temp; } } int main(){ int N = 10; int array[N] = {4, 3, 6, 8, 4, 5, 1, 2, 9, 6}; sort(array, N); for(int i=0;i<N;i++){ cout << array[i]; } cout << endl; return 0; }
無哨兵位的情況下,經過裡面的for迴圈之後,有兩種結果:找到了一個插入的位置;沒有找到插入的位置(其實可以說找到了,只是因為所有的元素已經比較完了,都沒有找到一個小於待插入元素的值)。跳出迴圈之後,需要判斷 j j j與的大小,如果j大於等於0,則待插入的位置為 j + 1 j+1 j+1;如果 j j j小於0,則待插入位置就是0.
有哨兵位
有哨兵位相對於無哨兵位多了一個條件,即在建立陣列儲存初始資料的時候,在陣列頭留出一個位置,這個位置不用於存放有序序列,充當的是無哨兵位方法中的臨時變數,防止需要插入的元素被覆蓋,它的好處是程式碼更加簡潔,而且減少了 i f if if判斷的次數;缺點是多了一個哨兵位,佔用額外的記憶體空間。
#include<iostream> using namespace std; void sort(int array[], int N){ for(int i=2;i<N;i++){ array[0] = array[i]; // array[0]為哨兵位 int j; for(j = i-1;array[0]<array[j]&&j>0;j--){ array[j+1] = array[j]; } array[j+1] = array[0]; } } int main(){ int N = 11; int array[N] = {0, 3, 5, 2, 6, 8, 4, 2, 9, 3, 1}; sort(array, N); for(int i=1;i<N;i++){ cout << array[i]; } return 0; }
穩定性
從上面的兩種方法中看出,不論是哨兵位還是無哨兵位,只有在待插入元素小於當前元素的時候才移動,等於的時候不會移動(升序),即能保持陣列中兩個大小相同的元素在排序前的前後關係,所以是穩定的。
效率
-
時間複雜度
最好的情況是,原序列是升序的,那麼內部迴圈不用執行,時間複雜度為 O ( n ) O(n) O(n);最壞的情況是,原序列是降序的,內外都全部執行,時間複雜度為 O ( n 2 ) O(n^2) O(n2)。 -
空間複雜度
需要一個長度為n的陣列來儲存原式資料,然後額外的臨時變數儲存待插入資料(對於哨兵位,陣列的長度為 n + 1 n+1 n+1),空間複雜度為 O ( n + 1 ) = O ( n ) O(n+1) = O(n) O(n+1)=O(n)
[注]:直接插入排序使用連結串列也可以實現,在眾多排序演算法中,可以同時適應兩種資料結構的並不多。
附加思路
下面這段程式碼是我在剛瞭解完直接插入排序的思路之後,動手實現的,在這段程式碼中,表面上看是三重迴圈,但是這個第三重迴圈所實現的僅僅是移動元素,什麼意思?直接插入排序中,第一層迴圈是遍歷無序序列,第二層迴圈是將待插入元素與當前指向的元素進行比較與元素的移動。我的思路是先迴圈比較,然後統一進行移動,而不是比較一個移動一個,那這樣的話為什麼要把這個移動的迴圈嵌入迴圈比較裡面呢?放在外面和迴圈比較同一個層次不是更容易理解嗎?因為我的程式碼一開始設定的迴圈比較中的變數 j j j只能在 f o r for for迴圈裡有效,在外部無效,所以我就順理成章的方進去了,當然設定成全域性變數是可以將這個迴圈拿到第二層迴圈的。
#include<iostream>
using namespace std;
void DirectInsertSort(int array[],int n){
for(int i=1;i<n;i++){ // 從第二個元素開始決定插入
for(int j=i-1;j>=0;j--){ // 跟前面的所有元素比較大小
if(array[i]>=array[j]){ // 如果大於當前指向的元素,插入當前元素的後一個位置
int keep = array[i];
for(int temp=i-1;temp>j;temp--){ // 將後面的元素全部後移
array[temp+1] = array[temp];
}
array[j+1] = keep; // 插入新元素
break;
}
if(j==0){ // 如果比較到了最後一個都沒有插入
int keep = array[i];
for(int temp=i-1;temp>=0;temp--){
array[temp+1] = array[temp];
}
array[0] = keep; // 插入到陣列的第一個位置
}
}
}
}
int main(){
int N = 10;
int array[N] = {3, 4, 8, 2, 3, 0, 7, 9, 3, 3};
DirectInsertSort(array, 10);
for(int i= 0;i<N;i++){
cout << array[i];
}
return 0;
}