1. 程式人生 > >排序演算法的Python實現以及時間分析

排序演算法的Python實現以及時間分析

選擇排序

首先,找到陣列中最小的那個元素,其次,將它和陣列的第一個元素交換位置(如果第一個元素就是最小元素那麼它就和自己交換)。再次,在剩下的元素中找到最小的元素,將它與陣列的第二個元素交換位置。如此往復,直到將整個陣列排序。這種方法叫做選擇排序,因為它在不斷地選擇剩餘元素之中的最小者。

1. 輔助函式——exchange()

#輔助函式——交換兩個數
def exchange(a,i,j):
    temp = a[i]
    a[i] = a[j]
    a[j] = temp

2. 選擇排序主函式——selection()

#選擇排序函式(無返回值)
def selection(a):
    length = len(a)
    for i in range(length):
        minIndex = i
        for j in range(i+1,length):
            if a[j] < a[minIndex]:
                minIndex = j
        exchange(a,i,minIndex)

插入排序

通常人們整理橋牌的方法是一張一張的來,將每一張牌插入到其他已經有序的牌中的適當位置。在計算機的實現中,為了給要插入的元素騰出空間,我們需要將其餘所有元素在插入之前都向右移動一位。這種演算法叫做插入排序。

#插入排序函式
def insertion(a):
    length = len(a)
    for i in range(1,length):
        j = i
        while (j>0 and a[j]<a[j-1]):
            exchange(a,j,j-1)
            j -= 1

歸併排序

歸併排序體現的是一種分治思想(Divide and conquer),下面是其排序的步驟:

1)將陣列一分為二(Divide array into two halves)

2)對每部分進行遞迴式地排序(Recursively sort each half)

3)合併兩個部分(Merge two halves)

1. merge()函式:

具體步驟如下:

1)給出原陣列a[],該陣列的low到mid,mid+1到high的子陣列是各自有序的。

2)將陣列複製到輔助陣列(auxiliary array)中,兩部分陣列的首元素分別以i和j的下標,給原陣列首元素以k的下標。

3)比較i下標和j下標的元素,將較小值賦到k下標位置的元素內

,然後對k和賦值的下標進行遞增。

4)重複上述過程,直到比較完全部元素。

def merge(a,aux,low,mid,high):
    for k in range(low,high+1):
        aux[k] = a[k] #輔助陣列
    i = low
    j = mid+1
    for k in range(low,high+1):
        if i > mid:
            a[k] = aux[j]
            j += 1
        elif j > high:
            a[k] = aux[i]
            i += 1
        elif aux[i]>aux[j]:
            a[k] = aux[j]
            j += 1
        else:
            a[k] = aux[i]
            i += 1

2. sort()函式

要對子陣列a[lo..hi]進行排序,先將它分為a[lo..mid]和a[mid+1..hi]兩部分,分別通過遞迴呼叫將它們單獨排序,最後將有序的子陣列歸併為最終的排序結果。

def sort(a,aux,low,high):
    if high <= low:
        return
    mid = (low+high)//2 #保證mid是整數
    sort(a,aux,low,mid)
    sort(a,aux,mid+1,high)
    merge(a,aux,low,mid,high)

3. mergeSort()函式

為了保證歸併排序函式mergeSort()輸入只有未排序的陣列,這裡呼叫前面的輔助函式sort():

def mergeSort(a):
    low = 0
    high = len(a)-1 #注意:0~len(a)-1
    aux = [0] * len(a)
    sort(a,aux,low,high)
    return a

4. 歸併排序結果

未排序陣列:
[4, 6, 2, 5, 16, 17, 7, 11, 19, 15, 0, 1, 18, 13, 9, 10, 14, 8, 12, 3]
歸併排序後陣列:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

5. 歸併排序與選擇排序、插入排序的比較

這裡我設定sortCompare函式中的陣列大小為1000,陣列個數為100個,下面是3種排序演算法的執行時間:

SelectionSort's total time:
5.374305248260498
InsertionSort's total time:
11.281434535980225
MergeSort's total time:
0.4907994270324707

歸併排序的時間複雜度為O(nlogn),選擇排序和插入排序的時間複雜度均為O(n^2),上面的執行時間也直觀說明了這一點。

快速排序

快速排序是一種分治的排序演算法。它將一個數組分成兩個子陣列,將兩部分獨立地排序。

快速排序和歸併排序是互補的:

歸併排序:1)將陣列分成兩個子陣列分別排序,並將有序的子陣列歸併以將整個陣列排序;2)遞迴呼叫發生在處理整個陣列之前;3)一個數組被等分為兩半。

快速排序:1)則是當兩個子陣列都有序時,整個陣列也就自然有序了;2)遞迴呼叫發生在處理整個陣列之後;3)切分(partition)的位置取決於陣列的內容。

1. 切分---partition()

切分方法:先隨意地取a[lo]作為切分元素(即那個將會被排定的元素),然後我們從陣列的左端開始向右掃描直到找到一個大於等於它的元素,再從陣列的右端開始向左掃描直到找到一個小於等於它的元素。這兩個元素是沒有排定的,因此我們交換它們的位置。如此繼續,當兩個指標相遇時,我們只需要將切分元素a[lo]和左子元素最右側的元素a[j]交換然後返回j即可。

def partition(a,low,high):
    i = low
    j = high + 1
    while True:
        #左邊指標往右移動
        i += 1
        while a[i] < a[low]:
            if i == high:
                break
            i += 1
        #右邊指標往左移動
        j -= 1
        while a[low] < a[j]:
            if j == low:
                break
            j -= 1
        #如果左右指標交叉,說明已經排序好了
        if i >= j:
            break
        #交換a[i]、a[j]
        exchange(a,i,j)
    exchange(a,low,j)
    return j

2. sort()函式

