1. 程式人生 > >內部排序(一)直接插入排序和二分插入排序

內部排序(一)直接插入排序和二分插入排序

我們都知道,程式=資料結構+演算法。資料結構和演算法是密切相關的,因為不同的資料結構配合不同的演算法,會有不同的效率。排序是最常見的演算法之一,功能顧名思義是將一個數據物件(即資料元素的集合)重新排列成遵循某種規則的序列。例如在構建二叉搜尋樹的過程中也有排序的過(插入節點過程中左子樹小於其父節點,右子樹大於其父結點 )程。排序是程式設計中的一種重要操作,因為對於無序的序列,進行查詢操作時,我們只能用順序查詢的方法,這樣平均查詢長度為(n+1)/2,但是如果對有序的序列進行查詢,我們可以用折半查詢法,折半查詢法的效率較高,平均查詢長度為((n+1)/n)*log(2n+1)-1(當n很大時,可以近似看作log(2n+1)-1),時間複雜度也是O(logN),所以說,排序是很重要的一種操作。

那麼什麼是內部排序?內部排序指的是,待排序列能夠一次全部匯入到記憶體空間裡,也就是說有足夠大的記憶體可以一次存放所有的待排序列,然後排序的過程是可以在記憶體中一次完成的。

排序演算法中有一個常用的概念是“穩定性”,排序演算法的穩定性指的是,對於任意的兩個或以上相等的資料,如果排序前後它們的相對位置不發生改變,那麼我們就說這個排序演算法是穩定的。假設我們要對一個學生資訊資料庫表做排序,表中有兩個學生的名字一樣都是A,我們把它編號A1和A2,如果排序前A1排在A2前面,排序後A1仍然排在A2的前面,那麼就說這個排序演算法是穩定的,否則說是不穩定的。

下面幾篇日誌中我都會分享對一些排序演算法的理解和實現的程式碼。

首先來看插入排序法。直接插入排序的思路是這樣的:把待排序列分成兩部分,前半部分是已經排好的序列,後半部分是待排列序列。假設序列裡有N個元素,一開始第一個元素當作已排好序列,然後從序列第二個元素開始,每次從後面部分待排序列中拿出一個元素例如Tmp,與前面的已排好序列元素從後往前作比較,按照關係依次將元素往後挪,直到找到正確的位置了,就把Tmp插入進去。這樣經過N-1次插入後,就能完成排序。我們直接來看個例子:

假設我們要對序列{85,12,59,36,62,43,94,7,35,52}從小到大升序排序(陣列預設第一個元素下標為1)。按照前面的思路,首先把序列第一個元素85看作是已排好序列,第二個開始的元素都是待排序列。

1、首先從第二個元素開始,從待排序列中拿出一個元素,即把12賦給一個變數Tmp,然後Tmp與前面的已排好序列元素從後往前做比較,發現12比85小,那麼85就往後挪一位到序列中的第二位。前面沒元素了,所以12就插進去了第一個位置。序列變為:

{12,85,59,36,62,43,94,7,35,52}

2、然後繼續把第三個元素59拿出來給Tmp,59和前面的已排好序列從後往前比較,發現59比85小,就把85往後挪一位到了第三個位置,59繼續和前一位12比較。發現59比12大,那麼就直接插入到第二位。序列變為:

{12,59,85,36,62,43,94,7,35,52}

3、繼續往後到36拿出來,36比85小,85往後挪一位,繼續36比59小,59往後挪,36大於12,那麼就插入在第二位。序列為:

{12,36,59,85,62,43,94,7,35,52}。

就這樣一直執行N-1步後,整個序列就排好了。那麼程式碼要怎麼寫?

第72行開始,for迴圈從第二個元素開始拿出來給一個臨時變數Tmp儲存,然後巢狀一個for迴圈,因為是與待排序列從後往前比較,所以j從i開始,j>0 && P->arr[ j-1 ]>Tmp(也就是已排好序列中有元素比Tmp大),就把這個較大的元素往後挪一位,直到巢狀的for迴圈做完後,找到正確的插入位置j了,第77行就把Tmp插入進去。

如果我們的序列是用一個數組來儲存的,我們還可以利用陣列的哨兵來暫時存放這個待排值,同樣按照上面的思路可以寫出一樣方法的直接插入排序程式碼:

等於是哨兵P->arr[ 0 ]代替了Tmp值。

可是對於直接插入排序法來說,上述兩種程式碼都用到了兩個巢狀for迴圈,時間複雜度為O(N²),對於序列元素少的排序,這種方法簡單易懂,但是當元素很多時,時間複雜度為O(N²)的排序法顯然是不合適的。因此我們要麼採用其他排序方法,要麼可以嘗試改進下直接插入排序法。

改進直接插入排序法,我們可以想想,從待排序列中拿出一個元素與已排好序列每一個元素作比較,找出合適的位置做插入,是一個“查詢過程”,而已排好序列中的元素又是有序的,因此我們可以用二分查詢的方法做插入排序,這樣就可以減少元素之間比較的次數了。程式碼如下:

把上面第一種方法改一下,就是通過二分查詢找出已排好序列中插入的位置,然後第107行,從插入位置front為起點,已排好序列終點i為終點,把插入位置即後面的元素都往後挪一位,騰出插入位置front來,最後把Tmp插入即可。

      同樣的方法我們可以對上面的方法二設定哨兵的方法也改一下:

折半插入排序的空間複雜度和直接插入排序一樣,折半插入排序減少了元素之間的比較次數,但是元素的移動次數不變,所以時間複雜度仍為O(n²)。

上述四種方法完整程式碼在個人程式碼雲:

https://gitee.com/justinzeng/codes/1xc8f2pa9u7vkbedqsti061