1. 程式人生 > >幾種搜尋、排序演算法的python實現

幾種搜尋、排序演算法的python實現

一些複雜度的例項

n 對數階 log2n 線性階n 平方階n2 指數階2n
100 7 100 10000 超標
1000 10 1000 1000000 超標
1000000 20 1000000 1000000000000 嚴重超標

搜尋演算法

搜尋最小值

def indexOfMin(lyst):
    '''返回最小項的索引'''
    minIndex=0
    currentIndex = 1
    while currentIndex<len(lyst):
        if lyst[currentIndex]<lyst[minIndex]:
            minIndex = currentIndex
        currentIndex +=1
return minIndex

這個演算法的複雜度為O(n)

順序搜尋一個列表

def sequentialSearch(target,lyst):
    '''如果找到目標返回他在列表中的索引否則返回-1'''
    position = 0
    while position<len(lyst):
        if target == lyst[position]:
            return position
        position += 1
    return -1

順序搜尋的分析要考慮如下這三種情況:

  1. 在最壞的情況下,目標位於列表的末尾,或者根本不在列表中。那麼,演算法必須訪問每一項,並且對大小為n的列表要執行n次迭代。因此順序搜尋的最壞情況的複雜度為O(n)
  2. 在最好的情況下,演算法只進行了一次迭代就在第一個位置找到目標項,複雜度為O(1)
  3. 要確定平均情況,把在每一個可能的位置找到目標項所需要的迭代次數相加,並用總和除以n。因此演算法執行了(n+1)/2次迭代。複雜度仍為O(n).

有序列表的二叉樹搜尋

# 假設列表升序排列
def binarySearch(target, sortedLyst):
    left = 0
    right = len(sortedLyst) - 1
    while left <= right:
        midpoint = (left + right) // 2
        if target == sortedLyst[midpoint]:
            return
midpoint elif target < sortedLyst[midpoint]: right = midpoint - 1 else: left = midpoint + 1 return -1

二叉搜尋的最壞情況的複雜度為O(log2n)

基本排序演算法

選擇排序

工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。

def selectionSort(lyst):
    i = 0
    while i < len(lyst) - 1:
        minIndex = i
        j = i + 1
        while j < len(lyst):              # 尋找第i項以後的最小項與第i項交換
            if lyst[j] < lyst[minIndex]:
                minIndex = j
            j += 1
        if minIndex != i:
            lyst[i], lyst[minIndex] = lyst[minIndex], lyst[i]
        i += 1

選擇排序的複雜度的為O(n2)

氣泡排序

氣泡排序演算法的原理如下:

  1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
  3. 針對所有的元素重複以上的步驟,除了最後一個。
  4. 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
def bubbleSort(lyst):
    n = len(lyst)
    while n > 1:
        swapped = False  #記錄每輪迴圈是否一次都沒有交換過資料(即已經排好序了)以便儘早退出迴圈。
        i = 1
        while i < n:
            if lyst[i] < lyst[i - 1]:   # 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
                lyst[i], lyst[i - 1] = lyst[i - 1], lyst[i]
                swapped = True
            i +=1
        if not swapped:
            return None
        n -=1

氣泡排序複雜度為O(n2)

插入排序

類似於人們排列手中撲克牌的順序。也就是說,如果你按照順序排 好了前i-1張牌,抓取第i張牌並且與手中的這些牌進行比較,直到找到合適的位置

假設有一組無序序列 R0,R1,...,Rn1

  1. 我們先將這個序列中下標為 0 的元素視為元素個數為 1 的有序序列。
  2. 然後,我們要依次把 R1,R2,...,Rn1 插入到這個有序序列中。所以,我們需要一個外部迴圈,從下標 1 掃描到 n-1 。
  3. 接下來描述插入過程。假設這是要將 Ri 插入到前面有序的序列中。由前面所述,我們可知,插入Ri時,前 i-1 個數肯定已經是有序了。

所以我們需要將RiR0 ~ Ri1 進行比較,確定要插入的合適位置。這就需要一個內部迴圈,我們一般是從後往前比較,即從下標 i-1 開始向 0 進行掃描。

def insertionSort(lyst):
    i = 1
    while i < len(lyst):
        itemToInsert = lyst[i]
        j = i - 1
        while j >= 0:
            if lyst[j] > itemToInsert:
                lyst[j + 1] = lyst[j]
                j -= 1
            else:
                break
        lyst[j + 1] = itemToInsert
        i += 1

更快的排序

快速排序

快速排序所使用的策略可以概括如下:

  1. 首先,從列表的中點位置選取一項。在這一項叫做基準點。

  2. 將列表中的項分割槽,以便小於基準點的所有項都移動到基準點左邊,而剩下的項都移動到基準點的右邊。根據相關的實際項,基準點自身的最終位置也是變化的。例如,如果基準點自身是最大的項,它會位於列表的最右邊,如果基準點是最小值,它會位於最左邊。但是,不管基準點最終位於何處,這個位置都是它在 完全排序的列表中的最終位置。

  3. 分而治之。對於基準點分割列表而形成的子列表,遞迴地重複該過程。一個子列表包含了基準點左邊所有的項(現在是較小的項),另一個子列表包含了基準點右邊的所有的項(現在是較大的項)。

  4. 每次遇到少於兩個項的一個子列表,就介紹這個過程。

