分治演算法
分治演算法(Divide And Conquer)是解決規模龐大的問題的很好的思路,它通過降低問題的規模,形成若干個規模更小但形式相同的子問題,進行遞迴求解。在求解過後,將各個子問題的解合併起來,形成原問題的解。
那麼它的大致流程主要分成三步:
- 分解(Divide)將大規模的問題分解成若干個規模更小但形式相同的子問題
- 解決(Conquer)如果當前問題的規模足夠小,並可以直接解決的話,那麼直接解決並返回解。否則,繼續進行分解並遞迴求解分解後的子問題。
- 合併(Merge)將各個子問題合併,最終形成原問題的解。
分治演算法一般來說會採用遞迴法來進行實現,當然利用迭代法(比如for、while)也是可以的。所以,我們往往看到的遞迴演算法從廣義上來說都是分治演算法。無非就是有些遞迴演算法將問題分解了若干個子問題,然而有些遞迴演算法將問題分解成了一個子問題。
應用場景
- 二分查詢
- 合併排序
- 快速排序
- 大整數乘法
- Strassen矩陣乘法
- 棋盤覆蓋
- 線性時間選擇
- 最接近點對問題
- 迴圈賽日程表
- 漢諾塔
例子:數列的最大子序列和
給定一個整數陣列,找出總和最大的連續數列,並返回總和。
輸入: [-2,1,-3,4,-1,2,1,-5,4]
輸出: 6
解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。
本題比較優的解法是動態規劃,我們嘗試用分治演算法進行解決。
我們把陣列分割成兩邊,那麼結果出現的區域,完全在左邊、完全在右邊、包括中間兩個節點的左右兩部分
public class Test1617 {
public static void main(String[] args) {
Test1617 test = new Test1617();
int[] nums = {-2,1,-3,4,-1,2,1,-5,4};
System.out.println(test.maxSubArray(nums));
}
public int maxSubArray(int[] nums) {
if (nums.length == 0) {
return 0;
}
return divide(nums, 0,nums.length-1);
}
private int divide(int[] nums, int left, int right) {
if (left == right) {
return nums[left];
}
int mid = (left + right) >> 1;
// 1.左邊最大的子序列
int leftMaxSum = divide(nums, left, mid);
// 2.右邊最大的子序列
int rightMaxSum = divide(nums, mid+1, right);
// 3.最大數列和在中間
// 包括中間的,左邊部分最大
int sum = nums[mid];
int leftMidSum = sum;
for (int i=mid-1; i>=left; i--) {
sum += nums[i];
leftMidSum = Math.max(leftMidSum, sum);
}
// 包括中間的,右邊部分最大
sum = nums[mid+1];
int midRightSum = sum;
for (int i=mid+2; i<=right; i++) {
sum += nums[i];
midRightSum = Math.max(midRightSum, sum);
}
return Math.max(Math.max(leftMaxSum, rightMaxSum), leftMidSum+midRightSum);
}
}