1. 程式人生 > >排序算法總結

排序算法總結

來看 left 語言 歸並排序 shift 獨立 lis lec 裏的

再一次復習排序算法,總結記錄一下

一 先看兩個不同的遞歸

def func3(x):
    if x>0:
        print(x)
        func3(x-1)
def func4(x):
    if x>0:
        func4(x-1)
        print(x)
func3(5)
func4(5)

func3(5) 輸出5,4,3,2,1

func4(5) 輸出 1,2,3,4,5

要理解這兩個遞歸的不同,func3是遞歸進去的時候進行打印,所以是5,4,3,2,1 . func4是遞歸出來的時候打印,

二 插入排序

def insert_sort(li):
    
for i in range(1, len(li)): tmp = li[i] j = i - 1 while j >= 0 and li[j] > tmp: li[j + 1] = li[j] j = j - 1 li[j + 1] = tmp

將一個記錄插入到已排序好的有序表中,從而得到一個新,記錄數增1的有序表。即:先將序列的第1個記錄看成是一個有序的子序列,然後從第2個記錄逐個進行插入,直至整個序列有序為止.如果碰見一個和插入元素相等的,那麽插入元素把想插入的元素放在相等元素的後面。所以,相等元素的前後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,所以插入排序是穩定的。

效率:

時間復雜度:O(n^2)

三 選擇排序

def select_sort(lst):
    for i in range(len(lst) - 1):
        min = i
        for j in range(i + 1, len(lst)):
            if lst[j] < lst[min]:
                min = j
        lst[i], lst[min] = lst[min], lst[i]

基本思想:在要排序的一組數中,選出最小(或者最大)的一個數與第1個位置的數交換;然後在剩下的數當中再找最小(或者最大)的與第2個位置的數交換,依次類推,直到第n-1個元素(倒數第二個數)和第n個元素(最後一個數)比較為止。

操作方法:

第一趟,從n 個記錄中找出關鍵碼最小的記錄與第一個記錄交換;

第二趟,從第二個記錄開始的n-1 個記錄中再選出關鍵碼最小的記錄與第二個記錄交換;

以此類推.....

第i 趟,則從第i 個記錄開始的n-i+1 個記錄中選出關鍵碼最小的記錄與第i 個記錄交換,直到整個序列按關鍵碼有序

效率:

時間復雜度:O(n^2)

四 冒泡排序

def bubble_sort(lst):
    for i in range(len(lst) - 1):
        exchange = False
        for j in range(len(lst) - i - 1):
            if lst[j] > lst[j + 1]:
                lst[j], lst[j + 1] = lst[j + 1], lst[j]
                exchange = True
        # 如果發現一趟沒有任何交換,說明已經排好了,剩下的就不用做了
        if not exchange:
            break

基本思想:在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沈,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。這裏還進行了優化.

效率:

時間復雜度:O(n^2)

五 快速排序

第一種方式

def inner_sort(lst, left, right):
    tem = lst[left]
    while left < right:
        while left < right and lst[right] >= tem:
            right -= 1
        lst[left] = lst[right]
        while left < right and lst[left] <= tem:
            left += 1
        lst[right] = lst[left]
    lst[left] = tem
    return left


def quick_sort_x(lst, left, right):
    while left < right:
        mid = inner_sort(lst, left, right)
        quick_sort_x(lst, left, mid - 1)
        quick_sort_x(lst, mid + 1, right)


def quick_sort(lst):
    quick_sort_x(lst, 0, len(lst) - 1)

第二種方式

def quick_sort(lst):
    def qsort(lst, begin, end):
        if begin >= end:
            return
        pivot = lst[begin]
        i = begin
        for j in range(begin + 1, end + 1):
            if lst[j] < pivot:
                i += 1
                lst[i], lst[j] = lst[j], lst[i]
        lst[begin], lst[i] = lst[i], lst[begin]
        qsort(lst, begin, i - 1)
        qsort(lst, i + 1, end)

    qsort(lst, 0, len(lst) - 1)

基本思想:

1)選擇一個基準元素,通常選擇第一個元素或者最後一個元素,

2)通過一趟排序將待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另一部分記錄的 元素值比基準值大。

3)此時基準元素在其排好序後的正確位置

4)然後分別對這兩部分記錄用同樣的方法繼續進行排序,直到整個序列有序。

快速排序的示例:

效率:

快速排序是通常被認為在同數量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化為冒泡排序。為改進之,通常以“三者取中法”來選取基準記錄,即將排序區間的兩個端點與中點三個記錄關鍵碼居中的調整為支點記錄。快速排序是一個不穩定的排序方法。

六 歸並排序

