1. 程式人生 > >分治法及其python實現例子

分治法及其python實現例子

urn 大數組 list nlog 解決 數組 max quic 序列

分治法概念

  1. 將一個復雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題----“分”
  2. 將最後子問題可以簡單的直接求解----“治”
  3. 將所有子問題的解合並起來就是原問題打得解----“合”

分治法特征

  1. 該問題的規模縮小到一定的程度就可以容易地解決
  2. 該問題可以分解為若幹個規模較小的相同問題,即該問題具有最優子結構性質。
  3. 利用該問題分解出的子問題的解可以合並為該問題的解;
  4. 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。

第一條特征是絕大多數問題都可以滿足的,因為問題的計算復雜性一般是隨著問題規模的增加而增加;

第二條特征是應用分治法的前提它也是大多數問題可以滿足的,此特征反映了遞歸思想的應用;、

第三條特征是關鍵,能否利用分治法完全取決於問題是否具有第三條特征,如果具備了第一條和第二條特征,而不具備第三條特征,則可以考慮用貪心法或動態規劃法。

第四條特征涉及到分治法的效率,如果各子問題是不獨立的則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然可用分治法,但一般用動態規劃法較好。

分治法例子:

一、對數組進行快速排序

‘‘‘
時間復雜度O(nlogn)
pivot樞紐,low和high為起點終點 ‘‘‘
#劃分分區(非就地劃分) def partition(nums=list): pivot = nums[0] #挑選樞紐 lo = [x for x in nums[1:] if x < pivot] #所有小於pivot的元素 hi = [x for x in nums[1:] if x >= pivot] #所有大於pivot的元素 return lo,pivot,hi #快速排序 def quick_sort(nums=list): #被分解的Nums小於1則解決了 if len(nums) <= 1: return nums #分解 lo,pivot,hi = partition(nums) # 遞歸(樹),分治,合並 return quick_sort(lo) + [pivot] + quick_sort(hi) lis = [7, 5, 0, 6, 3, 4, 1, 9, 8, 2] print(quick_sort(lis)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

二、對數組進行歸並排序

‘‘‘
名字很多:歸並排序/合並排序/二分排序
時間復雜度 O(logn)
遞歸
兩個步驟:1.拆分 2.合並
‘‘‘
def merge_sort(nums=list):
    #取mid以及左右兩個數組
    mid = len(nums)//2
    left_nums,right_nums = nums[:mid],nums[mid:]

    #遞歸分治
    if len(left_nums) > 1:
        left_nums = merge_sort(left_nums)
    if len(right_nums) > 1:
        right_nums = merge_sort(right_nums)

    #合並
    res = []
    while left_nums and right_nums:  #兩個都不為空的時候
        if left_nums[-1] >= right_nums[-1]:  #尾部較大者
            res.append(left_nums.pop())
        else:
            res.append(right_nums.pop())
    res.reverse() #倒序
    return (left_nums or right_nums) + res #前面加上剩下的非空nums

lis = [7, 5, 0, 6, 3, 4, 1, 9, 8, 2]
print(merge_sort(lis)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

三、給定一個順序表,編寫一個求出其最大值的分治算法

#O(nlogn)
#基本子算法(內置算法) #雖然也可以處理大數組,這裏用於解決分治問題規模小於2時候 def get_max(nums=list): return max(nums) #分治法 def solve(nums): n = len(nums) if n <= 2: #分治問題規模小於2時解決 return get_max(nums) # 分解(子問題規模為 n/2) left_list, right_list = nums[:n//2], nums[n//2:] # 遞歸(樹),分治 left_max, right_max = solve(left_list), solve(right_list) # 合並 return get_max([left_max, right_max]) if __name__ == "__main__": # 測試數據 alist = [12,2,23,45,67,3,2,4,45,63,24,23] # 求最大值 print(solve(alist)) # 67

四、給定一個順序表,判斷某個元素是否在其中

#O(nlogn)
#子問題算法(子問題規模為1)
def is_in_list(nums,key):
    if nums[0] == key:
        print(‘Yes! %d in the nums‘ % key)
    else:
        print(‘Not found‘)
#分治法
def solve(nums,key):
    n = len(nums)
    #N==1時解決問題
    if n == 1:
        return is_in_list(nums,key)
    #分解
    left_list,right_list = nums[:n//2],nums[n//2:]
    #遞歸(樹),分治,合並
    res = solve(left_list,key) or solve(right_list,key)

    return res

if __name__ == ‘__main__‘:
    #測試
    lis = [12,2,23,45,67,3,2,4,45,63,24,23]
    #查找
    print(solve(lis,45)) #YES~
    print(solve(lis,5))  #NOT~

五、找出一組序列中的第 k 小的元素,要求線性時間

‘‘‘
O(nlogn)
用快排的方法,選定pivot然後通過左右兩個分組遞歸得出結果
‘‘‘
# 劃分
def partition(nums=list):
    pi = nums[0]
    lo = [x for x in nums[1:] if x < pi]
    hi = [x for x in nums[1:] if x >= pi]
    return lo,pi,hi

# 查找第 k 小的元素
def solve(nums,key):
    #分解
    lo,pi,hi = partition(nums)

    n = len(lo) 
    #解決
    if n == key:
        return pi
    #遞歸分治
    elif n < key:
        return solve(hi,key-n-1)
    #遞歸分治
    else:
        return solve(lo,key)

if __name__ == ‘__main__‘:
    lis = [3, 4, 1, 6, 3, 7, 9, 13, 93, 0, 100, 1, 2, 2, 3, 3, 2]
    print(solve(lis,3))#2
    print(solve(lis,10))#4

分治法及其python實現例子