常見的排序算法Python實(shí)現(xiàn)

背景

按照相關(guān)排序算法的解釋,手動用Python實(shí)現(xiàn)了一遍,記錄一下。(部分代碼是摘自網(wǎng)上)
排序結(jié)果為從小到大。

安利一個學(xué)習(xí)算法的經(jīng)典網(wǎng)站:算法圖示

這個網(wǎng)站上有很多算法的動圖示例,還帶有操作步驟解釋,實(shí)在是居家旅行學(xué)習(xí)必備之網(wǎng)站。

冒泡排序

原理

冒泡排序的原理是將臨近的數(shù)字兩兩進(jìn)行比較,然后按照從小到大或者從大到小的順序進(jìn)行交換。

動圖如下:

冒泡排序

步驟

主要步驟如下:

  1. 比較相鄰的前后二個數(shù)據(jù),如果前面數(shù)據(jù)大于后面的數(shù)據(jù),就將二個數(shù)據(jù)交換。
  2. 按照1的方法對源數(shù)組的第0個數(shù)據(jù)到N-1個數(shù)據(jù)進(jìn)行一次遍歷后,最大的一個數(shù)據(jù)就“升”到數(shù)組第N-1個位置(就是最后一個位置)。
  3. 設(shè)置N=N-1,如果N不為0就重復(fù)前面二步,如果N==0,那么就是將原數(shù)組所有的數(shù)都排序一遍,此時排序完成。

代碼實(shí)現(xiàn)

def bubble_sort(array):
    length = len(array)
    while length > 0:
        for i in range(length - 1):
            if array[i] > array[i + 1]:
                array[i], array[i + 1] = array[i + 1], array[i]
        length -= 1
    return array    

基本優(yōu)化

1.對于結(jié)束的條件,如果遍歷某一趟時,沒有發(fā)生任何交換,說明此時排序已經(jīng)完成,所以我們可以在程序中加上一個標(biāo)志量用來表示某趟是否發(fā)生交換。

代碼如下:

def bubble_sort_better(array):
    length = len(array)
    flag = False
    while length > 0:
        for i in range(length - 1):
            if array[i] > array[i + 1]:
                array[i], array[i + 1] = array[i + 1], array[i]
                flag = True
            else:
                flag = False
        if flag is False:
            return array
        length -= 1
    return array

2.如果存在這樣的數(shù)組: 假設(shè)數(shù)組有100個數(shù),但是只有前10個是無序的,后面90個是已經(jīng)排序完畢,且均大于前面10個數(shù)。這樣我們分析即可發(fā)現(xiàn),第一趟遍歷的時候,最后發(fā)生交換的位置必定小于10。所以我們只需要記錄下這個最后的位置,下一次,只需要從頭遍歷到這個位置即可。

代碼如下:

def bubble_sort_best(array):
    length = len(array)
    while length > 0:
        for i in range(length - 1):
            if array[i] > array[i + 1]:
                array[i], array[i + 1] = array[i + 1], array[i]
                length = i + 1
        length -= 1
    return array

直接插入排序

原理

插入排序的基本思想就是:每次將一個待排序的記錄,按其關(guān)鍵字大小插入到前面已經(jīng)排好序的子序列中的適當(dāng)位置,直到全部記錄插入完成為止。

動圖效果如下:


步驟

設(shè)數(shù)組為a[0…n-1]。

  1. 從第一個元素開始,該元素可以認(rèn)為已經(jīng)被排序
  2. 取出下一個元素,在已經(jīng)排序的元素序列中從后向前掃描
  3. 如果該元素(已排序)大于新元素,將該元素移到下一位置
  4. 重復(fù)步驟3,直到找到已排序的元素小于或者等于新元素的位置
  5. 將新元素插入到該位置中
  6. 重復(fù)步驟2

代碼實(shí)現(xiàn)

def insert_while(nums):
    for index in range(1, len(nums)):
        deal_num = nums[index]
        j = index - 1
        while j >= 0 and nums[j] > deal_num:
            nums[j + 1] = nums[j]
            j -= 1
        nums[j + 1] = deal_num

