排序 --> Python 實現

瞭解和實現冒泡排序選擇排序插入排序希爾排序歸併排序快速排序python

1.冒泡排序
 冒泡排序要對一個列表屢次重複遍歷。算法

  • 它要比較相鄰的兩項,而且交換順序排錯的項。
  • 每對列表實行一次遍歷,就有一個最大項排在了正確的位置。
  • 大致上講,列表的每個數據項都會在其相應的位置「冒泡」。

它們的順序是否正確。shell

  • 若是列表有n項,第一次遍歷就要比較n-1對數據。
    • 須要注意,一旦列表中最大(按照規定的原則定義大小)的數據是所比較的數據對中的一個,
    • 它就會沿着列表一直後移,直到此次遍歷結束。
  • 第二次遍歷開始時,最大的數據項已經歸位。
  • 如今還剩n-1個待排數據項,即有n-2個要比較的數據對。
  • 因爲每一次遍歷都會使下一個最大項歸位,所須要遍歷的總次數就是n-1。
  • 完成n-1次遍歷以後,最小的數據項必定已經歸位,此時不須要再執行其餘步驟。
def bubble_sort(alist):
    n = len(alist)
    # 外層循環控制比較幾回
    for i in range(n-1):
        # 內存循環控制 第i次 交換 n-i 趟
        # -i 是再也不換前i次已經排好的
        for j in range(n-i-1):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
        # print(alist)

  因爲冒泡排序要遍歷整個未排好的部分,它能夠作一些大多數排序方法作不到的事。app

  • 尤爲是若是在整個排序過程當中沒有交換,咱們就可判定列表已經排好。
  • 所以可改良冒泡排序,使其在已知列表排好的狀況下提早結束。
  • 這就是說,若是一個列表只須要幾回遍歷就可排好,冒泡排序就佔有優點:它能夠在發現列表已排好時馬上結束
  • 改良版冒泡排序。它一般被稱做「短路冒泡排序」。
def shortBubbleSort(alist):
    n = len(alist)
    # 外層循環控制比較幾回
    for i in range(n-1):
        # 假設已經徹底排好序
        sorted = True
        # 內存循環控制 第i次 交換 n-i 趟
        # -i 是再也不換前i次已經排好的
        for j in range(n-1-i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
                # 未徹底排好序
                sorted = False
    
        if sorted:    # 發現列表已排好時馬上結束
            return

        print(alist)

2.選擇排序函數

  選擇排序提升了冒泡排序的性能,post

  • 它每遍歷一次列表只交換一次數據,即進行一次遍歷時找到最大的項,完成遍歷後,再把它換到正確的位置。
  • 和冒泡排序同樣,第一次遍歷後,最大的數據項就已歸位,第二次遍歷使次大項歸位。
  • 這個過程持續進行,一共須要n-1次遍從來排好n個數據,
  • 由於最後一個數據必須在第n-1次遍歷以後才能歸位。

  • 每一次遍歷,最大的數據項被選中,隨後排到正確位置。
  • 第一次遍歷排好了93,第二次排好了77,第三次排好了55,以此類推。
def selectionSort(alist):
    # 外層控制比較幾回,一共須要 n-1 次遍從來排好 n 個數據
    for fillslot in range(len(alist)-1, 0, -1):
        # 假設第一次元素就是最大值
        position_max = 0
        # 內層控制元素比較和更新索引
        for location in range(1, fillslot+1):
            # 進行比較
            if alist[location] > alist[position_max]:
                # 更新索引
                position_max = location
        # 退出循環後,交換數據
        alist[fillslot], alist[position_max] = alist[position_max], alist[fillslot]

測試:性能

if __name__ == "__main__":
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    print(alist)
    selectionSort(alist)
    print(alist)

3.插入排序
  插入排序的算法複雜度仍然是O(n2),但其工做原理稍有不一樣。測試

  • 它老是保持一個位置靠前的已排好的子表,
  • 而後每個新的數據項被「插入」到前邊的子表裏,排好的子表增長一項。

圖4展現了插入排序的過程。陰影區域的數據表明了程序每運行一步後排好的子表。ui

  • 咱們認爲只含有一個數據項的列表是已經排好的。
  • 每排後面一個數據(從1開始到n-1),這個的數據會和已排好子表中的數據比較。
  • 比較時,咱們把以前已經排好的列表中比這個數據大的移到它的右邊
  • 當子表數據小於當前數據,或者當前數據已經和子表的全部數據比較了時,就能夠在此處插入當前數據項。
def insertionSort(alist):
    # 外層循環控制 從右邊第二個元素開始 向前面排好序的子列表中插入
    for index in range(1, len(alist)):
        pos = index
        # 內存循環 依次從子列表的最後一個最大的元素 和 你要插入的元素比較
        # 若是你的當前要插入的元素小,兩個元素交換位置
        while pos > 0 and alist[pos-1] > alist[pos]:
            alist[pos], alist[pos - 1] = alist[pos-1], alist[pos]
            pos -= 1

測試:spa

if __name__ == '__main__':
    lst = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    print(lst)
    insertionSort(lst)
    print(lst)

4. 希爾排序

 希爾排序有時又叫作「縮小間隔排序」,它以插入排序爲基礎,將原來要排序的列表劃分爲一些子列表,再對每個子列表執行插入排序,從而實現對插入排序性能的改進。劃分子列的特定方法 是希爾排序的關鍵。咱們並不是將原始列表分紅含有連續元素的子列,而是肯定一個劃分列表的增量「i」,這個i更準確地說,是劃分的間隔。而後把每間隔爲i 的全部元素選出來組成子列表。

 

這裏有一個含九個元素的列表。

  • 若是咱們以3爲間隔來劃分,就會分爲三個子列表,每個能夠執行插入排序。
  • 這三次插入排序完成以後,雖然這個列表尚未徹底排好序,
  • 但有趣的是,通過咱們對子列的排序以後,列表中的每一個元素更加靠近它最終應該處在的位置。

  • 最終以1爲間隔進行插入排序,即標準的插入排序的過程。
  • 注意到,經過對前面子列進行排序以後,咱們減小了將原始列表排序時須要比對和移動的次數。

在這個例子中,咱們僅須要再進行四次移動就能夠完成排序。

  • 特定選取劃分間隔的方法是希爾排序的獨特之處。
  • 代碼1中的函數使用了一組不一樣的間隔。
  • 在這個例子中,咱們從含2個元素的子列開始排序;
  • 下一步排含4個元素的子列。
  • 最終,整個數列用基本的插入排序排好。
def shellSort(alist):
    # 咱們從含2個元素的子列開始排序, 子列表的數目 就是列表長度的一半
    sublistcount = len(alist)//2
    while sublistcount > 0:
        # 每一個子列表執行插入排序
        for startpostion in range(sublistcount):
            # 間隔正好是 子列表的數量
            print("alist: %s" % alist, "startpostion:  %d" % startpostion, "sublistcount  %d" % sublistcount)
            gapInsertionSort(alist, startpostion, sublistcount)

        print("After increments of size", sublistcount, "The list is", alist)
        sublistcount = sublistcount // 2


def gapInsertionSort(alist, start, gap):
    for i in range(start+gap, len(alist), gap):
        pos = i
        while pos >= gap and alist[pos-gap] > alist[pos]:
            alist[pos], alist[pos-gap] = alist[pos-gap], alist[pos]
            pos -= gap

測試:

if __name__ == '__main__':
    # 奇數次第一組第一次是三個元素 [54,77,20]
    # alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    # 偶數次比較好看
    alist = [54, 26, 93, 17, 77, 31, 44, 55]
    shellSort(alist)
    print(alist)

縮減版:

def shell_sort(lst):
    step = int(len(lst)/2)
    while step > 0:
        for i in range(step, len(lst)):
            while i >= step and lst[i] < lst[i - step]: 
                lst[i], lst[i - step] = lst[i - step], lst[i]
                i -= step
        step = int(step/2)
    print(lst)


alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
shell_sort(alist)
  • 乍一看,你可能會以爲希爾排序不會比插入排序要好,由於它最後一步執行了一次完整的插入排序
  • 但事實上,最後的一次排序並不須要不少次的移動,
  • 由於正如上面所討論的,這個列表已經在以前的對子列的插入排序中實現了部分排序
  • 換句話說,每一個步驟使得這個列表與原來相比更趨於有序狀態。這使得最後的排序很是高效。

對希爾排序的綜合算法分析已經遠超出討論範圍,基於對該算法的描述,

  • 咱們能夠說它的時間複雜度大體介於O(n)和O(n2)之間。
  • 若是使用某些間隔時,它的時間複雜度爲O(n2)。
  • 經過改變間隔的大小,好比以2k-1(1,3,5,7,15,31等等)爲間隔,希爾排序的時間複雜度能夠達到O(n3/2)。

5.歸併排序
  咱們如今把注意力轉移到用分而治之的策略來改進排序算法的表現。

  • 咱們要學的第一種算法就是歸併排序
  • 歸併排序是一種遞歸算法,它持續地將一個列表分紅兩半。
  • 若是列表是空的或者只有一個元素,那麼根據定義,它就被排序好了(最基本的狀況)。
  • 若是列表裏的元素超過一個,咱們就把列表拆分,而後分別對兩個部分調用遞歸排序。
  • 一旦這兩個部分被排序好了,那麼這種被叫作歸併的最基本的操做,就被執行了。
  • 歸併是這樣一個過程:把兩個排序好了的列表結合在一塊兒組合成一個單一的,有序的新列表
  • 圖10就展現了咱們熟悉的做爲例子的列表如何被歸併排序算法拆分,

 

def merge_sort(alist):
    n = len(alist)
    # 遞歸結束條件
    if n <= 1:
        return alist

    # 中間位置
    mid = n // 2
    # 遞歸拆分左側
    left_lst = merge_sort(alist[:mid])
    # 遞歸拆分右側
    right_lst = merge_sort(alist[mid:])

    return merge(left_lst, right_lst)


def merge(left, right):
    print(left, right)
    
    # 須要兩個遊標,分別指向左列表和右列表的第一個元素
    left_point, right_point = 0, 0
    # 定義最終返回的結果集
    result = []
    # 循環合併數據
    while left_point < len(left) and right_point < len(right):
        # 誰小放前面
        if left[left_point] <= right[right_point]:
            # 放入結果集
            result.append(left[left_point])
            # 遊標移動
            left_point += 1
        else:
            result.append(right[right_point])
            right_point += 1

    # print("合併數據後:", result)
    # print('left: ', left[left_point:])
    # print('right: ', right[right_point:])
    # 退出循環時,造成左右兩個序列
    result += left[left_point:]
    result += right[right_point:]
    return result


if __name__ == '__main__':
    li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    lst = [8, 3, 2, 6, 7, 1, 5, 4]
    print(lst)
    sort_lst = merge_sort(lst)
    print(lst)
    print(sort_lst)

  

 爲了分析歸併算法,咱們須要考慮它實施的兩個不一樣步驟。

    • 第一步,列表被拆分,咱們已經(在二分查找中)計算過,咱們能經過logn的數量級的計算將長度爲n的列表拆分。
    • 而第二個過程是合併
    • 每一個列表中的元素最終將被處理並被放置在排序好的列表中,因此合併操做對一個長度爲n的列表須要n的數量級的操做。
    • 所以分析結果就是,拆分須要logn數量級的操做
    • 而每次拆分須要n數量級的操做所以最終操做的複雜度爲nlogn
    • 歸併排序是一種O(nlogn) 的算法。

6.快速排序
 快速排序用了和歸併排序同樣分而治之的方法來得到一樣的優點,但同時不須要使用額外的存儲空間。

通過權衡以後,咱們發現列表不分離成兩半是可能的,當這發生的時候,咱們能夠看到,操做減小了。

  • 快速排序首先選擇一箇中值。雖然有不少不一樣的方法來選擇這個數值,咱們將會簡單地選擇列表裏的第一項。
  • 中值的做用在於協助拆分這個列表。
  • 中值在最後排序好的列表裏的實際位置,咱們一般稱之爲分割點的,是用來把列表變成兩個部分來隨後分別調用快速排序函數的。

快速排序算法的工做原理以下:

  1. 從數列中挑出一個元素,稱爲"基準"(pivot)
  2. 從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。
  3.  在這個分區結束以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做。
  4.  遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

 

# first 理解爲第一個位置的索引,last 是最後位置索引
def quick_sort(alist, first, last):
    # 遞歸停止條件
    if first >= last:
        return

    # 設置第一個元素爲中間值
    mid_value = alist[first]
    # low 指向
    low = first
    # high
    high = last

    # 只要 low 小於 high 就一直走
    flag = 0

    while low < high:
        # high 大於中間值,則進入循環
        while low < high and alist[high] >= mid_value:
            # high 往左走
            high -= 1

        # 出循環後,說明high小於中間值,low指向該值
        alist[low] = alist[high]

        # low 小於中間值,則進入循環
        while low < high and alist[low] < mid_value:
            # low 往右走
            low += 1

        # 出循環後,說明low 大於中間值,high 指向該值
        alist[high] = alist[low]

        if not flag:
            print(alist)

    # 退出整個循環後,low 和 high 相等
    # 將中間值放到中間位置
    alist[low] = mid_value
    print(alist)
    flag = 1

    # 遞歸
    # 先對左側快排
    quick_sort(alist, first, low-1)
    # 對右側快排
    quick_sort(alist, low+1, last)

測試:

if __name__ == "__main__":
    lst = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    print("原來的list:\n", lst)
    quick_sort(lst, 0, len(lst) - 1)
    print("快速排序後的list: \n", lst)

各類排序方法的比較:

  

相關文章
相關標籤/搜索