算法笔记-分支算法

MapReduce 是 Google 大数据处理的三大马车之一,它在倒排索引,PageRank 计算,网页分析等搜索引擎相关技术中都有大量的应用。而 MapReduce 主要的思想就是分治思想。

如何理解分治算法

分治算法的核心思想就是分而治之。将原问题分解成 n 个规模较小,但是结构与原问题相似的子问题,递归解决这些问题,最后合并结果,就完成了问题的解答。

分治算法中用到了递归的方法求解,二者却不等同。分治是一种处理问题的思想,递归是一种编程技巧。

在分治算法中用到递归,都会涉及一下三个操作:
分解:将原问题分解成一系列子问题。
解决:递归求解各个子问题,足够小的子问题直接求解。
合并:将子问题的解合并成原问题的解。

分治算法解决问题一般需要满足以下条件:
1:原问题与分解成的小问题具有相同的模式。
2:原问题分解成的小问题可以独立求解,子问题之间没有相关性。
3:分解具有终止条件,即足够小的问题可以直接求解无需继续分解。
4:子问题可以合并成原问题,合并过程复杂度不能太高,否则起不到减小算法总体复杂度的效果了。

分治算法的应用举例

假设有 n 个数据,如果数据从小到大排列完全有序,那么有序度是 n(n - 1)/2,逆序度为 0 。

那么如何编程求出一组数据的有序度和逆序度呢?

以求逆序度为例,最笨的方法就是双层 for 循环,分别拿每个数字和它后面的数字对比,然后记录比它小的数字的个数 k,最后 k 就是数组的逆序度。这样的算法时间复杂度是 O(n^2)。

利用分治算法求解这个问题。我们将数组分成前后两半 A1 和 A2,求出 A1 的逆序度 K1,求出 A2 的逆序度 K2,最后求出 A1 和 A2 的逆序度,最终数组的逆序度就是 K1 + K2 + K3。

对于 K1,K2 的值在递归的过程中我们可以求出,但是对于 K3 的值如何计算呢?分治算法中,要求子问题可以合并成原问题,那么我们就在合并的过程中求解 K3 的值。在归并排序中,两个有序的数组合并成一个有序数组,在合并的过程中,计算出两个数组之间的逆序度。

图示解析如下:
在这里插入图片描述

详细代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//逆序度数值
private int num = 0;

//计算逆序度
public int count(int[] a, int n) {
num = 0;
mergeSortCounting(a, 0, n - 1);
return num;
}

//分治,将数组拆分成两个更小的数组
private void mergeSortCounting(int[] a, int p, int r) {
//数组下标 p >= r 无法拆分数组
if (p >= r){
return;
}
//当前数组的中间位置
int q = (p + r) / 2;
//取前一半数组
mergeSortCounting(a, p, q);
//取后一半数组
mergeSortCounting(a, q + 1, r);
//合并
merge(a, p, q, r);
}

//合并两个数组
private void merge(int[] a, int p, int q, int r) {
int i = p, j = q + 1, k = 0;
//创建数组存放下标 p 到 r 之间的数据
int[] tmp = new int[r - p + 1];
//循环两个数组(前一半数组,后一半数组)
while (i <= q && j <= r) {
//前半数组中元素更小
if (a[i] <= a[j]) {
tmp[k++] = a[i++];
} else {
//前半元素中数组的值大于后半数组当前元素,说明有逆序数据
num += (q - i + 1); // 统计 p-q 之间,比 a[j] 大的元素个数
tmp[k++] = a[j++];
}
}
//合并前半数组剩下的元素
while (i <= q) {
tmp[k++] = a[i++];
}
//合并后半数组剩下的元素
while (j <= r) {
tmp[k++] = a[j++];
}
//将合并后的数据搬移到数组 a 中
for (i = 0; i <= r - p; ++i) {
a[p + i] = tmp[i];
}
}

上述代码中有详细的注释,足以看懂代码的流程。需要关注的就是当 a[i] > a[j] 的时候,从 j 开始往后的数据都对于当前 i 位置上的数据都是逆序,理解了这个就很容易理解整个代码。

分治思想在海量数据中的应用

分治思想处除了知道我们编写代码以为,还有另一个重要应用,解决海量数据的问题。

假如我们对 10G 订单按照金额排序,但是内存只有 2G,那就无法将所有的数据加载到内存中排序了。利用分治思想,先将 10G 的订单按照金额区间进行分类归纳,使得每个区间的数据量都小于内存空间。然后,针对每一个分区区间的订单数据排序,最后按照区间金额大小逐个取出数据放在更大的集合中,就完成了订单的排序。

在这个过程当中,我们甚至可以引入更多的机器,或者开启更多的线程去实现不同分区订单金额的排序,提高了数据处理的速度。但是如果是引入其他机器排序,要注意最终结果的合并效率。如果处理数据的机器都在同一个网络例如局域网,那么数据传输就会很快。否则会因为数据传输过慢,那就可能导致整个过程变缓。

总结

文章开头提到的 MapReduce,在谷歌搜索引擎中的应用就是分治思想的实际运用。

在谷歌中搜索网页的时候,后台会对上 T 的网页数据进行爬取,清洗,分析,分词,计算权重,倒排索引。在这个过程中,如果是单机处理,根本是不可能完成的任务,只能把任务拆分到多台机器上处理,这恰恰就是任务的分解,分子思想的体现。

利用 MapReduce 提供的高可靠,高性能,高容错的并行计算框架,可以并行的处理几十亿甚至是上百亿的网页。

王争老师的金句:创新并非离我们很远,创新的源泉来自对事物本质的认识。无数优秀架构设计的思想来源都是基础的数据结构和算法,这本身就是算法的魅力所在。

本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。

初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~
与诸君共勉

关注本人公众号,第一时间获取最新文章发布,每日更新一篇技术文章。

在这里插入图片描述

-------------本文结束感谢您的阅读-------------