希爾排序

原理

希爾排序,也稱遞減增量排序算法,實(shí)質(zhì)是分組插入排序。由 Donald Shell 于1959年提出。希爾排序是非穩(wěn)定排序算法。

希爾排序是基于插入排序的以下兩點(diǎn)性質(zhì)而提出改進(jìn)方法的:

  • 插入排序在對幾乎已經(jīng)排好序的數(shù)據(jù)操作時, 效率高, 即可以達(dá)到線性排序的效率
  • 但插入排序一般來說是低效的, 因為插入排序每次只能將數(shù)據(jù)移動一位

希爾排序的基本思想是:先將整個待排序的記錄序列分割成為若干子序列分別進(jìn)行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進(jìn)行依次直接插入排序。

步驟

我們以一組數(shù)[49, 38, 65, 97, 26, 13, 27, 49, 55, 4]為例。

加入首先我們以步長5排序

49 38 65 97 26
13 27 49 55 4

然后我們對每列進(jìn)行排序。

13 27 49 55 4
49 38 65 97 26

合并變成了[13, 27, 49, 55, 4, 49, 38, 65, 97, 26]。第一輪結(jié)束。

更換步長為5//2=2,繼續(xù)排序

13 27
49 55
4 49
38 65
97 26

排序過后

4 26
13 27
38 49
49 55
97 65

合并:[4,26,13,27,38,49,49,55,97,65]

更換步長為2//1=1
排序之后就得到了結(jié)果。

代碼實(shí)現(xiàn)

def shell_sort(array, n):
step = 2
now_gap = n // step  # 初始步長
while now_gap > 0:
    for i in range(now_gap, n):
         # 每個步長進(jìn)行插入排序
        temp = array[i]
        j = i
        # 插入排序
        while j >= now_gap and array[j - now_gap] > temp:
            array[j] = array[j - now_gap]
            j = j - now_gap
        array[j] = temp
    # 新的步長
    now_gap //= step
return array

基本優(yōu)化

優(yōu)化部分主要在與步長的選擇上,請移步維基百科上查閱。
當(dāng)前比較優(yōu)秀的步長序列有:

  1. 已知的最好步長序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項來自

    這兩個算式。這項研究也表明“比較在希爾排序中是最主要的操作,而不是交換。”用這樣步長序列的希爾排序比插入排序要快,甚至在小數(shù)組中比快速排序和堆排序還快,但是在涉及大量數(shù)據(jù)時希爾排序還是比快速排序慢。

  2. 另一個在大數(shù)組中表現(xiàn)優(yōu)異的步長序列是(斐波那契數(shù)列除去0和1將剩余的數(shù)以黃金分區(qū)比的兩倍的冪進(jìn)行運(yùn)算得到的數(shù)列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713,…)

直接選擇排序

原理

直接選擇排序和直接插入排序類似,都將數(shù)據(jù)分為有序區(qū)和無序區(qū),所不同的是直接插入排序是將無序區(qū)的第一個元素直接插入到有序區(qū)以形成一個更大的有序區(qū),而直接選擇排序是從無序區(qū)選一個最小的元素直接放到有序區(qū)的最后。

步驟

  1. 在給定的數(shù)組中找到最小(大)的元素,將其放置為數(shù)組的首位作為已排序區(qū)域。
  2. 繼續(xù)在剩下的數(shù)組區(qū)域中尋找最小(大)的元素,放置到已排序區(qū)域的后邊。
  3. 重復(fù)2步知道剩下的元素排序完畢。

動圖示例:

代碼實(shí)現(xiàn)

def select_sort(array):
for i in range(len(array)):
    min_index = i
    for j in range(i + 1, len(array)):
        if array[j] < array[min_index]:
            min_index = j
    array[min_index], array[i] = array[i], array[min_index]
return array

歸并排序

原理

歸并排序是建立在歸并操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應(yīng)用。它的思想就是先遞歸分解數(shù)組,再合并數(shù)組。

