1、直接插入排序算法
直接插入排序的基本操作是將一個記錄插到已排隊好的有序表中,從而得到一個新的,記錄增1的有序表。
1 // 直接插入排序.cpp : 定義控制台應用程序的入口點。 2 // 3 #include "stdafx.h"
4 #include "stdio.h"
5 //直接插入排序
6 void InsertSort(int a[], int len); 7 void main(){ 8 int i; 9 int a[5] = {5,3,4,6,2}; 10 InsertSort(a,5); 11
12 for (i = 0; i < 5; i++) 13 printf("%d ", a[i]); 14 } 15
16 void InsertSort(int a[] ,int len){ 17 int temp,i,j; 18 for (i = 1; i < len;i++) 19 { 20 if (a[i] < a[i - 1]){ 21 temp = a[i]; //用一個臨時變量存一下
22 for (j = i - 1; a[j] > temp && j>=0; j--){ 23 a[j + 1] = a[j]; //凡事比i這個數大的就要后移,因為大的數總是在后面
24 } 25 a[j+1] = temp; //這里需要注意的是j+1,調bug好累勒 ->@@
26 } 27 } 28 }
直接插入排序算法分析
根據代碼我們來解釋一下直接插入排序的核心
例如,我們要對5,3,4,6,2這幾個數進行排序
a[] | 0 | 1 | 2 | 3 | 4 |
值 | 5 | 3 | 4 | 6 | 2 |
當這個數組進入函數后,下標首先定義到i = 1,即排序前,首先定義為a[0] = 5即是有序的。
進入循環內,比較a[1] 是否小於 a[0] 發現是小於的,這個時候按理說是要把a[0]這個元素右移動1位。然后將a[1]這個元素插在a[0]的位置上
但是考慮到這樣子將覆蓋原來的a[1]的值,所以先將a[1]的值拷貝一份給temp,然后將a[0]右移一位,再將temp的值傳給a[0] .即
a[] | 0 | 1 | 2 | 3 | 4 |
值 | 3 | 5 | 4 | 6 | 2 |
這時i =2了。此時a[0],a[1]屬於有序的序列了,我們此時再次比較a[2]是否小於a[1](前一位),4<5,滿足if條件
temp = a[2] 先拷貝一份,再將a[1] 右移一位,再次比較a[0]是否大於temp ,發現3並沒有大於4,由此可見只要i前面有序數存在大於a[i]的值,有序序列就要向后移動,
然后再把a[i] 插在正確的位置。
a[] | 0 | 1 | 2 | 3 | 4 |
值 | 3 | 4 | 5 | 6 | 2 |
當i = 3時,這個時候6比5大,不滿足if條件,也可以發現,前面已經都是有序序列{3,4,5,6}.
a[] | 0 | 1 | 2 | 3 | 4 |
值 | 3 | 4 | 5 | 6 | 2 |
最后當i = 4時,發現2 < a[3] 這個時候同理前面操作,先將a[4]拷貝一份給temp ,a[4] = a[3],右移一位
再次比較 ,發現temp < a[2] , a[3] =a[2] ,右移一位
再次比較 ,發現temp < a[1] , a[2] =a[1] ,右移一位
再次比較 ,發現temp < a[0] , a[1] =a[0] ,右移一位
此時就可以把temp 賦值給了a[0] ,這個時候就已經排序完成了。
a[] | 0 | 1 | 2 | 3 | 4 |
值 | 2 | 3 | 4 | 5 | 6 |
直接插入排序復雜度分析
從空間上看,它只需要一個輔助空間temp ,因此我們關鍵看它的時間復雜度。
當最好的情況下,也就是序列本身就是有序的 ,這個時候我們只有進行每次的if判斷(第20行),比較的次數n-1,移動的次數0,這個時候時間復雜度O(n)
如果排序記錄是隨機的話,那么根據概率相同的情況原則,平均比較和移動的次數約為(n^2)/4 次,因此我們可以得出直接插入排序法的書劍復雜度為O(n^2) 從這里也可以看出
直接插入排序比冒泡排序和簡單選擇排序性能要好一點,是一個穩定的排序算法。
2、折半插入排序算法
折半插入排序(Binary Insertion Sort)是對插入排序算法的一種改進,所謂排序算法過程,就是不斷的依次將元素插入前面已排好序的序列中。
基本思想:
基本思想和直接插入排序相同
不同在於查找插入位置
直接插入排序是采用順序查找法,而折半插入排序排序是采用二分查找思想。
1 #include <stdio.h>
2
3 void BlnInsertSort(int a[], int len); 4 int main(){ 5
6 int i; 7 int a[5] = { 15, 3, 41, -6, 2 }; 8 BlnInsertSort(a, 5); 9
10 for (i = 0; i < 5; i++) 11 printf("%d ", a[i]); 12
13 return 0; 14 } 15
16 void BlnInsertSort(int a[], int len){ 17 int i, temp, j; 18 for (i = 1; i < len;i++) 19 { 20 temp = a[i]; //將要插入的元素拷貝一份 21 int low , high,mid; 22 low = 0, high = i-1; 23 while (low <=high) //在[l...h] 中尋找插入的位置 24 { 25 mid = (low + high) / 2; //折半 26 if (mid <= temp) 27 { 28 low = mid+1; //插在高半區 29 } 30 else{ 31 high = mid-1; //插在低半區 32 } 33 } 34 for (j = i - 1; j >= high + 1;--j) //騰出high+1的位置 35 { 36 a[j + 1] = a[j]; //記錄后移 37 } 38 a[j + 1] = temp; 39
40 } 41 }
假如要將i 位置上的數插入前面的有序[0...4]序列中,令 i = 5,h =i-1 ,l = 0, m = (h+l)/2;
發現 i = 5 對應的數 小於 m 對應的數,這個時候 h =m -1 ,查找位置縮小到左半區間 ,m = (h+l)/2;
再次比較m 和 i對應的值,發現27 > 13 ,此時發現27應該在m的右半部分。於是 l = m+1 ,m = (h+l)/2;
再次比較m 和 i 對應的值,發現27 < 38 ,此時發現27應該在m的左半部分。於是 h = m-1 ,m = (h+l)/2;
此時再次觀察發現h <l這個時候循環就應該結束了。我們i要插入的位置即是 h+1 ,於是將數組元素開始移動,騰出位置,准備插入數據。
這就是二分插入排序的基本思想。
折半插入排序算法復雜度分析
1、折半插入排序要比直接插入排序快,所以折半插入排序的平均性能要優於直接插入排序。
2、折半插入排序的關鍵碼比較次數和待排序序列的初始序列無關,僅依賴於對象的個數,再插入第i個對象時,需要經過{log2 i}次關鍵碼比較,才能確定它插入的位置。
3、當n比較大時,總關鍵碼的比較次數要比直接插入排序情況好的多,但要比最好的情況差。
4、在對象的初始排序已經按關鍵碼排好或接近有序時,直接插入排序比折半插入排序的關鍵碼的比較次數要少。
5、折半插入排序的對象的移動次數與直接插入排序相同,依賴於對象的初始序列,折半插入排序只是在數據較多的情況下減少了比較次數。
3、希爾排序算法

