【核心演算法9】分治演算法
阿新 • • 發佈:2020-07-03
分治演算法的核心思想是將一個規模很大的問題化簡為n個規模較小的問題,這些子問題雖然獨立而不同,但是問題的本質是一致的,從而達到分而治之的目的
- 歸併排序
- 連續子列表的最大和
- 幾何問題-凸包
- 數學問題-多項式乘法
歸併排序
歸併排序是一種有效的排序演算法。其他常見的八種排序演算法包括氣泡排序,插入排序,二叉樹排序,快速排序,堆排序,希爾排序等
問題描述
將亂序的數列用歸併演算法將其排序輸出
思路描述
歸併演算法有兩種型別:遞迴法(Top-down) 和迭代法(Bottom-up)
遞迴法描述
遞迴法的核心思想是
-
把列表分為兩個子列表,單獨排序子列表再合併子列表。
-
如圖,遞迴法可以利用二叉樹理解:列表為根節點,子列表為子節點。
-
首先排序子列表,再一步步合併子列表
這個演算法之所以稱為遞迴法是因為它利用遞迴的思想,把根節點的問題遞給子節點,一環套一環解決問題
迭代法描述
迭代法的核心思想是
-
利用for迴圈將列表的子列表成對排序合併,最後組成一個整體
-
如圖,迭代法可以利用倒二叉樹:對獨立子列表(子節點) 進行多次迭代,直到完整列表(根節點)
-
首先把列表看成n個長度為1的子列表,利用迴圈把相鄰的兩個子列表合併
-
得到 n/2 個長度為2的子列表
-
依次類推,最後得到長度為n的完整列表
程式碼實現
遞迴法
def merge_sort1(arr): """ 遞迴法 歸併排序 :param arr: 亂序陣列 :return: """ if len(arr) < 2: return # 找到列表中點 cut = len(arr) // 2 # 左子列表 arr1 = arr[: cut] # 右子列表 arr2 = arr[cut: ] # 左子列表歸併排序 merge_sort1(arr1) # 右子列表歸併排序 merge_sort1(arr2) pointer1 = 0 pointer2 = 0 counter = 0 while pointer1 < len(arr1) and pointer2 < len(arr2): if arr1[pointer1] < arr2[pointer2]: arr[counter] = arr1[pointer1] pointer1 += 1 else: arr[counter] = arr2[pointer2] pointer2 += 1 counter += 1 while pointer1 < len(arr1): arr[counter] = arr1[pointer1] pointer1 += 1 counter += 1 while pointer2 < len(arr2): arr[counter] = arr2[pointer2] pointer2 += 1 counter += 1 array = [2, 3, 4, 1, -2, 0, 5, 1] merge_sort1(array) print(array) # >>>
迭代法
def merge_sort2(arr): length = len(arr) n = 1 while n < length: for i in range(0, length, n*2): arr1 = arr[i: i + n] arr2 = arr[i + n: i + n*2] pointer1 = 0 pointer2 = 0 counter = i while pointer1 < len(arr1) and pointer2 < len(arr2): if arr1[pointer1] < arr2[pointer2]: arr[counter] = arr1[pointer1] pointer1 += 1 else: arr[counter] = arr2[pointer2] pointer2 += 1 counter += 1 while pointer1 < len(arr1): arr[counter] = arr1[pointer1] pointer1 += 1 counter += 1 while pointer2 < len(arr2): arr[counter] = arr2[pointer2] pointer2 += 1 counter += 1 n = n*2 array = [2, 3, 4, 1, -2, 0, 5, 1] merge_sort2(array) print(array) # >>>
連續子列表的最大和
問題描述
在一個列表中找出連續子列表的最大和,列表中的數字可負可正,並且子列表不能為空
思路解析
- 列表的最大子列表的和,有三種可能:左子列表,右子列表或左子列表與右子列表之間
- 左子列表和右子列表答案是知道的,找到左右子列表之間的子列表最大和就可以了
- 設一箇中點,遍歷中點左邊的值,跟蹤記錄已遍歷過的值的總和,取這些總和的最大值
- 遍歷中點右邊的值,同樣跟蹤記錄已遍歷過的值的總和,取這些總和的最大值
- 最後,左面的最大值+右面的最大值+中點值就是第三種可能的答案了
程式碼實現
def max_sub_array(arr):
if arr == []:
return
if len(arr) == 1:
return arr[0]
# 設中點
cut = len(arr) // 2
# 分治:找到左子列表最大和
left_sum = max_sub_array(arr[: cut])
# 分治:找到右子列表最大和
right_sum = max_sub_array(arr[cut: ])
left_middle_sum = 0
max_l = 0
right_middle_sum = 0
max_r = 0
# 找到左子列表與右子列表之間子列表的最大和
for i in range(cut - 1, -1, -1):
left_middle_sum += arr[i]
max_l = max(left_middle_sum, max_l)
for i in range(cut + 1, len(arr), 1):
right_middle_sum += arr[i]
max_r = max(right_middle_sum, max_r)
# 返回三種可能的最大值
return (max(left_sum, max_l + arr[cut] + max_r, right_sum))
arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
max_sum = max_sub_array(arr)
print(max_sum)
#>>>