幾種常用的排序演算法

Tybyq發表於2018-11-07

什麼是演算法

我想很多程式設計師恐怕誤解了「演算法」的意義,一想到演算法就是動態規劃,機器學習之類的高大名詞。演算法其實就是數學中的「解題過程」,解題過程要求精確,考慮各種情況,需要人看得懂。演算法不需要你在鍵盤上選擇什麼程式語言實現,只需要在本子上詳細的寫出每一個步驟就可以了。

演算法真的很重要嗎?

我經常在社群裡看到有人說初級開發不需要懂演算法,這是非常真切的,很多的業務構建都是很常規的套路,查個資料庫返回,沒有太多複雜的計算,哪需要什麼解題過程。

但是我想遇到稍微複雜一點的業務,或者想要系統執行得更流暢、更有效能的時候,我們就會構思採取什麼樣的方法能讓系統跑得更快、更穩定,於是有了「分散式演算法」等架構方面的演算法。有時候我們發現某個響應很慢,可能就是某個演算法的執行效率過慢,只是我們不知道這也能稱為演算法?最常見的恐怕是多層遍歷,很容易導致效率很低的問題。所以在程式設計的時候要養成思考演算法複雜度的習慣。

演算法對於提高程式碼的執行效率,對問題的抽象有非常大的幫助。演算法學好了,在遇到同一類問題的時候再也不用擠破腦袋來想了,能夠更加容易的聯想到相關的演算法解決問題。程式設計師要想程式設計更容易,不怕各種場景沒遇到過,學好演算法是很有必要的。

排序演算法的運用非常廣泛。各種語言都有自己內建的排序函式,在面試過程中也經常會有排序演算法的考題。總結幾種排序演算法的實現。

這個問題的顯示錶示是:請詳細描述如何將一組數字按從小到大的順序排列。

我首先想到的是:

  1. 找出陣列中最小的一個;

  2. 把這個數放到另一陣列的最後面;

  3. 把這個數從原來的陣列中剔除;

  4. 重複

重複的過程通常涉及到遍歷和遞迴,上面這個解法叫「選擇排序」,用 Python 實現如下:

def select_sort(arr):
    new_arr = []
     # 重複
    for i in range(len(arr)):
        small_index = find_smallest(arr)
         # 把這個數從原來的陣列中剔除;
        smallest = arr.pop(small_index)
         # 把這個數放到另一陣列的最後面;
        new_arr.append(smallest)
    return new_arr
def find_smallest(arr):
     """找出陣列中最小的一個;"""
    smallest = arr[0]
    index = 0
    for e in range(1,len(arr)):
        if arr[e] < smallest:
            smallest = arr[e]
            index = e
    return index

可以看出來,程式碼實現基本上就是用程式語言寫出解題思路。所以很多程式設計進階書都提到一個解決問題的辦法就是離開鍵盤,去上個廁所,在紙上畫一畫。只要是解題思路很詳細,基本上是可以用來當虛擬碼使用的,可以全部放入程式碼的註釋當中。

氣泡排序(Bubble Sort)

  1. 比較前一個數和後一個數,如果前比後大,對換他們的位置;

  2. 迴圈執行

def bubble_sort(arr):
    for i in range(len(arr) - 1):
        for j in range(len(arr) - i - 1):
            if arr[j] > arr[j + 1]:
                tmp = arr[j + 1]
                arr[j + 1] = arr[j]
                arr[j] = tmp
    return arr

快速排序

上面兩種演算法要操作的步驟很多,當陣列太多時就會造成效能過低,我們可以想辦法減少要操作的步驟,從而降低演算法的複雜度,提高執行效率。減少步驟的很多演算法都是將資料分成幾部分來處理,也就是通常說的「分治」,從而不斷減少沒部分需要處理的步驟,選擇排序就是這樣一種演算法:
1.選出第一個元素
2.遍歷每個元素,也就是從第二個開始拿,如果比第一個元素小,放到一個新陣列裡;如果比它大,放到另一個陣列;
3.對兩個新陣列執行同樣的操作;
那什麼時候不需要執行這樣的操作了呢?當陣列的元素個數小於2的時候,不需要比較了,分治策略就結束。

「分治」是一種非常常見的解題思路。因為它能不斷的將問題變成更簡單的問題,最後變成一個顯而易見的事。也就是說它有兩個條件:

  • 基準條件。也就是沒有辦法再分了,足夠簡單了。

  • 分治條件或者叫遞迴條件。能夠進一步縮小需要解決的問題的規模。

分治法在演算法中非常普遍,不是因為他能降低演算法的複雜度,而是他能一步步將複雜的問題變得越來越簡單,規模越來越小,最後變成一個超級簡單的問題,如果能進一步抽象這種過程,就能考執行同樣的抽象步驟解出來來。分治法經常和遞迴用在一起,這就衍生了一種變成方式——函數語言程式設計,如果能多接觸一些遞迴的案例,對於函式式變成和抽象是非常有幫助的。軟體設計就是講一個非常複雜的問題抽象的過程,所以掌握函數語言程式設計和遞迴概念對於抽象能力和軟體設計應該是很有幫助的。

下面實現快速排序:

def quick(arr):
    if len(arr) < 2:
        return arr
    else:
        base = arr[0]
        less = [i for i in arr[1:] if i < base]
        greater = [i for i in arr[1:] if i >= base]
        return quick(less) + [base] + quick(greater)

歸併排序

歸併排序和選擇排序一樣是一種分治遞迴策略:

  1. 從中間分成兩組

  2. 將兩個已經排序好的列表進行合併,合併成的列表就是排序好的
    那怎麼對上述兩個列表排序呢?對兩個列表再執行分組策略
    什麼時候不能繼續了呢?當元素個數小於 2 的時候

具體實現:

def merge_sort(arr):
    # divide to two
    if len(arr) < 2:
        return arr
    mid = int(len(arr)/2)
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)
def merge(left, right):
    result = []
    j = 0
    i = 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    # add the larger part both left and right
    result += left[i:]
    result += right[j:]
    return result

總結


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31557424/viewspace-2219133/,如需轉載,請註明出處,否則將追究法律責任。

相關文章