步驟 列表
假設子列表是由數字組成的,其中包含一個基準點14 12 19 17 18 14 11 15 13 16
將基準點和最後一項交換 12 19 17 18 16 11 15 13 14
在第一項之前建立一個邊界 * 12 19 17 18 16 11 15 13 14
掃描小於基準點的第一項 * 12 19 17 18 16 11 15 13 14
將這一項和邊界之後的第一項交換。在這個例子中,
該項是與自身交換
* 12 19 17 18 16 11 15 13 14
將邊界向後移動 12 * 19 17 18 16 11 15 13 14
掃描小於基準點的第一項 12 * 19 17 18 16 11 15 13 14
將這一項和邊界之後的第一項交換 12 * 11 17 18 16 19 15 13 14
將邊界向後移動 12 11 * 17 18 16 19 15 13 14
掃描小於基準點的第一項 12 11 * 17 18 16 19 15 13 14
將這一項和邊界之後的第一項交換 12 11 * 13 18 16 19 15 17 14
將邊界向後移動 12 11 13 * 18 16 19 15 17 14
掃描小於基準點的第一項;然而這次沒有這樣的一項 12 11 13 * 18 16 19 15 17 14
將這一項和邊界之後的第一項交換。此時小於基準
項的所有項都在基準項的左邊;剩餘的項在基準點
的右邊
12 11 13 * 14 16 19 15 17 18
import random
def quicksort(lyst):
    quicksortHelper(lyst, 0, len(lyst) - 1)
def quicksortHelper(lyst, left, right):
    if left < right:
        pivolocation = partition(lyst, left, right)
        quicksortHelper(lyst, left, pivolocation - 1)
        quicksortHelper(lyst, pivolocation + 1, right)
def partition(lyst, left, right):
    # 找到基準點與最後一項交換
    middle = (left + right) // 2
    pivot = lyst[middle]
    lyst[middle], lyst[right] = lyst[right], lyst[middle]
    # 在第一項前建立一個邊界
    boundary = left
    # 將小於基準點的第一項與邊界後的第一項交換
    for index in range(left, right):
        if lyst[index] < pivot:
            lyst[index], lyst[boundary] = lyst[boundary], lyst[index]
            boundary += 1
    # 全部掃描完後 將基準點與邊界後的那一項交換
    lyst[right], lyst[boundary] = lyst[boundary], lyst[right]
    return boundary
def main(size=20, sort=quicksort):
    lyst = []
    for count in range(size):
        lyst.append(random.randint(1, size + 1))
    print(lyst)
    sort(lyst)
    print(lyst)
if __name__ == '__main__':
    main()

合併排序(歸併排序)

非正式的概述;

  1. 計算一個列表的中間位置,並且遞迴地排序其左邊和右邊的子列表(分而治之).
  2. 將兩個排好序的子列表重新合併為單個的排好序的列表
  3. 當子列表不再能夠劃分的時候,停止這個過程
from TestClass import Array


def mergeSort(lyst):
    '''
    合併的過程中使用了和列表相同大小的一個數組(這個陣列是在根目錄下定義的)。這個陣列名為copyBuffer。為了避免每次呼叫merge的時候為copyBuffer分配和釋放記憶體的開銷。只在mergeSort中分配一次該緩衝區,並且在後續將其作為一個引數傳遞給mergeSortHelper和merge。每次呼叫mergeSortHelper的時候,都需要知道他所操作的子列表的邊界。這些邊界通過另外的兩個引數low,high來提供。
    '''
    copyBuffer = Array(len(lyst))
    mergeSortHelper(lyst, copyBuffer, 0, len(lyst) - 1)

def mergeSortHelper(lyst, copyBuffer, low, high):
    if low < high:
        middle = (low + high) // 2
        mergeSortHelper(lyst, copyBuffer, low, middle)
        mergeSortHelper(lyst, copyBuffer, middle + 1, high)
        merage(lyst, copyBuffer, low, middle, high)

def merage(lyst, copyBuffer, low, middle, high):
    '''
    :param low: 第一個子列表的第一索引
    :param middle: 第一個子列表的最後一索引
           middle+1:第二個子列表的第一索引
    :param high: 第二個子列表的最後一索引
    這個函式將兩個排好序的子列表合併到一個大的拍好序的子列表中。

    '''
    i1 = low  # 將索引指標設定為每個子列表的第一索引
    i2 = middle + 1

    for i in range(low, high + 1):  # 從每個子列表的第一項開始重複地比較各項。將較小的項從其子列表中複製到複製快取即copyBuffer中
        if i1 > middle:  # 並繼續處理子列表中的下一項。重複這個過程,直到兩個子列表中的所有的項都已經複製過。如果先到達其中一個子列表的末尾,通過從另一個子列表複製剩餘的項,從而結束這個步驟。
            copyBuffer[i] = lyst[i2] 
            i2 += 1
        elif i2 > high:
            copyBuffer[i] = lyst[i1]
            i1 += 1
        elif lyst[i1] < lyst[i2]:
            copyBuffer[i] = lyst[i1]
            i1 += 1
        else:
            copyBuffer[i] = lyst[i2]
            i2 += 1
    for i in range(low, high + 1):  # 將copyBuffer中low 和high之間的部分,複製回lyst中對應的位置。
        lyst[i] = copyBuffer[i]

排序法 平均時間 最差情形 穩定度 額外空間 備註
冒泡 O(n2) O(n2) 穩定 O(1) n小時較好
選擇 O(n2) O(n2) 不穩定 O(1) n小時較好
插入 O(n2) O(n2) 穩定 O(1) 大部分已排序時較好
基數 O(logRB) O(logRB) 穩定 O(n) B是真數(0-9),
R是基數(個十百)
Shell O(nlogn) O(ns) (1