插入排序 —— 直接插入排序 + 折半插入排序,直接插入折半
插入排序
直接插入排序
每次將一個待排序的記錄,按其關鍵字大小插入到前面的已經排好的子表中的適當的位置。直到全部記錄插入完成為止。
看圖說話,:
一共有 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 Aa = [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 Aabc =[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來供temp使用
2、是low 和heigh位於同一個位置的時候
這種情況和heigh_3的情況一致
所以 heigh+1 即為要插入的位置。