插入排序 —— 直接插入排序 + 折半插入排序


插入排序

  直接插入排序

 

  每次將一個待排序的記錄,按其關鍵字大小插入到前面的已經排好的子表中的適當的位置。直到全部記錄插入完成為止。


看圖說話,如圖所示:

  一共有 N 個記錄 ,放在 R 列表中 R[0,n-1]

  在排序過程中的某一時刻,呈現了如果所示的場景。

  其中:

    淺綠色為 已經排好序的 部分 稱之為 有序區 R[0,i-1]

    橘色為 當前元素 

    橘色+ 藍色 為尚未排序的 部分 稱之為 無序區 R[i,n-1]

 

   我們排序的時候,總是將無序區的第一個記錄,放到有序區的合適位置,使有序區長度 +1 ,形成新的有序區 R[0,i]

 

  說人話:

    其實最開始的時候,我們就假設該列表的的有序區內僅含有一個記錄。即列表的第一個記錄 R[0]

    其余所有記錄都位於無序區,即R[1,n-1] 均為無序區。

    開始排序:

      從無序區拿第一個元素和有序區的最后一個元素比較,即R[1] 和R[0] 比較,如果 R[1] 小於R[0],那么R[0]向后移動一位,占據R[1]的位置。將原R[1] 插入到R[0] 前面。即原來R[0]的位置。有序區長度+1;如果R[1] 大於R[0],兩個記錄保持不變。有序區長度依舊+1。有序區編程R[0],R[1]

      經過過上一步的比較、排序,現在的無序區的第一個記錄就變成了R[2],然后將R[2] 提出來,依次和它前面的記錄進行比較,如果發現比R[2]小的記錄,那么將R[2]插入到該記錄后面。假設一種情況:R[2] 首先和R[1]比較,發現R[2] 大於R[1] 於是將R[1]向后移動一位,注意:先別急着把R[2]插入到R[1]的前面。因為你不知道R[0]和R[2]的關系。所以,繼續用R[2]和R[0]比較,假設R[0]小於R[2]了,那么這個時候,再將R[2]放入到R[0]的后面即R[1]的位置。

      。。。。

      直到所有的記錄都這樣插入一個遍。

    

 

  可以這樣理解,有一個平台上,上面都是排列好的娃娃(左邊一排排)。還有一對雜亂無章的娃娃(右側圓圈),無亂的堆放在一起。你操作着頭頂的機械手,每次從右邊的那一大堆的娃娃里面夾起一個,然后向左移動。在平台上,每看到一個娃娃,那么就將機械手里夾着的娃娃和現在你看到的平台上的娃娃那個更大,如果發現平台上的娃娃更大,那么就繼續向左移動機械手,看平台上下一個娃娃,如果還是平台上的娃娃大,那么繼續向左移動機械手。。。知道你發現機械手的娃娃比平台上的那個娃娃大(假設叫 z ),那么就將機械手上的娃娃放到 z 的后面。其他的比你手里的娃娃大的那些娃娃,在你每一次比較完畢之后,都被人向右移動了一些距離。於是,剛好就給你流出了放置娃娃的空間。

 

 

代碼如下:

def insert_sort(A):
    n = len(A)           # 序列A 的長度
    for  i in range(1,n):  # 從序列A 的第二個元素開始比較。即下標從 1 開始。
        temp = A[i]     # 設置一個變量,承接當前的 元素的值
        j = i -1        #  有序區的最后一個元素的索引,每次比較都是從和該位置元素比較開始,逐漸向左推進。
        while j>=0 and temp<A[j]:  # j 指代 每個有序區元素的索引,所以j 不能小於0。 當 當前元素 小於 要比較的元素時,進行while循環。
            A[j+1]=A[j]   # 將比當前元素大的有序區的元素向右移動一個位置。
            j -= 1  # 不着急放下當前元素,而是繼續查看在j 元素之前是否還有元素,且該元素和當前元素相比,哪個大
        A[j+1] = temp  # j前面已經沒有元素了,或者是那個元素小於當前元素。那么將當前元素放到那個元素的后面。
    return A