def one_merge_sort(lst, begin, mid, end):
    left = begin
    right = mid + 1  # 這裏的mid是前半段結束的那個元素的下標,一定要理解,因為下方是left <= mid ,
    temp_list = []
    while left <= mid and right <= end:
        if lst[left] < lst[right]:
            temp_list.append(lst[left])
            left += 1
        else:
            temp_list.append(lst[right])
            right += 1
    while left <= mid:
        temp_list.append(lst[left])
        left += 1
    while right <= end:
        temp_list.append(lst[right])
        right += 1
    # 註意這裏一定不能寫成lst[:]=temp_list因為在每個遞歸中,只能替換已經排好的部分,而不是全部替換
    lst[begin:end + 1] = temp_list


def _merge_sort(lst, begin, end):
    if begin < end:
        mid = (begin + end) // 2
        _merge_sort(lst, begin, mid)
        _merge_sort(lst, mid + 1, end)
        one_merge_sort(lst, begin, mid, end)


def merge_sort(lst):
    _merge_sort(lst, 0, len(lst) - 1)

歸並排序是建立在歸並操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。首先考慮下如何將將二個有序數列合並。這個非常簡單,只要從比較二個數列的第一個數,誰小就先取誰,然後再進行比較,如果有數列為空,那直接將另一個數列的數據依次取出即可。可以看出合並有序數列的效率是比較高的,可以達到O(n)。

解決了上面的合並有序數列問題,再來看歸並排序,其的基本思路就是將數組分成二組A,B,如果這二組組內的數據都是有序的,那麽就可以很方便的將這二組數據進行排序。如何讓這二組組內數據有序了?可以將A,B組各自再分成二組。依次類推,當分出來的小組只有一個數據時,可以認為這個小組組內已經達到了有序,然後再合並相鄰的二個小組就可以了。這樣通過先遞歸的分解數列,再合並數列就完成了歸並排序。

七 堆排序

def shift(data, low, hight):
"""假設除堆頂點之外,其余部分都已是堆的一次的調整"""
    i = low
    j = 2 * i + 1
    tem = data[i]
    while j <= hight:
        if j + 1 <= hight and data[j + 1] > data[j]:
            j += 1
        if data[j] > tem:
            data[i] = data[j]
            i = j
            j = 2 * i + 1
        else:
            break
    data[i] = tem
 
 
def heap_sort(data):
    n = len(data)
#這個for 循環是為了建堆
    for i in range(n // 2 - 1, -1, -1):
        shift(data, i, n - 1)
#此時已建好堆,接下來就是從堆中取數
    for i in range(n - 1, -1, -1):
        data[i], data[0] = data[0], data[i]
        shift(data, 0, i - 1)

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

基本思想:堆的定義如下:具有n個元素的序列(k1,k2,...,kn),當且僅當滿足父節點都大於(或都小於)子節點時稱之為堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最小項(小頂堆)。若以一維數組存儲一個堆,則堆對應一棵完全二叉樹,且所有非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。如:

(a)大頂堆序列:(96, 83,27,38,11,09)

(b) 小頂堆序列:(12,36,24,85,47,30,53,91)

初始時把要排序的n個數的序列看作是一棵順序存儲的二叉樹(一維數組存儲二叉樹),調整它們的存儲序,使之成為一個堆,將堆頂元素輸出,得到n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然後對前面(n-1)個元素重新調整使之成為堆,輸出堆頂元素,得到n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。稱這個過程為堆排序。

實現方法如下:

  1. 先假設一個堆(不能稱為堆,因為他的頂點不滿足堆的條件,這裏姑且這麽叫),這個堆除了頂點之外,其他各節點都是一個完全二叉樹的堆,對這個堆做調整,使之成為一個真正的完全二叉樹的堆,這個過程就是shift函數,
  2. 從最後一下有子節點的父節點開始循環,這個父節點和他的所有子節點就可以看成是 1) 所列示的情況.在循環中執行shift函數,當這個循環結束之後這個堆也已經建好,
  3. 從堆頂依次數,方法如下,1 .先把堆頂的數取出來,把最後一個數拿上去,把最後一個數拿上去之後,這個堆又變成1)中形式的堆,又執行一次1)的操作,再取數就行,為了簡化理解,可以把堆頂的數取出來之後放入一個空數組中,但是這裏為了不開多余的內存就把拿出來的數放在原列表的最後一個,下次循環時,就把這個序列的n-1,作為堆進行循環.

時間復雜度也為:O(nlogn )

八 總結

  其實各種算法,特別是後三種較復雜的算法,要用語言把這些算法的過程描述清楚,真的有點因難,掌握這些算法唯一的方法就是多復習,多寫幾次

排序算法總結