用一個變量dk = 5 ,將全部序列分割成
這里要做的事將a[0] ,a[5] ,a[10];a[1] ,a[6] ,a[11] ;a[2] ,a[7] ,a[12];a[3] ,a[8] 這幾組數做直接插入排序,注意此時的間隔為5,而不是之前的1,完成后
發現規律 ,只要是間隔5的每一組數都是有序的,如{35,41,81},{17,75,91},{11,15,95}都是有序的,這也導致了全部的序列有序性有所提高。
繼續我們現在另dk = 3 ,即將上面的序列分割成間隔為3的子序列,看顏色區分
同理按照5間隔的規則進行排序,排序完后
這個時候有序性又提高了不少。最后再將所有的序列做最后一次的直接插入排序,dk = 1的排序。
直接插入排序在序列元素少,序列的有序性好的情況下,它的效率是非常高的。
這個時候就已經全部排序完成了。
代碼:
1 #include<stdio.h>
2
3 void ShellSort(int a[], int dk[],int len); 4 void InsertSort(int a[], int dk, int len); 5
6 int main(){ 7
8 int i, j, len; 9 int a[13] = { 81, 94, 11, 96, 12, 35, 17, 95, 28, 58, 41, 75, 15 }; 10 int dk[3] = { 0 }; 11 len = sizeof(a) / 4; 12 int increament = len / 3 + 1; //increament = 5
13 for (j = increament, i = 0; j >= 1; j -= 2, i++) //dk[0...i-1]={5,3,1}
14 dk[i] = j; 15
16 ShellSort(a, dk, len); 17
18 printf("排完序:\n"); 19 for (i = 0; i < len; i++) 20 printf("%d ", a[i]); 21 printf("\n"); 22
23 return 0; 24 } 25
26 void ShellSort(int a[], int dk[],int len){ 27 int i,j=0; 28 //按增量序列dk[0...i-1]對順序表進行希爾排序
29 for (i = 0; i < 3; i++){ 30 printf("第%d趟排序: ",++j ); 31 InsertSort(a, dk[i], len); //一趟增量為dk[i]的插入排序
32 } 33 } 34
35 void InsertSort(int a[], int dk,int len){ 36
37 int i, j, temp; 38 for (i = dk; i < len; i++){ 39 if (a[i] < a[i - dk]){ 40 temp = a[i]; 41 //從有往左開始掃描,尋找有沒有大於temp的數(間隔是dk)
42 for (j = i - dk; j >= 0 && (temp < a[j]); j = j - dk){ 43 a[j + dk] = a[j]; 44 } 45 a[j + dk] = temp; 46 } 47 } 48 //輸出每一趟排序結果
49 for (i = 0; i < len; i++) 50 printf("%d ", a[i]); 51 printf("\n"); 52 }
希爾排序的特點:
1、一次移動,移動位置較大,跳躍式接近排序后的最終位置。
2、最后一次只需要進行少量的移動。
3、增量序列必須是遞減的,最后一個必須是1.
4、增量序列應該是互質的。
希爾排序算法的復雜度分析
希爾排序算法的效率和增量序列的取值有關系
那么增量序列是如何取值的,取一些什么值才能達到最高的排序效率呢?
這個問題至今都沒有被解決,是一個數學上的難題。
大量研究表明增量序列為:
可以獲得不錯的效率,其時間復雜度為O(n^2/3).要優於直接插入排序的O(n^2)
需要注意的是,最后一個增量必須是1才可以。由於記錄是跳躍性移動的,所以希爾排序並不是一個穩定的排序算法。
常見的內部排序就這3個,嚴奶奶書里面還提到了2-路插入排序表插入排序。
如果有些錯的地方大家希望可以指出,我們共同進步 -- 共勉之**
參考資料:《大話數據結構》
https://www.bilibili.com/video/av38482403/?spm_id_from=333.788.videocard.5
https://www.bilibili.com/video/av43523066/?p=2