標籤:merge 第一個 快速 特殊 通過 result 子節點 bin 估算
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 = 7def 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 Nonei = 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)-1): min_loc = i for j in range(i+1,len(list)-1): if list[min_loc]>list[j]: #最小值遍曆比較 min_loc = j list[i],list[min_loc] = list[min_loc],list[i] return listprint(choice(list))
插入排序:將列表分為有序區和無序區,最開始的有序區只有一個元素,每次從無序區選擇一個元素按大小插到有序區中
list = [3,1,5,7,8,6,2,0,4,9]def cut(list): for i in range(1,len(list)-1): 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 listprint(cut(list))
這三種排序方式時間複雜度都是O(n^2),不太高效,所以下面介紹幾種更高效的排序方式
1 快速排序:好寫的排序裡最快的,快的排序裡最好寫的。步驟為1 提取 2 左右分開 3 遞迴調用
list = [3,1,5,7,8,6,2,0,4,9]left = 0right = len(list) - 1def partition(left,right,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): if 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 listprint(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 listlist = [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 resultprint(MergeSort(list))
快排,堆排,歸併的總結:
- 時間複雜度都是O(nlogn)
- 快排<歸併<堆排(一般情況)
- 快排的缺點:極端情況效率較低,可到O(n^2),歸併則是需要額外的開銷,堆排則在排序演算法中相對較慢
python 常見演算法