python 常見算法


  python雖然具備很多高級模塊,也是自帶電池的編程語言,但是要想做一個合格的程序員,基本的算法還是需要掌握,本文主要介紹列表的一些排序算法

  遞歸是算法中一個比較核心的概念,有三個特點,1 調用自身  2 具有結束條件  3 代碼規模逐漸減少

  舉例:以下四個函數只有兩個為遞歸

  func3和func4 但是輸出是不同的比如func3(5)輸出為5,4,3,2,1func4(5)輸出為1,2,3,4,5,有一個遞歸層級在里面。

  兩個概念:時間復雜度和空間復雜度

  時間復雜度:用於體現算法執行時間的快慢,用O表示。一般常用的有:幾次循環就為O(n幾次方)  循環減半的O(logn)

  空間復雜度:用來評估算法內存占用大小的一個式子,通常情況下會選擇使用空間換時間

 

  e.g 列表查找:從列表中查找指定元素

    輸入:列表、待查找元素

    輸出:元素下標或未查找到元素 

    version 1 順序查找:從列表中的第一個元素開始,順序進行搜索,直到找到為止,復雜度為O(n)

       version 2 二分查找:從有序列表中,通過待查值與中間值比較,以減半的方式進行查找,復雜度為O(logn)

    代碼如下:

list = [1,2,3,4,5,6,7,8,9]
element = 7
def ord_sear(list,element):
    for i in range(0,len(list)):
        if list[i] == element:
            print('list[{0}]={1}'.format(i,element))
            return i
    else:
        print('not found')

def bin_sear(list,element):
    low = 0
    high = len(list)-1
    while low<=high:
        mid = (low+high)//2
        if element == list[mid]:
            print('list[{0}]={1}'.format(mid,element))
            return mid
        elif element > list[mid]:
            low =mid +1
        else:
            high =mid -1
    return None


i = bin_sear(list,element)
j = ord_sear(list,element)

  二分查找雖然在時間復雜度上優於順序查找,但是有比較苛刻的條件,即列表必須為有序的。下面將介紹列表排序:

  列表排序是編程中一個最基本的方法,應用場景非常廣泛,比如各大音樂、閱讀、電影、應用榜單等,雖然python為我們提供了許多排序的函數,但我們那排序來作為算法的練習再好不過。

  首先介紹的是最簡單的三種排序方式:1 冒泡排序 2 選擇排序 3 插入排序

  冒泡排序:列表中每相鄰兩個如果順序不是我們預期的大小排列,則交換。時間復雜度O(n^2)

def bubble(list):
    high = len(list)-1      #指定一個最高位
    while high>0:
        for i in range(0,high):
                if list[i]>list[i+1]:   #如果比下一位大
                    list[i],list[i+1] = list[i+1],list[i]   #交換位置
        high -=1            #最高位減1
    return list #返回列表

print(bubble(list))

  優化一下:

list = [3,1,5,7,8,6,2,0,4,9]
def bubble(list):
    high = len(list)-1      #定一個最高位

    for j in range(high,0,-1):
        exchange = False    #交換的標志,如果提前排好序可在完整遍歷前結束
        for i in range(0,j):
            if list[i]>list[i+1]:   #如果比下一位大
                list[i],list[i+1] = list[i+1],list[i]   #交換位置
                exchange = True #設置交換標志
        if exchange == False:
            return list     # return list #返回列表
print(bubble(list))

  選擇排序:一趟遍歷選擇最小的數放在第一位,再進行下一次遍歷直到最后一個元素。復雜度依然為O(n^2)

list = [3, 1, 5, 7, 8, 6, 2, 0, 4, 9]
def choice(list):
    for i in range(0,len(list)):
        min_loc = i
        for j in range(i+1,len(list)):
            if list[min_loc]>list[j]:   #最小值遍歷比較
                min_loc = j
        list[i],list[min_loc] = list[min_loc],list[i]
    return list
print(choice(list))

  插入排序:將列表分為有序區和無序區,最開始的有序區只有一個元素,每次從無序區選擇一個元素按大小插到有序區中

list = [3,1,5,7,8,6,2,0,4,9]
def cut(list):
    for i in range(1,len(list)):
        temp = list[i]
        for j in range(i-1,-1,-1):  #從有序區最大值開始遍歷
            if list[j]>temp:    #如果待插入值小於有序區的值
                list[j+1] = list[j] #向后挪一位
                list[j] = temp  #將temp放進去
    return list
print(cut(list))

  這三種排序方式時間復雜度都是O(n^2),不太高效,所以下面介紹幾種更高效的排序方式

  1 快速排序:好寫的排序里最快的,快的排序里最好寫的。步驟為1 提取 2 左右分開 3 遞歸調用

