插入排序算法之直接插入排序和希爾排序
插入排序算法
有一個已經有序的數據序列,要求在這個已經排好的數據序列中插入一個數,但要求插入後此數據序列仍然有序,這個時候就要用到一種新的排序方法——插入排序法,插入排序的基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據。
直接插入排序
直接插入排序的排序思路是:每次將一個待排序的元素與已排序的元素進行逐一比較,直到找到合適的位置按大小插入。
例子:
有序列:
開始時,有序序列只有一個元素就是第一個元素(紅色),後面的無序序列(綠色)。接下來,取無序序列中的第一個元素3,把它放到有序系列的合適位置。方法是,從有序序列的最後面向前,依次和3比較,如果比3大,就向後移動一個位置,直到找到比3小的元素,然後把3插到後面(由於後面的元素已經依次移動,所以該位置已經空出),或者有序序列中沒有比3小的元素,則將3放在有序序列的第一個位置(由於移動,該位置已經空出)
同樣,取無序隊列中的第一個元素,也就是6,然後,從有序序列的後面依次向前比較,首先是8,大於6,則向後移動(註意,8後移則會占據6的位置,所以要提前將6存一份)。接著比較3和6,3比6小,所以將6插在3的後面(也就是原來8的位置,8已經後移,該位置已空)。所以結果就是:
繼續下去,直到安排好最後一個元素。
代碼:
代碼也很簡單,主要的就是比較和後移,但要註意,要將待排序的元素多存一份,因為後移時,會占據該元素的位置。
#include <stdio.h> void insert_sort(int value[],int n) { int i = 1; for(;i < n;i++) { if(value[i] < value[i - 1]) { int j = i - 1; int temp = value[i];//value[i]的再移動的過程中會變化,所以要提前存儲下來 for(;j >= 0;j--) { if(temp < value[j])//當value[i]小於value[j]的時候,將value[j]向後移動 { value[j+ 1] = value[j]; continue; } //否則退出循環 break; } //退出循環時,說明value[i]大於value[j],這時,應該將value[i]放在value[j]的後面(後面一個位置已經移空) //還有一種情況是前面所有元素都比value[i]大 value[j + 1] = temp; } } } int main() { int value[] = {8,3,6,2,4,5,7,1,9,0}; insert_sort(value,10); printf("排序後的結果為:\n"); int i = 0; for(;i < 10;i++) printf("%d ",value[i]); printf("\n"); return 0; }
時間復雜度
只是定性的一個分析:從代碼中可以看出,算法的核心就是比較和移動,如果序列本身是有序的,那麽只需要n次比較,不需要移動,所以此時的時間復雜度為O(n)。如果序列是倒序的,則排第n個元素時,需要與前n-1個元素進行比較,前n-1個元素也都要後移。這樣n從1取到n就是,比較和移動的次數都是0+(2-1)+(3-1)+..+(n-1)結果就是n*(n-1)/2,所以是O(n2)級別。書上說,直接插入排序的平均時間復雜度也是O(n2)級別。
是否是穩定的
是的。(稍微想一下就知道)
希爾排序
希爾排序是希爾(Donald Shell)於1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高效的版本,也稱為縮小增量排序,同時該算法是沖破O(n2)的第一批算法之一。
希爾排序算法的時間復雜度和步長的選取有關,平均時間復雜度為O(nlog2n),最壞為O(n2),最好為O(n).
直接插入排序更適合於原始記錄基本有序的集合。這是因為如果記錄基本有序,那麽直接插入排序時移動的次數就會很少。而希爾排序正式利用了直接排序的這一個特點,希爾排序將數據按一定的步長進行分組,是的記錄很快就會達到整體基本有序。
例子:
有序列:
首先選擇一個步長,前面說過不同的初始步長會導致不同的時間復雜度,書上說,希爾排序的步長選擇是一個數學難題,所以我們不要糾結。最常用的初始步長就是length/2。在這個例子中,length=9,所以初始步長step=4。然後我們將原序列分成四組(記住,步長是多少就分成多少組!!!!),分組的原則是,同一組中的元素中,每兩個元素之間的下標的差為步長step。分組結果如下(相同顏色為一組)
然後,分別對每一組按照直接插入排序的方法進行排序(註意,此時每組中相鄰的兩個元素之間的下標差是步長step,而不是1)結果為:
然後改變步長:step=step/2,所以這一輪的步長為2,然後將數組分成兩組(再次說明,步長是多少,就分多少組)。如下(相同顏色為一組):
然後按照直接插入進行排序
然後,繼續改變步長,step=step/2,所以這一輪的步長為1,此時素組就分成一組了:
然後,按照直接插入排序進行排序,
接下來改變步長,step=step/2,步長為0,結束。
寫代碼:
通過上面的例子我們可以看到,實際上對分成的每一個組,進行的操作還是直接插入排序,只不過處理時,要考慮相鄰兩個元素之間的下標差不在是1,而是step。所以,我們首先要對上面直接插入排序的函數insert_sort()進行必要的修改,加入兩個參數:首元素的下標(以確定是對哪一組數據進行直接排序)和步長。如下:
/** * 修改直接插入排序的函數 * 加上了兩個參數:start_index表示每組的第一個元素的下標 * step表示步長 * */ void insert_sort(int value[],int n,int start_index,int step) { int i = start_index + step; for(;i < n;i+=step) { if(value[i] < value[i - step]) { int j = i - step; int temp = value[i];//value[i]的再移動的過程中會變化,所以要提前存儲下來 for(;j >= 0;j-=step) { if(temp < value[j])//當value[i]小於value[j]的時候,將value[j]向後移動 { value[j + step] = value[j]; continue; } //否則退出循環 break; } //退出循環時,說明value[i]大於value[j],這時,應該將value[i]放在value[j]的後面(後面一個位置已經移空) //還有一種情況是前面所有元素都比value[i]大 value[j + step] = temp; } } }
最後主函數中,主要任務就是分組,然後對每組數據都調用insert_sort()函數,還是再次強調:step是多少就分多少組!!!
完整代碼
#include <stdio.h> /** * 修改直接插入排序的函數 * 加上了兩個參數:start_index表示每組的第一個元素的下標 * step表示步長 * */ void insert_sort(int value[],int n,int start_index,int step) { int i = start_index + step; for(;i < n;i+=step) { if(value[i] < value[i - step]) { int j = i - step; int temp = value[i];//value[i]的再移動的過程中會變化,所以要提前存儲下來 for(;j >= 0;j-=step) { if(temp < value[j])//當value[i]小於value[j]的時候,將value[j]向後移動 { value[j + step] = value[j]; continue; } //否則退出循環 break; } //退出循環時,說明value[i]大於value[j],這時,應該將value[i]放在value[j]的後面(後面一個位置已經移空) //還有一種情況是前面所有元素都比value[i]大 value[j + step] = temp; } } } int main() { int value[] = {7,3,6,2,5,1,8,9,4}; int len = 9; int step = len; step /= 2; while(step != 0) { int j = 0; for(;j < step;j++)//將原數據集分成了step組數據 { insert_sort(value,len,j,step); } step /= 2; } printf("排序後的結果為:\n"); int i = 0; for(;i < len;i++) printf("%d ",value[i]); printf("\n"); return 0; }
最後附上word文檔和源文件的鏈接:
鏈接:http://pan.baidu.com/s/1c2zrh1e 密碼:zy5a
插入排序算法之直接插入排序和希爾排序