1、什么是分治算法
分治算法(divide and conquer)的核心思想其实就是四个字,分而治之,也就是将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,然后合并其结果,就得到原问题的解。
实际上,分治算法一般都比较适合用递归来实现,分治算法是一种处理问题的思想,递归是一种编程技巧。分治算法递归实现中,每一层递归都会涉及这样三个操作:
- 分解原问题为若干子问题,这些子问题是原问题的规模较小的实例
- 解决这些子问题,递归地求解各个子问题,若子问题足够小,则直接求解
- 合并这些子问题的解,成原问题的解
2、模板
2.1、Java 模板
private static int divide_conquer(Problem problem, ) {
if (problem == NULL) {
int res = process_last_result();
return res;
}
subProblems = split_problem(problem)
res0 = divide_conquer(subProblems[0])
res1 = divide_conquer(subProblems[1])
result = process_result(res0, res1);
return result;
}
2.2、Python 模板
def divide_conquer(problem, param1, param2, ...):
# recursion terminator
if problem is None:
print_result
return
# prepare data
data = prepare_data(problem)
subproblems = split_problem(problem, data)
# conquer subproblems
subresult1 = self.divide_conquer(subproblems[0], p1, ...)
subresult2 = self.divide_conquer(subproblems[1], p1, ...)
subresult3 = self.divide_conquer(subproblems[2], p1, ...)
…
# process and generate the final result
result = process_result(subresult1, subresult2, subresult3, …)
# revert the current level states
3、实战
3.1、括号生成
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n < 1) {
return res;
}
generate(0, 0, "", n);
return res;
}
public void generate(int left, int right, String target, int n) {
if (target.length() == 2 * n) {
res.add(target);
return;
}
if (left < n) {
generate(left + 1, right, target + "(", n);
}
if (right < left) {
generate(left + 1, right, target + ")", n);
}
}
}
3.2、归并排序(Merge Sort)
3.2.1、归并排序算法完全遵循分治模式,直观操作如下:
- 分解:把长度为 n 的输入序列,分为两个长度为 n/2 的子序列
- 解决:对这两个子序列分别采用归并排序
- 合并:将两个排序好的子序列合并成一个最终的排序序列
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
3.2.2、实现步骤图解(图片来源菜鸟教程)
3.2.3、伪代码
# A[] 下标从 1 开始
MERGE(A, p, q, r)
n1 = q - p + 1
n2 = r - q
let L[1..n1 + 1] and R[1..n2 + 1] be new arrays
# [0, q]
for i = 1 to n1
L[i] = A[p + i - 1]
# [q + 1, r]
for i = 1 to n2
R[i] = A[q + i]
L[n1 + 1] = ∞
R[n2 + 1] = ∞
i = 1
j = 1
# [p, r]
for k = p to r
if L[i] <= R[j]
A[k] = L[i]
i++
else
A[k] = R[j]
j++
MERGE-SORT(A, p, r)
if p < r
q = (p + r) / 2
MERGE-SORT(A, p, q)
MERGE-SORT(A, q + 1, r)
MERGE(A, p, q, r)
3.2.4、Java 实现
class Solution {
public static void main(String[] args) {
int[] array = {1, 24, 5, 56, 9, 23, 5, 16, 7};
Solution.mergeSort(array, 0, array.length - 1);
System.out.println(array);
}
/**
* 参数入口 mergeSort(array, 0, array.length - 1)
*
* @param array 待排序数组
* @param left 左边界
* @param right 右边界
*/
public static void mergeSort(int[] array, int left, int right) {
if (right <= left) {
return;
}
// (left + right) / 2
int mid = left + ((right - left) >> 1);
mergeSort(array, left, mid);
mergeSort(array, mid + 1, right);
merge(array, left, mid, right);
}
public static void merge(int[] array, int left, int mid, int right) {
// 中间数组
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
temp[k++] = array[i] <= array[j] ? array[i++] : array[j++];
}
while (i <= mid) {
temp[k++] = array[i++];
}
while (j <= right) {
temp[k++] = array[j++];
}
for (int p = 0; p < temp.length; p++) {
array[left + p] = temp[p];
}
}
}
4、总结
分治算法能解决的问题,一般需要满足下面这几个条件:
- 原问题与分解成的小问题具有相同的模式
- 原问题分解成的子问题可以独立求解,子问题之间没有相关性;这一点是分治算法跟动态规划的明显区别
- 具有分解终止条件,也就是说,当问题足够小时,可以直接求解
- 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了