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) 由此可以通過父親找到孩子或相反。
知道了樹就可以說說堆了,堆分為大根堆和小根堆,分別的定義為:一棵完全二叉樹,滿足任一節點都比其孩子節點大或者小。
堆排序的過程:
-
- 建立堆
- 得到堆頂元素,為最值
- 去掉堆頂,將最后一個元素放到堆頂,進行再一次堆排序(迭代)
- 第二次的堆頂為第二最值
- 重復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),歸並則是需要額外的開銷,堆排則在排序算法中相對較慢