算法的开章,递归是实现其他高级算法如深度优先、分治等的基础;
碰到一个题目就找它的重复性,重复性有最近的重复性(根据重复性怎么构造怎么分解-->分治、回溯等办法,本质就是递归),或者最优重复性(即动态规划)。
本质上就是找它的重复性。
找重复性以及分解问题,最后组合每个子问题的结果。
分治算法(divide and conquer)的核心思想,分而治之 ,即将原问题划 分成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题(庖丁解牛,依次对子问题进行分析),然后再合并其 结果,就得到原问题的解。
分治算法是一种处理问题的思想,递归是一种编程技巧。
分治算法的递归实现中,每一层递归都会涉及这样三个操作:
- 分解:将原问题分解成一系列子问题;
- 解决:递归地求解各个子问题,若子问题足够小,则直接求解;
- 合并:将子问题的结果合并成原问题。
分治算法能解决的问题,一般需要满足下面这几个条件:
- 原问题与分解成的小问题具有相同的模式;
- 原问题分解成的子问题可以独立求解,子问题之间没有相关性,这一点是分治算法跟动态规 划的明显区别,等我们讲到动态规划的时候,会详细对比这两种算法;
- 具有分解终止条件,也就是说,当问题足够小时,可以直接求解;
- 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总 体复杂度的效果了。
例子:给定一个字符串,把它的每个字符都变成大写;
可以写循环,或者递归
如上图做法的好处是可以并行计算,每个子问题是互不相关的,可以在多核CPU中跑。
分治可以解决的问题,它没有所谓的中间结果(重复计算),如果有大量重复计算如递归分治的效率并不高,这时可以有更适合的算法,比如动态规划、 把中间结果先保存下来下次直接使用。
分治的代码模板:分治一般是用递归来处理的
怎么拆分为子问题,怎么merge这些subresult,得到这些子结果怎么合并起来; 中间的这些子结果如何做质量控制和质量保证,下面的人给你个返回结果你咋知道他做的好还是坏。
专注本层,不要下探。
应用:
如何编程求出一组数据的有序对个数或者逆序对个数呢?
最笨的方法是,拿每个数字跟它后面的数字比较,看有几个比它小的。我们把比它小的数字个数 记作 k,通过这样的方式,把每个数字都考察一遍之后,然后对每个数字对应的 k 值求和,最后 得到的总和就是逆序对个数。不过,这样操作的时间复杂度是 O(n^2)。那有没有更加高效的处 理方法呢? 我们用分治算法来试试。我们套用分治的思想来求数组 A 的逆序对个数。我们可以将数组分成 前后两半 A1 和 A2,分别计算 A1 和 A2 的逆序对个数 K1 和 K2,然后再计算 A1 与 A2 之间 的逆序对个数 K3。那数组 A 的逆序对个数就等于 K1+K2+K3。
代码:
private int num = 0; public int count(int[] array, int n) { num = 0; mergeSort(array, 0, n - 1); return num; } private void mergeSort(int[] array, int left, int right) { if (left >= right) return; int mid = (left + right) / 2; mergeSort(array, left, mid); mergeSort(array, mid+1, right); merge(array, left, mid, right); } private void merge(int[] array, int left, int mid, int right) { int i = left, j = mid + 1, k = 0; int[] temp = new int[right - left + 1]; while (i <= mid && j <= right) { if (array[i] <= array[j]) { temp[k++] = array[i++]; } else { num += (mid - i + 1); //计数逆序对的个数 temp[k++] = 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]; } }