演算法學習(一)——分治以及排序演算法總結
阿新 • • 發佈:2018-12-31
分治策略:
- 分解(Divide):將問題劃分為若干子問題
- 解決(Conquer):遞迴求解子問題
- 合併(combine):子問題組合成原問題
主方法:T(n) = aT(n/b)+f(n) 分解成a個問題,每個子問題降b倍,合併為O(f(n)) 主定理: 比較f(n)和aT(n/b)的階,要求是多項式意義上的比較,即只能差n^x。 1.f(n)的階大,T就是O(f(n)) 2.反之,為O[n^logb(a)] 正則條件:(對常數c<1,有af(n/b)<=cf(n)) 3.最後就是O(f(n)) = O[n^logb(a)],O(T) = O[n^logb(a)*lgn] n^logb(a)就是滿a叉樹中葉子節點的個數,對應遞迴樹上,以上三種情況可以解釋為:1.樹的總代價由根節點決定 2.樹的總代價由葉節點決定 3.樹的總代價均勻分佈在樹的所有層次上 舉個反例,不能使用主定理的情況: T(n) = 2T(n/2)+nlgn f(n)/n^logb(a) = nlgn/n = lgn 它是漸近小於n^c.
排序演算法總結
排序的分類: (一)內部排序和外部排序:內部排序就是在記憶體中排序;外部排序涉及資料量大,記憶體不夠用,需要對外存訪問的排序(多路歸併排序)。 (二)基於比較和不基於比較的排序(基數排序) 還有一個穩定性的問題,若存在次關鍵字(不唯一),排序後位置變化就是不穩定的。 內部排序的分類(下面只標註時間複雜度不是O(n^2)的演算法): 1.交換排序:氣泡排序(穩定),快速排序 (不穩定 O(nlgn) 空間複雜度:O(nlgn~n)) 2.插入排序:直接插入排序(穩定), 折半插入排序(穩定), 希爾排序/縮小增量排序(不穩定) 3.選擇排序:簡單選擇排序(不穩定),堆排序(不穩定 O(nlgn) ),二路歸併排序(穩定 O(nlgn) 空間複雜度:O(n) ) 4.不基於比較的基數排序(穩定 O(d(n+r))r:基數 n:節點數 d:每個節點關鍵字的位數
先介紹三個時間複雜度為O(n^2)的排序演算法
冒泡,選擇排序,插入排序
1.冒泡 def bubbleSort(alist): n = len(alist) for i in range(n-1, 0, -1): for j in range(0, i): if alist[j] > alist[j+1]: alist[j], alist[j+1] = alist[j+1], alist[j] return alist # 改進的氣泡排序 def bubbleSort(alist): n = len(alist) exchange = False for i in range(n-1, 0, -1): for j in range(0, i): if alist[j] > alist[j+1]: alist[j], alist[j+1] = alist[j+1], alist[j] exchange = True # 如果發現整個排序過程中沒有交換,提前結束 if not exchange: break return alist 2. 選擇排序 它與冒泡的不同之處在於,需要一個多出變數儲存遍歷當前序列的最小值下標,每輪遍歷只需要交換一次 def selectionSort(alist): n = len(alist) for i in range(n - 1): # 尋找[i,n]區間裡的最小值 min_index = i for j in range(i+1, n): if alist[j] < alist[min_index]: min_index = j alist[i], alist[min_index] = alist[min_index], alist[i] return alist #這是最簡單的選擇排序,另外還有堆排序 3. 插入排序 它是將新元素插入已排好序的序列中,同樣需要儲存排好序的隊尾元素下標,找到第一個比它小的位置之前插入,關鍵就是找到比currentvalue小的元素的位置,比currentvalue大就後移。它是需要比較和移動的。 移動次數會比選擇排序多,但是它大大減少了比較次數,只要在已經基本有序的序列中找到位置就停止比較和移動了。 def insertionSort(alist): for i in range(1,len(alist)): currentvalue=alist[i] position=i while alist[position-1]>currentvalue and position>0: alist[position]=alist[position-1] position=position-1 alist[position]=currentvalue return alist
希爾排序
#基於插入排序的效能提升:需要待排記錄基本有序和n值較小,可以大大減少移動比較次數。(如果序列已經有序,只需要比較n-1次)
#增量序列函式有很多,沒有最好,它的好壞直接影響時間複雜度。
# 希爾排序
def shellSort(alist):
n = len(alist)
gap = n // 2
#" / "就表示 浮點數除法,返回浮點結果;" // "表示整數除法,代表不大於n/2的整數
while gap > 0:
for i in range(gap): #每輪gap個子列,gap//2產生子列的函式
gapInsetionSort(alist, i, gap) #每個子列分別調直接插入排序
gap = gap // 2
return alist
# # start子數列開始的起始位置, gap表示間隔
def gapInsetionSort(alist,startpos,gap):
#希爾排序的輔助函式
for i in range(startpos+gap,len(alist),gap):
#每輪有gap個子序列,每個子列有len(alist)/gap個元素,
position=i
currentvalue=alist[i]
while position>startpos and alist[position-gap]>currentvalue:
alist[position]=alist[position-gap]
position=position-gap
alist[position]=currentvalue
下面介紹使用分治法的經典演算法,快速排序
快速排序
快排充分體現了分治法的思想,運用遞迴,每次將待排序列一分為二,最終分到不可分。
就是三個迴圈只需要一個變數的額外儲存空間,選取樞軸量,空出一個元素,然後“左右互搏”,左右指標從序列兩端分別遍歷,交換元素。快排需要一個棧空間實現遞迴,深度為lgn~n
改進:
(1)隨機化改進:不是選取第一個值為基準,而是隨機選取。
平衡化改進:取第一個、最後一個和中間點三個值中中間值為基準進行排序。
設定閥值–混合排序:當陣列長度小於某一值時使用其他較快的排序。
(2)可以改進版的冒泡+快速排序,即在左右指標向中間移動時,同時執行氣泡排序,如果碰頭時沒有交換,證明該半邊已經基本有序,那麼就不需要再操作該半邊元素。
(3)每次只對元素較少的半邊進行快排
1.
def quick_sort(nums, start, end):
if start >= end:
return
pivot = nums[start] # 基準值
low = start # 左指標
high = end # 右指標
while low < high:
while low < high and nums[high] >= pivot:
high -= 1
nums[low] = nums[high]
while low < high and nums[low] <= pivot:
low += 1
nums[high] = nums[low]
nums[low] = pivot
quick_sort(nums, start, low - 1)
quick_sort(nums, low + 1, end)
nums = [1,3,54, 26, 93, 44,17, 77, 31, 1,321,44, 55, 20]
quick_sort(nums, 0, len(nums) - 1)
print(nums)
#結果:
[1, 1, 3, 17, 20, 26, 31, 44, 44, 54, 55, 77, 93, 321]
2.廖雪峰版本
from random import Random
def quick_sort(arr):
if len(arr) > 1:
qsort(arr, 0, len(arr) - 1)
def qsort(arr, start, end):
base = arr[start]
pl = start
pr = end
while pl < pr:
while pl < pr and arr[pr] >= base:
pr -= 1
if pl == pr:
break
else:
arr[pl], arr[pr] = arr[pr], arr[pl]
while pl < pr and arr[pl] <= base:
pl += 1
if pl == pr:
break
else:
arr[pl], arr[pr] = arr[pr], arr[pl]
# now pl == pr
if pl - 1 > start:
qsort(arr, start, pl - 1)
if pr + 1 < end:
qsort(arr, pr + 1, end)
r = Random()
a = []
for i in range(20):
a.append(r.randint(0, 100))
print a
quick_sort(a)
print a
#結果:
[40, 57, 29, 21, 98, 37, 16, 77, 63, 86, 63, 12, 23, 68, 38, 66, 64, 13, 63, 36]
[12, 13, 16, 21, 23, 29, 36, 37, 38, 40, 57, 63, 63, 63, 64, 66, 68, 77, 86, 98]
如果設f(n)為陣列長為n時的比較次數,則f(n)=[(f(1)+f(n-1))+(f(2)+f(n-2))+…+(f(n-1)+f(1))]/n.
利用數學知識易知f(n)=(n+1)*[1/2+1/3+…+1/(n+1)]-2n~1.386nlog(n).
歸併排序
歸併也體現分治的思想
詳見這篇部落格
# 歸併排序
def mergesort(seq):
"""歸併排序"""
if len(seq) <= 1:
return seq
mid = len(seq) / 2 # 將列表分成更小的兩個列表
# 分別對左右兩個列表進行處理,分別返回兩個排序好的列表
left = mergesort(seq[:mid])
right = mergesort(seq[mid:])
# 對排序好的兩個列表合併,產生一個新的排序好的列表
return merge(left, right)
def merge(left, right):
"""合併兩個已排序好的列表,產生一個新的已排序好的列表"""
result = [] # 新的已排序好的列表
i = 0 # 下標
j = 0
# 對兩個列表中的元素 兩兩對比。
# 將最小的元素,放到result中,並對當前列表下標加1
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
result += left[i:]
result += right[j:]
return result
自底向上(非遞迴法)方法
# 自底向上的歸併演算法
def mergeBU(alist):
n = len(alist)
#表示歸併的大小
size = 1
while size <= n:
for i in range(0, n-size, size+size):
merge(alist, i, i+size-1, min(i+size+size-1, n-1))
size += size
return alist
# 合併有序數列alist[start....mid] 和 alist[mid+1...end],使之成為有序數列
def merge(alist, start, mid, end):
# 複製一份
blist = alist[start:end+1]
l = start
k = mid + 1
pos = start
while pos <= end:
if (l > mid):
alist[pos] = blist[k-start]
k += 1
elif (k > end):
alist[pos] = blist[l-start]
l += 1
elif blist[l-start] <= blist[k-start]:
alist[pos] = blist[l-start]
l += 1
else:
alist[pos] = blist[k-start]
k += 1
pos += 1