排序算法之插入排序及其優化


插入排序


其他排序方法:選擇排序冒泡排序歸並排序快速排序插入排序希爾排序堆排序


思想

先將數組第一個元素作為一個排好序的序列,然后將數組中剩下的元素從左往右一個一個地按照大小插進此序列里,所插位置后面的元素都往后移一位,直到元素全部插完。
這個很好理解,就像我們玩撲克牌的時候,一張一張地摸牌,剛摸到手的一張牌,找到合適的位置后,插進去,右邊的牌就要挪個位置。

圖解

再次借用一下百科的圖emmm:

性能

插入排序最好的情況就是你每次插入的元素都剛好是插在最后,那就只用進行n-1次比較就行了,時間復雜度為O(n);
最壞情況的時間復雜度顯然是O(n2),平均時間復雜度也為O(n2)。

代碼

在找出正確的插入位置時,先比較前后元素位置是否正確,正確的話就結束循環;不正確的話,前面的元素往后移一位,繼續比較前一位。
有兩種方式,一種就是每比較一次就互換位置,另一種就是先記住需要插入的元素,需要換位置的時候就先把前一位元素后移一位,等找到合適的位置在插入。
這時候有些人就會圖方便(譬如我),選擇第一種方式,這樣代碼寫起來好看,也容易理解,但這樣子效率會比第二種方式低。
所以還是推薦使用第二種方式。

# 插入排序
def insertionSort1(arr):
    for i in range(1, len(arr)):
        j = i
        while j > 0:
            if arr[j] < arr[j - 1]:
                arr[j], arr[j - 1] = arr[j - 1], arr[j] #這里可以改進一下
                j -= 1
            else:
                break

def insertionSort2(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i
        while j > 0:
            if arr[j - 1] > key:
                arr[j] = arr[j - 1]
                j -= 1
            else:
                break
        if (i != j): arr[j] = key

優化

上面的排序算法也叫直接插入排序,那也就是說有不那么直接的插入排序咯。
直接插入排序是從后往前一個個地進行比較來找出插入位置的。學過查找算法的人都知道這樣並不高效,我們可以利用其他查找方法來代替它。

優化一 二分插入排序

二分插入排序是指在簡單插入排序的基礎上用二分查找算法來找出插入位置的排序算法。

# 二分查找
# size 為有序部分大小
def binarySearch(arr, size, key):
    # 最左下標為0, 最右下標為size-1
    left, right = 0, size - 1
    while left <= right:
        # mid為中間元素的下標
        mid = left + right >> 1
        # 中間元素剛好與插入元素相同時,返回中間元素后一位的下標
        if key == arr[mid]:
            return mid + 1
        elif key < arr[mid]:
            # 查找左半邊
            right = mid - 1
        else:
            # 查找右半邊
            left = mid + 1
    # 結束循環時,left必定等於right+1
    # 最后一次查找如果是查找左半邊,則key比arr[mid]小,key應該插入到mid的位置,而mid等於現在的right+1等於left
    # 最后一次查找如果是查找右半邊,則key比arr[mid]大,key應該插入到mid+1的位置,而mid+1等於現在的left
    # 所以只需返回left
    return left


# 二分插入排序(插入排序優化一)
def binarySort(arr):
    for i in range(1, len(arr)):
        # 需要插入的元素
        key = arr[i]
        # 利用二分查找來找到插入的位置
        pos = binarySearch(arr, i, key)
        j = i
        # 插入位置后面的元素全部后移一位
        while j > pos:
            arr[j] = arr[j - 1]
            j -= 1
        # 插入元素
        arr[j] = key

優化二 希爾排序

直接插入排序在找到插入位置時,需要一個一個地往后移動元素。有沒有什么辦法可以一下子移動多個呢?答案是不可以哈哈。
因為元素已經是有序的,新的元素插到中間,那其右邊的元素必定都要往后移一位。
我們換個角度來想,有序不行,那就在元素還是無序的時候,跳着來移動。這就要提到希爾排序算法了。

希爾排序大概就是,選一組遞減的整數作為增量序列。最小的增量必須為1:\(D_M>D_{M-1}>...>D_1=1\)

  • 先用第一個增量把數組分為若干個子數組,每個子數組中的元素下標距離等於增量;
  • 然后對每個子數組進行簡單插入排序
  • 再使用第二個增量,繼續同樣的操作,直到增量序列里的增量都使用過一次。
    (增量為1時,其實就是對整個數組進行簡單插入排序)

看圖更容易理解吧:
(借用一下慕課的浙大數據結構課件。因為課件原本是ppt,而我只有pdf,所以顏色沒有上齊,請將就將就emmm)

希爾排序快不快主要取決於我們怎么取增量序列,最常見的取法就是:\(D_M=\lfloor N/2 \rfloor, D_k=\lfloor D_{k+1}/2 \rfloor\)

# 希爾排序
# 增量序列為D(M)=N/2, D(k)=D(k+1)/2 向下取整
def shellSort(arr):
    size = len(arr)
    # 正整數右移一位相當於除以2且向下取整
    step = size >> 1
    while step > 0:
        for i in range(step, size):
            j = i
            tmp = arr[j]
            while j >= step:
                if tmp < arr[j - step]:
                    arr[j] = arr[j - step]
                    j -= step
                else:
                    break
            arr[j] = tmp
        step = step >> 1

這就是原始希爾排序,關於希爾排序的其他增量序列我會在下一篇文章再作介紹:希爾排序


其他排序方法:選擇排序冒泡排序歸並排序快速排序插入排序希爾排序堆排序


免責聲明!

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



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