快速排序遞迴地將子陣列a[lo..hi]排序,先用partition()方法將a[j]放到一個合適位置,然後再用遞迴呼叫將其他位置的元素排序

def sort(a,low,high):
    if low >= high:
        return
    j = partition(a,low,high)
    sort(a,low,j-1)
    sort(a,j+1,high)

3. quickSort()函式

為了保證快速排序函式quickSort()輸入只有未排序的陣列,這裡呼叫前面的輔助函式sort():

def quickSort(a):
    length = len(a)
    sort(a,0,length-1)

4. 快速排序結果

未排序陣列:
[4, 6, 2, 5, 16, 17, 7, 11, 19, 15, 0, 1, 18, 13, 9, 10, 14, 8, 12, 3]
快速排序後陣列:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

5. 快速排序與歸併排序、選擇排序、插入排序的比較

這裡我設定sortCompare函式中的陣列大小為1000,陣列個數為100個,下面是4種排序演算法的執行時間:

SelectionSort's total time:
5.730731010437012
InsertionSort's total time:
11.002668857574463
MergeSort's total time:
0.4718046188354492
QuickSort's total time:
0.30764150619506836

快速排序和歸併排序的時間複雜度為O(nlogn),選擇排序和插入排序的時間複雜度均為O(n^2),上面的執行時間也直觀說明了這一點。

堆排序

堆的定義

在二叉堆的陣列中,每個元素都要保證大於等於另兩個特定位置的元素,也就是父節點一定要大於等於它的兩個子節點,此時稱為堆有序。

(1)由下至上的堆有序化(上浮)

當插入一個元素時,我們一般先放到陣列的末尾,然後讓它一點點向上移動,直到移動到它的父節點(記住:k結點的父節點是)比他大,這種操作叫上浮swim(),完成這個操作後陣列重新恢復有序性。

(2)由下至上的堆有序化(下沉)

一般刪除最大鍵值的方式是將最大鍵值與陣列的末尾進行交換,然後把最大鍵值的指向null,可是由於陣列的末尾肯定不是最大的父節點,所以需要將它一點點向下移動,直到移動到它的子節點比它小為止,這種操作就叫下沉sink(),完成這個操作後陣列重新恢復有序性。

#下沉函式
def sink(a,k,N):
    while 2*k < N:
        #選出兩個子結點中大的那個
        j = 2 * k
        if j<N and a[j]<a[j+1]: #j<N是為了保證j+1<=N
            j += 1
        #如果結點k不比兩個子節點小,說明下沉結束
        if not a[k]<a[j]:
            break
        #下沉
        exchange(a,k,j)
        k = j

(3)插入元素、刪除最大元素操作

堆排序

堆排序可以分成兩個階段。在堆的構造階段中,我們將原始陣列重新組織安排進一個堆中;然後在下沉排序階段,我們從堆中按遞減順序取出所有元素並得到排序結果。

(1)堆的構造

目的:N個給定的元素構造一個堆。

一個高效的方法:從右向左用sink()函式構造子堆。陣列的每個位置都已經是一個子堆的根結點了,sink()對於這些子堆也適用。如果一個結點的兩個子節點都已經是堆了,那麼在該結點上呼叫sink()可以將它們變成一個堆。這個過程會遞迴得建立起堆的秩序

下圖是一個堆構造的例子,首先呼叫sink(5,11),然後呼叫sink(4,11),一直到sink(1,11):

    #第一步:堆的構造
    k = N // 2 #向下整除
    while k >= 0:
        sink(a,k,N-1)
        k -= 1

(2)下沉排序

堆排序的主要工作都是在第二階段完成的。這裡我們將堆中的最大元素刪除,然後放入堆縮小後陣列中空出的位置

這個過程和選擇排序有些類似,但所需的比較要少得多,因為堆提供了一種從未排序部分找出最大元素的有效方法。

下圖是一個下沉排序的例子:

    #第二步:下沉排序
    while N > 0:
        exchange(a,0,N-1)
        N -= 1
        sink(a,0,N-1)

(3)堆排序主函式程式碼

#堆排序主函式
def heapSort(a):
    N = len(a)
    #第一步:堆的構造
    k = N // 2 #向下整除
    while k >= 0:
        sink(a,k,N-1)
        k -= 1
    #第二步:下沉排序
    while N > 0:
        exchange(a,0,N-1)
        N -= 1
        sink(a,0,N-1)

各大排序演算法執行時間對比

1. 陣列大小為1000,陣列個數為100

1) random.random()

這個函式隨機生成0-1之間的浮點數。

SelectionSort's total time:
5.555765151977539
InsertionSort's total time:
11.97522497177124
MergeSort's total time:
0.540255069732666
QuickSort's total time:
0.31798601150512695
HeapSort's total time:
0.6184689998626709

2) random.randint(1,10)

這個函式隨機生成1-10之間的整數。

SelectionSort's total time:
5.795068740844727
InsertionSort's total time:
10.428340673446655
MergeSort's total time:
0.5053162574768066
QuickSort's total time:
0.2666609287261963
HeapSort's total time:
0.5388846397399902

2. 陣列大小為10,陣列個數為100000

1) random.random()

這個函式隨機生成0-1之間的浮點數。

SelectionSort's total time:
1.2794384956359863
InsertionSort's total time:
1.2449653148651123
MergeSort's total time:
2.4802358150482178
QuickSort's total time:
1.2663071155548096
HeapSort's total time:
2.2440004348754883

2) random.randint(1,10)

這個函式隨機生成1-10之間的整數。

SelectionSort's total time:
1.246002197265625
InsertionSort's total time:
1.0966532230377197
MergeSort's total time:
2.417612075805664
QuickSort's total time:
1.1484196186065674
HeapSort's total time:
2.0112228393554688