首先是合并兩個數(shù)組。思路如下:比較兩個數(shù)組的最前面的數(shù)字,取小的那個,取了之后就要從原來的數(shù)組中刪除這個數(shù)字。 然后繼續(xù)比較,知道一個數(shù)組為空,最后把另一個數(shù)組的剩余部分復(fù)制過來即可。

再者考慮遞歸分解。思路如下:將數(shù)組分為leftright,如果這兩個數(shù)組內(nèi)部有序,就合并這兩個數(shù)組。確定兩個數(shù)組內(nèi)部有序的方法是,持續(xù)二分,知道每個小組中只有一個數(shù)字,此時該小組就認(rèn)為有序。然后合并相鄰兩個小組。

動圖演示:

代碼實(shí)現(xiàn)

def merge_array(array1, array2):
    left_index = right_index = 0
    result = []
    # 循環(huán)比較兩個數(shù)組,知道某個數(shù)組為空
    while len(array1) > left_index and len(array2) > right_index:
        if array1[left_index] < array2[right_index]:
            result.append(array1[left_index])
            left_index += 1
        else:
            result.append(array2[right_index])
            right_index += 1
    # 將不為空數(shù)組剩下的數(shù)字依次加入到結(jié)果列表中。另一個是空列表,所以可以這樣實(shí)現(xiàn)。
    result += array1[left_index:]
    result += array2[right_index:]
    return result


def divide_array(array):
    # 結(jié)束條件
    if len(array) <= 1:
        return array

    index = len(array) // 2

    left = divide_array(array[:index])  # 左半部分
    right = divide_array(array[index:])  # 右半部分

    return merge_array(left, right)

快速排序

原理

快速排序通常明顯比同為Ο(n log n)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多筆試面試中能經(jīng)??吹娇炫诺挠白印?梢娬莆湛炫诺闹匾浴?/p>

步驟

  1. 先從數(shù)列中取出一個數(shù)作為基準(zhǔn)數(shù)。
  2. 分區(qū)過程,將比這個數(shù)大的數(shù)全放到它的右邊,小于或等于它的數(shù)全放到它的左邊。
  3. 再對左右區(qū)間重復(fù)第二步,直到各區(qū)間只有一個數(shù)

動圖示例:

代碼實(shí)現(xiàn)

# 普通實(shí)現(xiàn)
def quick_sort_simple(array):
    if len(array) <= 1:
        return array
    key = array[0]
    less, greater = [], []
    for i in range(1, len(array)):
        if array[i] > key:
            greater.append(array[i])
        else:
            less.append(array[i])
    return quick_sort_simple(less) + [key] + quick_sort_simple(greater)


# 使用列表推導(dǎo)式實(shí)現(xiàn)
def quick_sort_nic(array):
    if len(array) <= 1:
        return array
    return quick_sort_nic([x for x in array if x < array[0]]) + [x for x in array if x == array[0]] + quick_sort_nic([x for x in array if x > array[0]])

# 不開辟空間實(shí)現(xiàn)
def quick_sort(ary):
    return qsort(ary,0,len(ary)-1)
def qsort(ary,left,right):
    #快排函數(shù),ary為待排序數(shù)組,left為待排序的左邊界,right為右邊界
    if left >= right : return ary
    key = ary[left]     #取最左邊的為基準(zhǔn)數(shù)
    lp = left           #左指針
    rp = right          #右指針
    while lp < rp :
        while ary[rp] >= key and lp < rp :
            rp -= 1
        while ary[lp] <= key and lp < rp :
            lp += 1
        ary[lp],ary[rp] = ary[rp],ary[lp]
    ary[left],ary[lp] = ary[lp],ary[left]
    qsort(ary,left,lp-1)
    qsort(ary,rp+1,right)
    return ary

堆排序

原理

堆排序與快速排序,歸并排序一樣都是時間復(fù)雜度為O(N*logN)的幾種常見排序方法,