a = [34,1,4,3,6,8,89,23,12]
print(insert_sort(a))
View Code

 

 

 

二、折半插入排序(二分插入排序)

   直接插入排序是比較一下,移動一下記錄的位置,再比較一下,再移動一次。。。。有點浪費時間。因此鑒於有序區已經有序這一特性,我們可以通過使用折半(二分)查找,快速找到要插入的位置,然后移動該位置以后的所有記錄,將當前記錄插入到目標位置。

代碼如下:

def insert_into(A):
    n = len(A)
    for i in range(1,n):  # 無序區從1到n-1
        low = 0            #初始化 low ,最開始為0
        heigh = i-1         #初始化 heigh ,最開始為 i-1 ,為有序區最大索引,
        temp = A[i]         #定義一個變量,盛放無序區第一個元素,也就是要插入的元素
        while low <= heigh:    #開始二分查找。注意 low <= heigh  這個條件
            mid = (low + heigh) // 2   # mid 是隨着每次low或者是heigh的調整而動態調整的。且,為防止出現小數,故使用地板除
            if temp < A[mid]:   # 如果 當前元素小於列表的中間元素
                heigh = mid - 1   # 移動heigh到mid-1位置。 以下同理,移動low的位置。直到出現low>heigh情況,退出循環
            else:
                low = mid + 1

        for j in range(i-1,heigh,-1):  # 經過上述循環,找到要插入的位置即為heigh+1 ,因此從 i-1到heigh的元素都 統一向后移動。一會兒具體說為什么目標位置是 heigh+1 。
            A[j+1] = A[j]
        A[heigh+1] = temp   # 將當前元素放到相應位置。排序完成,返回序列。
    return A

abc =[34,1,4,3,6,8,89,23,12]
print(insert_into(abc))
View Code

 

現在來說一說為什么要插入到 heigh+1 的位置。

 其實當時犯暈也是因為二分查找沒有學好。因此也就在詳細分析分析

 

隨着二分查找的進行,low / heigh的不斷移動,會出現low和heigh出現兩種情況:

1、low 和 heigh 是相鄰元素

 

 low_1, midd_1, heigh_1 是上一次查找的結果,

現在進行下一次查找 發現 temp小於midd_1 ,於是heigh 移動到midd_1-1的位置(midd_1前一位)。記作heigh_2

此時出現了low_2(還是原來的low-1位置,只是變了名字,表示有一次查找結果),和heigh_2 這種情況,即 low和heigh相鄰。

此時又進行二分查找。midd_2即為low和heigh的中間元素,因為二分查找規定,和使用地板除,所以midd_2實際上是和low_2 處於同一的位置。

if temp>midd_2:

  low = midd_2 +1    #情況一

else:

  heigh = midd_2 -1 #情況二

 

 

情況一:

  

  

  low_3 == heigh_3 即 low 和heigh位於同一個位置。此時符合更上一層二分查找的條件 low <= heigh 因此繼續進行二分查找。

  midd_3 = (low_3+heigh_3)//2 = heigh_3

  此時:

  如果temp > midd_3 也就是說temp > heigh_3 ==》》temp>heigh ,所以,heigh以后的元素都應該向右移動一個位置,“留出” heigh+1 來供 temp 使用

  如果temp < midd_3 ,那么heigh = midd_3-1 。如下圖:

  

  heigh_4 < low_4 即 low> heigh 退出二分查找

  temp應該位於heigh_4+1的位置。即:low_4和midd_4的位置。即low的位置。

  此時low = heigh+1

    那么low及其以后的元素都應該向后移動過一個位置,留出low即 heigh+1 的位置來供temp使用

 

 

2、是low 和heigh位於同一個位置的時候

    這種情況和heigh_3的情況一致

 

 

 

所以 heigh+1 即為要插入的位置。

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM