演算法第三記 插入排序

2021-09-01 02:18:25 字數 2732 閱讀 1012

今天我們要講的是插入排序,對於插入排序的思路我們可以拿我們平時打撲克整理牌的思路來講,當我們拿到一張牌我們先隨意放在左邊,然後又抽到一張牌,那麼這張牌應該放在**呢?一般來說我們會跟已經整理好的牌序列最右邊的那張比較比較,如果比它小,我們就需要將牌插入到它的左邊,然後繼續不斷比較,直到遇到比待插入牌小的位置,然後我們插入到它的後面。這樣解釋或許有點牽強,因為有時候我們打牌不只是會去湊順子。之所以這樣比喻,是因為好多演算法書都喜歡用這個例子,所以我總結的時候也選擇這個了。

在一段模糊的比喻之後。我們來點更專業的術語。插入排序其實也是維護了乙個有序序列和乙個無序序列,排序的過程其實就是從未排序序列拿乙個元素出來,然後在已排序序列中找到適合自己待的位置,不斷重複....最終使得已排序序列逐漸增長到原先有序序列和無序序列的長度總和。

還有乙個重要的點就是在找待插入元素該待的位置時,已經比較過的元素需要向後挪,這是為了給待插入元素騰位置。如圖示意

這是一整趟插入排序結束的過程,而且我們還需要注意在找待插入元素該待的位置時,我們最終停止的位置會比最終要插入的位置小1,因為我們找待插入位置的停止條件就是待插入元素大於當前比較元素。這一點在**可以很明顯體現出來,下面我給出插入排序的**:

void insert_sort(int arr, int length)      //代價          次數

arr[j + 1] = val; // c8 n-1

}}

簡單解釋**:1.外迴圈從1開始是因為預設將第乙個元素就當做有序序列中的第乙個元素,然後從第二個元素開始往裡插入。

2.arr[j+1]=val就是我前面所說的 因為終止條件是待插入元素大於當前比較元素,所以由於前面執行了j--才結束了迴圈,我們只要在j+1的位置插入元素就行了,當然如果迴圈是因為j<0而結束的,那我們的j+1其實就相當於在下標0也就是陣列第乙個位置插入。

接下來我們分析以下插入排序的效率:

首先還是從它的空間複雜度開始,由於插入排序沒有借用任何額外的空間,所以它的空間複雜度是o(1),所以說它是乙個原地排序。然後是它的穩定性,從**中可以看出,當待插入元素等於當前比較元素時,並不會發生那個挪的操作,這也就是說相同值元素的相對位置是不會改變的。然後是它的時間複雜度,插入排序的時間複雜度最好是o(n),最壞是o(n²),平均而言是o(n²),接下來我會給出插入排序的分析方法,明白原理後自然就知道這個最好最壞究竟出現在什麼時候了。

首先外層迴圈判斷n次(加上最後那次迴圈結束的判斷),執行n-1次,所以只要計算迴圈每一步的代價就可以計算出時間複雜度了,我們可以看出每一步迴圈最大的時間代價就是那個while內層迴圈,如果我們可以度量出這個內層迴圈的代價,那麼整個排序的時間複雜度就出來了。我之前說過 最優的時候是o(n)那麼什麼時候是最優呢?答案是已排好序的情況!此時內部迴圈由於一開始就違反了迴圈條件所以內層的while迴圈相當於o(1)的代價,那麼我們自然很輕易就算出此時插入排序時間複雜度是o(n)。那麼什麼時候是最壞情況呢?答案是逆序!為什麼呢 我們來看看 當序列是逆序時,假設我們待插入元素的位置是j,則我們要經過j-1次挪的操作才能真正找到最後的位置。比如 下標2的元素 需要挪一次,下標3的元素需要挪2次,一直推導會發現這是乙個等差數列,等差數列前n項和忽略常係數的話最後是n平方階。那麼有人會問為什麼平均時間複雜度為什麼也是o(n²)呢? 這是因為我們分析演算法時,最壞時間複雜度往往是乙個上界,我們一般分析演算法效率時會針對每種情況的出現概率乘以各自消耗的時間以求期望。針對插入排序針對每一種情況的出現概率,以及各自消耗的時間相加同樣是n平方級數。

最後我會補充幾個關於插入排序的面試考點:

1.插入排序的效率 最好和最壞分別是在什麼時候?(前面已經提過)

2.鍊錶可以插入排序麼?(可以)

答:偷個懶 就寫個思路吧!^_^  有兩種方式 一種是像之前一樣 乙個乙個線性查詢 值不斷往後挪 如果拷貝操作的效率不高的話,我們可以利用鍊錶的刪除高效的特性。先將待插入元素從鍊錶脫離出來,然後找到待插入的位置,將其插入進去,這樣就可以省去了挪的操作,提高效率,因為鍊錶的插入刪除可以在o(1)完成,所以借用鍊錶的特殊物理組織形式,我們省去了元素乙個個向後挪的操作。

3.針對插入排序 你是否可以進一步優化?(答案就是第四點提到的二分查詢) 

答:採用一種特殊的二分查詢(找到大於等於val的第乙個位置)代替線性查詢。(注:此時用的是一種特殊的二分,二分查詢有四種形式 之後會單獨開一記講講)

int binary_find_1(int arr, int length,int value)

else }}

void insert_sort_1(int arr, int length)

arr[insert_pos] = val;

}}

4.同接上一題 我們知道二分查詢的效率是o(log n)那麼如果我們內層迴圈採用二分查詢的話,效率是否可以達到o(nlogn)?

答:我們來解釋一下第四題,首先我們使用二分查詢代替前面的線性查詢,提公升了查詢的效率,但是為什麼最終的時間複雜度依然是o(n²)呢?原因在於我們忽略了插入排序的乙個關鍵操作,「向後挪」 無論我們使用線性查詢還是二分查詢都是為了找到最終待插入的位置。而找到待插入的位置我們依然還需要將待插入位置一直到已排序序列尾 向後挪乙個位置以給待插入元素騰位置。挪的步數是不會因為你採用的查詢演算法而有所不同。線性查詢是一邊查詢一邊挪,二分查詢是找到後一起往後挪。

排序演算法(三)插入排序

今天來更新排序演算法中的第三種演算法 插入排序插入排序是基於比較的排序。所謂的基於比較,就是通過比較陣列中的元素,看誰大誰小,根據結果來調整元素的位置 因此,對於這類排序,就有兩種基本的操作 比較操作 交換操作其中,對於交換操作,可以優化成移動操作,即不直接進行兩個元素的交換,還是用乙個樞軸元素 t...

排序演算法(三) 插入排序

一 直接插入排序 最差時間複雜度 o n 2 最優時間複雜度 o n 平均時間複雜度 o n 2 穩定性 穩定 直接插入排序 insertion sort 是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對未排序的資料,在已排序序列中從後向前掃瞄,找到相應位置並插入。插入排序演算法的一般...

演算法第三章 插入排序

從現在開始,我會嘗試用我自己的話來表達一些演算法,如果說得不好,請見諒。插入排序主要步驟分為以下幾部分 1 從第2個元素開始把元素乙個乙個拿出來 2 把拿出來的元素從後向前乙個乙個比較,如果被比較的數大於拿出來的元素,則被比較的數向後移一位 3 當拿出來的元素小於等於被比較數時,把拿出來的元素存入被...