首先我們要理解數(shù)據(jù)結(jié)構(gòu)中的二叉堆。具體介紹請移步文章數(shù)據(jù)結(jié)構(gòu)之堆

我們知道二叉堆的兩大性質(zhì):

  1. 父節(jié)點(diǎn)的鍵值總是大于或者等于任何一個子節(jié)點(diǎn)的鍵值。
  2. 每個節(jié)點(diǎn)的左右子樹都是一個二叉樹堆(都是最大堆或者是最小堆)

步驟

  1. 構(gòu)建最大堆(Build_Max_Heap):若數(shù)組下標(biāo)范圍為0~n,考慮到單獨(dú)一個元素的大根堆,則從下標(biāo)n/2開始的元素均為大根堆。于是只要從n/2-1開始,向前依次構(gòu)造大根堆,這樣就保證,構(gòu)造到某結(jié)點(diǎn)時,它的左右子樹都已經(jīng)是大根堆。

  2. 堆排序(HeapSort):由于堆是用數(shù)組模擬的。得到一個大根堆后,數(shù)組內(nèi)部并不是有序的。因此需要將堆化數(shù)組有序化。 這種操作的思想是:移除根節(jié)點(diǎn),并做最大堆調(diào)整的遞歸運(yùn)算。第一次將heap[0]heap[n-1]交換,再對heap[0...n-2]做最大堆調(diào)整。第二次將heap[0]heap[n-2]交換,再對heap[0..n-3]做最大堆調(diào)整。重復(fù)該操作直至heap[0]heap[1]交換。由于每次都是將最大的數(shù)字并入到后邊的有序區(qū)間。所以操作完成時,整個數(shù)組就是有序的了。

  3. 最大堆調(diào)整(Max_Heapify):該方法是被調(diào)用的,目的是將堆的末端子結(jié)點(diǎn)作調(diào)整。使得子結(jié)點(diǎn)永遠(yuǎn)小于父節(jié)點(diǎn)。

動圖演示:

代碼實(shí)現(xiàn)

def heap_sort(ary):
    n = len(ary)
    first = int(n / 2 - 1)  # 最后一個非葉子節(jié)點(diǎn)
    for start in range(first, -1, -1):  # 構(gòu)造大根堆
        max_heapify(ary, start, n - 1)
    for end in range(n - 1, 0, -1):  # 堆排,將大根堆轉(zhuǎn)換成有序數(shù)組
        ary[end], ary[0] = ary[0], ary[end]
        max_heapify(ary, 0, end - 1)
    return ary


# 最大堆調(diào)整:將堆的末端子節(jié)點(diǎn)作調(diào)整,使得子節(jié)點(diǎn)永遠(yuǎn)小于父節(jié)點(diǎn)
# start為當(dāng)前需要調(diào)整最大堆的位置,end為調(diào)整邊界
def max_heapify(ary, start, end):
    root = start
    while True:
        child = root * 2 + 1  # 調(diào)整節(jié)點(diǎn)的子節(jié)點(diǎn)
        if child > end:
            break
        if child + 1 <= end and ary[child] < ary[child + 1]:
            child = child + 1  # 取較大的子節(jié)點(diǎn)
        if ary[root] < ary[child]:  # 較大的子節(jié)點(diǎn)成為父節(jié)點(diǎn)
            ary[root], ary[child] = ary[child], ary[root]  # 交換
            root = child
        else:
            break

相關(guān)代碼都放置在了我的github

參考資料:

  1. http://blog.csdn.net/morewindows/article/category/859207
  2. http://wuchong.me/blog/2014/02/09/algorithm-sort-summary/
  3. https://visualgo.net/zh/sorting
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,297評論 0 52
  • 概述:排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,819評論 0 15
  • 從一顆飽滿的谷粒到破殼而出的萌芽 從微風(fēng)吹到頭點(diǎn)地的幼苗到半尺身長的灌漿 從稻花飄散的綠桿到稻穗沉甸的金黃 從懷抱...
    C半夏閱讀 799評論 0 1

友情鏈接更多精彩內(nèi)容