list = [3,1,5,7,8,6,2,0,4,9]
def partition(left=0,right=len(list)-1,list):
    temp = list[left]
    while left < right:
        while left<right and list[right]>temp:      #當右邊值較大時,值不動
            right -=1
        list[left]=list[right]          #否則移動到左邊
        while left<right and list[left]<temp:
            left +=1
        list[right]=list[left]
    list[left]=temp
    return left     #返回leftright都可以,值是一樣的
def quick_sort(left,right,list):
    while left<right:      #迭代中斷
        mid = partition(left,right,list)        #獲取中間位置
        quick_sort(left,mid-1,list)     #小序列進一步迭代
        quick_sort(mid+1,right,list)    #大序列進一步迭代
    return list         #返回列表
print(quick_sort(left,right,list))

  快排的時間復雜度最佳情況是O(nlogn),最差情況是O(n^2)

  下面要介紹堆排序了。在介紹堆排序之前先簡單提一下樹的概念:

  樹是一種數據結構(比如目錄),樹是一種可以遞歸的數據結構,相關的概念有根節點、葉子節點,樹的深度(高度),樹的度(最多的節點),孩子節點/父節點,子樹等。

  在樹中最特殊的就是二叉樹(度不超過2的樹),二叉樹又分為滿二叉樹和完全二叉樹,見下圖:

  二叉樹的儲存方式有:1 鏈式儲存 2 順序儲存(列表)

  父節點和左孩子節點的編號下表的關系為 i  -->  2i+1,右孩子則是i  --> 2i+2  最后一個父節點為(len(list)//2-1)  由此可以通過父親找到孩子或相反。

  知道了樹就可以說說堆了,堆分為大根堆和小根堆,分別的定義為:一棵完全二叉樹,滿足任一節點都比其孩子節點大或者小。

  堆排序的過程:

    1. 建立堆
    2. 得到堆頂元素,為最值
    3. 去掉堆頂,將最后一個元素放到堆頂,進行再一次堆排序(迭代)
    4. 第二次的堆頂為第二最值
    5. 重復3,4直到堆為空

  代碼為:

list = [3, 1, 5, 7, 8, 6, 2, 0, 4, 9]
def sift(low, high, list):#low為父節點,high為最后的節點編號
    i = low
    j = 2 * i + 1       #子節點位置
    temp = list[i]      #存放臨時變量
    while j <= high:    #遍歷子節點到最后一個
        if j < high and list[j] < list[j + 1]:#如果第二子節點大於第一子節點
            j += 1      
        if temp < list[j]:      #如果父節點小於子節點的值
            list[i] = list[j]   #父子交換位置
            i = j               #進行下一次編號
            j = 2 * i + 1
        else:
            break       #遍歷完畢退出
    list[i] = temp      #歸還臨時變量
def heap_sort(list):
    n = len(list)
    for i in range(n // 2 - 1, -1, -1): #從最后一個父節點開始
        sift(i, n-1, list)#完成堆排序
    for i in range(n - 1, -1, -1):#開始排出數據
        list[0], list[i] = list[i], list[0]#首尾交換
        sift(0, i - 1, list)    #進行新一輪堆排序
    return list
print(heap_sort(list))

   歸並排序:假設列表中可以被分成兩個有序的子列表,如何將這兩個子列表合成為一個有序的列表成為歸並。

  原理如下圖:

  代碼如下:

def merg(low,high,mid,list):
    i = low
    j = mid +1
    list_temp = []      #定義臨時列表
    while i <=mid and j <=high:
        if list[i]<=list[j]:        #分別比較有序子列表元素的大小
            list_temp.append(list[i])   #添加進臨時列表中
            i +=1
        else:
            list_temp.append(list[j])
            j +=1
    while i <= mid:
        list_temp.append(list[i])
        i +=1
    while j <= high:
        list_temp.append(list[j])
        j +=1
    list[low:high+1]=list_temp  #將已完成排序的列表賦值給原列表相應位置
def merge_sort(low,high,list):
    if low < high:
        mid = (low+high)//2 #二分法
        merge_sort(low,mid,list)
        merge_sort(mid+1,high,list)#遞歸調用,
        merg(low,high,mid,list)
    return list
list = [3,1,5,7,8,6,2,0,4,9]
print(merge_sort(0,len(list)-1,list))

 version2 代碼量更少:

def MergeSort(lists):
    if len(lists) <= 1:
        return lists
    num = int(len(lists) / 2)
    left = MergeSort(lists[:num])
    right = MergeSort(lists[num:])
    return Merge(left, right)
def Merge(left, right):
    r, l = 0, 0
    result = []
    while l < len(left) and r < len(right):
        if left[l] < right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += right[r:]
    result += left[l:]
    return result
print(MergeSort(list))

快排,堆排,歸並的總結:

  • 時間復雜度都是O(nlogn)
  • 快排<歸並<堆排(一般情況)
  • 快排的缺點:極端情況效率較低,可到O(n^2),歸並則是需要額外的開銷,堆排則在排序算法中相對較慢

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM