從這一篇博文開始,我們將開始討論排序演算法。所謂排序演算法,就是將給定資料根據關鍵字進行排序,最終實現資料依照關鍵字從小到大或從大到小的順序儲存。而這篇博文,就是要介紹一種簡單的排序演算法——插入排序(insertion sort)。
為了使精力專注於排序演算法本身,而不是對資料的分析、處理,若無特殊說明,我們每一篇介紹排序演算法的博文,均做如下假定:
1.資料儲存於乙個陣列之中,且資料個數n即陣列大小
2.資料型別即int
3.排序目標為從小到大
那麼,插入排序是怎樣的演算法呢?其實插入排序的思想**就是「插隊」。
首先我們想象一下這個現實場景:有乙個長度為n的隊伍,隊伍中每個人都比前面的人要高,你是新來的第n+1個人,現在位於隊尾,請問你該怎麼找到自己應處的位置,以使得隊伍保持原有順序(從矮到高)?
這個問題所有人都會解,那就是:我與前乙個人比較,若我更矮,則我站到他前面,持續此比較直至我比我前面的人高或同樣高,或者我來到第一名為止。
這個問題的解法,就是插入排序的根本。
現在我們將場景轉換到陣列中:有乙個大小為n的int型陣列a,a[0]至a[n-2]已按從小到大排好序,但是第a[n-1]即最後乙個元素是「新來的」,現在要將新元素放到正確的位置上,以使得陣列保持從小到大的順序,該怎麼做?
同樣的,我們令新元素不斷地與前乙個元素比較,若小於前乙個元素則兩者交換位置,直至新元素大於等於前乙個元素,或新元素到達a[0]時停止。
void unfinishedinsertionsort(int *a, unsigned intn)
上述想法就是插入排序的雛形:若元素xn前面的x0到xn-1均已排好序,那麼xn只需要不斷地向前「插入」,直至前乙個元素與xn的大小關係符合順序
,或xn到達第乙個位置即可完成排序。
不難發現,雛形中尚待解決的問題就是:如何令xn前面的所有元素排好順序?
這個問題可以嘗試用遞迴的想法解決:要令xn排好序,就需要令x0到xn
-1有序,而要令xn-1排好序,則需要令x0到xn-2有序……最後會發現,要想令x1排好序,則需要令x0到x0有序,而x0到x0一定是有序的,因為只有乙個元素,也就是說此刻遞迴的基準情形出現了,而根據x0到x0的有序,可以得到x0到x1的有序,x0到x2的有序,直至得到x0到xn-1的有序,也就有了x0到xn的有序。因此這個遞迴的想法可行,而且這個想法就是插入排序從雛形到完整的解決思想。
將上述想法與插入排序的雛形相結合後,我們就得到了插入排序的實現方法:設陣列a有n個元素,令下標x從1遞增至n-1,對於每個a[x]我們都執行一次「插隊」操作(即插入排序的雛形操作)。
下面為插入排序的例程:
void insertionsort(int *a, unsigned intsize)
}
計算插入排序的時間複雜度並不難,最壞的情況是陣列中元素恰好完全反序,此時插入排序的內迴圈必然執行至curpos==0為止,而外迴圈從startpos=1至startpos=size-1共size-1次,每一次內迴圈執行startpos次,即內迴圈總共執行1+2+3+……+size-1次,即size(size-1)/2次,即o(n2)
大部分人在學習c語言時就接觸過氣泡排序,所以我們將不再對氣泡排序進行介紹。從時間複雜度上看,插入排序和氣泡排序是一樣的,都是o(n2),但是在實際執行時,插入排序會比氣泡排序好得多,原因就是在資料「部分有序」時,插入排序可以減少很多比較次數,而氣泡排序的比較則是「固定的」。
舉例來說,現有資料1,2,3,4,5,7,6。若插入排序則需比較7次,交換1次(元素2,3,4,5,7均一次比較即結束,元素6與7比較一次,交換,再與5比較一次,結束)。而氣泡排序則需比較6+5+4+3+2+1=21次,交換1次。
造成氣泡排序與插入排序間差異的主要原因就是:插入排序的比較「更充分地利用了已存在的順序資訊」,而氣泡排序無論如何都需要(n-1)+(n-2)+(n-3)+……+1次比較。其實在資料「接近有序」的情況下,插入排序幾乎是最快的排序,完全有序的資料其只需要n-1次比較即結束排序。可以說在o(n2)這個級別的排序演算法中,插入排序是絕對的首選。
附:選擇排序也是常見的初學排序演算法,它需要的比較次數為n+n-1+n-2+……+2,比氣泡排序還要多n-1次,但是它的交換次數有可能比插入和冒泡都要少,比如資料5,4,3,2,1需按從小到大排序,若使用插入或氣泡排序,將需要10次交換,而選擇排序的交換操作只需要2次。但是實際使用時插入排序依然比選擇排序優先考慮,因為:
選擇排序的「實質交換」雖然可以更少,但形式上來說,選擇排序是固定執行n次交換的:每一趟我們都會找出當前最小元素然後將其交換至正確位置,所以肯定有n次交換。只不過會出現「當前元素恰好在正確位置上」的情況,從而沒有「實質交換」罷了,但代價依然是有的,比如判斷當前元素位置與目標位置是否不同,或直接執行自己與自己的交換。此外,在選出當前最小元素時,我們都認為是「比較操作」,然而實際上這裡面混雜著很多賦值操作。而這些不起眼的操作都會使得選擇排序沒有理想的那麼快。
淺入淺出資料結構(18) 希爾排序
而希爾排序就是 簡單地 將這個道理應用到了插入排序中,將插入排序小小的公升級了一下。那麼,希爾排序是怎麼將這個道理應用於插入排序的呢?我們先來回顧一下插入排序的 void insertionsort int a,unsigned int size 不難看出,在插入排序中,對於每乙個元素,我們都令其執...
淺入淺出資料結構(21) 合併排序
在講解合併排序之前,我們先來想一想下面這個問題如何解決 有兩個陣列a和b,它們都已各自按照從小到大的順序排好了資料,現在我們要把它們合併為乙個陣列c,且要求c也是按從小到大的順序排好,請問該怎麼做?這個問題非常容易解決,我們將a b和c都視為佇列,然後不斷比較a和b的首部,取出其中更小的資料出隊,然...
淺入淺出資料結構(17) 有關排序演算法的分析
這一篇博文我們將討論一些與排序演算法有關的定理,這些定理將解釋插入排序博文中提出的疑問 為什麼氣泡排序與插入排序總是執行同樣數量的交換操作,而選擇排序不一定?同時為講述高階排序演算法做鋪墊 高階排序為什麼會更快。在討論相關定理之前,我們必須先掌握乙個與順序有關的概念 逆序數。所謂逆序數,就是 逆序組...