|
| 1 | +# MapReduce分治思想 |
| 2 | + |
| 3 | +Google大数据处理的三驾马车: |
| 4 | + |
| 5 | +- MapReduce |
| 6 | +- GFS |
| 7 | +- Bigtable |
| 8 | + |
| 9 | +在倒排索引、PageRank计算、网页分析等搜索引擎相关的技术中都有大量的应用。 |
| 10 | + |
| 11 | +MapReduce本质是分治算法。 |
| 12 | + |
| 13 | +## 1 理解分治算法divide and conquer |
| 14 | + |
| 15 | +分而治之:将原问题划分成n个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,再合并结果,得到原问题解。 |
| 16 | + |
| 17 | +类似递归的定义: |
| 18 | + |
| 19 | +- 分治算法是一种处理问题思想 |
| 20 | +- 递归是一种编程技巧 |
| 21 | + |
| 22 | +分治算法一般都适合用递归实现。分治算法的递归实现中,每层递归都涉及操作: |
| 23 | + |
| 24 | +- 分解:将原问题分解成一系列子问题 |
| 25 | +- 解决:递归求解各子问题,若子问题够小,则直接求解 |
| 26 | +- 合并:将子问题结果合并成原问题 |
| 27 | + |
| 28 | +分治算法能解决的问题,一般需满足: |
| 29 | + |
| 30 | +- 原问题与分解成的小问题具有相同模式 |
| 31 | +- 原问题分解成的子问题可以独立求解,子问题之间没有相关性,这一点是分治算法跟动态规划的明显区别,等我们讲到动态规划的时候,会详细对比这两种算法 |
| 32 | +- 具有分解终止条件,即当问题够小,可直接求解 |
| 33 | +- 可将子问题合并成原问题,而该合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。 |
| 34 | + |
| 35 | +## 2 分治算法案例 |
| 36 | + |
| 37 | +排序算法中: |
| 38 | + |
| 39 | +- 有序度表示一组数据的有序程度 |
| 40 | +- 逆序度表示一组数据的无序程度 |
| 41 | + |
| 42 | +n个数据,期望从小到大排列,则完全有序的数据的有序度即$n(n-1)/2$,逆序度为0。 |
| 43 | +倒序排列的数据的有序度就是0,逆序度是$n(n-1)/2$ |
| 44 | +除了这两种极端,通过计算有序对或者逆序对的个数,来表示数据的有序度或逆序度。 |
| 45 | + |
| 46 | + |
| 47 | + |
| 48 | +> 如何编程求出一组数据的有序对个数或逆序对个数? |
| 49 | +
|
| 50 | +有序对个数和逆序对个数求解方式类似,可只思考逆序对个数的求解方法。 |
| 51 | + |
| 52 | +最笨的: |
| 53 | + |
| 54 | +- 拿每个数字跟其后面数字比较,看有几个比它小的。把比它小的数字个数记作k |
| 55 | +- 把每个数字都考察一遍后,然后对每个数字对应的k值求和 |
| 56 | +- 最后得到的总和就是逆序对个数 |
| 57 | + |
| 58 | +但这样操作时间复杂度$O(n^2)$,还有更高效的处理方法吗? |
| 59 | + |
| 60 | +分治算法: |
| 61 | + |
| 62 | +- 将数组分成前后两半A1和A2 |
| 63 | +- 分别计算A1和A2的逆序对个数K1和K2 |
| 64 | +- 再计算A1与A2之间的逆序对个数K3 |
| 65 | +- 则数组A的逆序对个数=K1+K2+K3 |
| 66 | + |
| 67 | +分治算法其中一个要求是,子问题合并代价不能太大,否则无法降低时间复杂度。那如何快速计算出两个子问题A1与A2之间的逆序对个数? |
| 68 | + |
| 69 | +就要借助归排。 |
| 70 | +归并排序中有一个非常关键的操作,就是将两个有序的小数组,合并成一个有序的数组。实际上,在这个合并的过程中,我们就可以计算这两个小数组的逆序对个数了。每次合并操作,我们都计算逆序对个数,把这些计算出来的逆序对个数求和,就是这个数组的逆序对个数了。 |
| 71 | + |
| 72 | +### 过程代码 |
| 73 | + |
| 74 | +```java |
| 75 | +private int num = 0; // 全局变量或者成员变量 |
| 76 | + |
| 77 | +public int count(int[] a, int n) { |
| 78 | + num = 0; |
| 79 | + mergeSortCounting(a, 0, n-1); |
| 80 | + return num; |
| 81 | +} |
| 82 | + |
| 83 | +private void mergeSortCounting(int[] a, int p, int r) { |
| 84 | + if (p >= r) return; |
| 85 | + int q = (p+r)/2; |
| 86 | + mergeSortCounting(a, p, q); |
| 87 | + mergeSortCounting(a, q+1, r); |
| 88 | + merge(a, p, q, r); |
| 89 | +} |
| 90 | + |
| 91 | +private void merge(int[] a, int p, int q, int r) { |
| 92 | + int i = p, j = q+1, k = 0; |
| 93 | + int[] tmp = new int[r-p+1]; |
| 94 | + while (i<=q && j<=r) { |
| 95 | + if (a[i] <= a[j]) { |
| 96 | + tmp[k++] = a[i++]; |
| 97 | + } else { |
| 98 | + num += (q-i+1); // 统计p-q之间,比a[j]大的元素个数 |
| 99 | + tmp[k++] = a[j++]; |
| 100 | + } |
| 101 | + } |
| 102 | + while (i <= q) { // 处理剩下的 |
| 103 | + tmp[k++] = a[i++]; |
| 104 | + } |
| 105 | + while (j <= r) { // 处理剩下的 |
| 106 | + tmp[k++] = a[j++]; |
| 107 | + } |
| 108 | + for (i = 0; i <= r-p; ++i) { // 从tmp拷贝回a |
| 109 | + a[p+i] = tmp[i]; |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +有些算法确实非常巧妙,并不是每个人短时间都能想到的。比如这个问题,并不是每个人都能想到可以借助归并排序算法来解决,不夸张地说,如果之前没接触过,绝大部分人都想不到。但是,如果我告诉你可以借助归并排序算法来解决,那你就应该要想到如何改造归并排序,来求解这个问题了,只要你能做到这一点,我觉得就很棒了。 |
| 115 | + |
| 116 | +分治算法经典问题: |
| 117 | + |
| 118 | +- 二维平面上有n个点,如何快速计算出两个距离最近的点对? |
| 119 | +- 有两个n*n的矩阵A,B,如何快速求解两个矩阵的乘积C=A*B? |
| 120 | + |
| 121 | +## 3 分治思想在海量数据处理的应用 |
| 122 | + |
| 123 | +数据结构和算法,大部分都是基于内存存储和单机处理。但是,如果要处理的数据量非常大,没法一次性放到内存中,这个时候,这些数据结构和算法就无法工作。 |
| 124 | + |
| 125 | +如给10GB的订单文件按照金额排序这样一个需求,看似简单排序,但数据量大10GB,而机器内存可能只有2、3GB,无法一次性加载到内存,也就无法通过单纯快排、归并等基础算法解决。 |
| 126 | + |
| 127 | +这种数据量大到内存装不下的问题,就可用分治思想。将海量的数据集合根据某种方法,划为几个小数据集合,每个小数据集合单独加载到内存解决,再将小数据集合合并成大数据集。这种分治处理,不仅克服内存限制,还能利用多线程或多机处理,加快处理速度。 |
| 128 | + |
| 129 | +10GB订单排序,先扫描一遍订单,根据订单金额,将10GB文件划为几个金额区间。如订单金额1~100元放到一个小文件,101~200之间放到另一文件。这样每个小文件都可单独加载到内存排序,最后将这些有序小文件合并,就是最终有序的10GB订单数据。 |
| 130 | + |
| 131 | +若订单数据存储在类似GFS分布式系统,当10GB订单被划分成多个小文件,每个文件可并行加载到多台机器上处理,再将结果合并,这样并行处理速度也加快。不过,数据存储与计算所在机器是同一或在网络中靠的很近(如一个局域网内,数据存取速度很快),否则就会因为数据访问速度,导致整个处理过程不但不快,反而可能变慢。 |
| 132 | + |
| 133 | +## 4 为啥MapReduce本质就是分治思想? |
| 134 | + |
| 135 | +若处理1T、10T、100T,一台机器处理效率非常低。而对谷歌搜索引擎,网页爬取、清洗、分析、分词、计算权重、倒排索引等各环节,都面对如此海量的数据(比如网页)。所以,利用集群并行处理大势所趋。 |
| 136 | + |
| 137 | +一台机器过于低效,那就把任务拆分到多台机器处理。若拆分之后小任务之间互不干扰,独立计算,再将结果合并。这不就是分治思想? |
| 138 | + |
| 139 | +MapReduce框架只是一个任务调度器,底层依赖GFS来存储数据,依赖Borg管理机器。它从GFS中拿数据,交给Borg中的机器执行,并且时刻监控机器执行的进度,一旦出现机器宕机、进度卡壳等,就重新从Borg中调度一台机器执行。 |
| 140 | + |
| 141 | +尽管MapReduce的模型非常简单,但是在Google内部应用非常广泛。它除了可以用来处理这种数据与数据之间存在关系的任务,比如MapReduce的经典例子,统计文件中单词出现的频率。除此之外,它还可以用来处理数据与数据之间没有关系的任务,比如对网页分析、分词等,每个网页可以独立的分析、分词,而这两个网页之间并没有关系。网页几十亿、上百亿,如果单机处理,效率低下,我们就可以利用MapReduce提供的高可靠、高性能、高容错的并行计算框架,并行地处理这几十亿、上百亿的网页。 |
| 142 | + |
| 143 | +## 5 总结 |
| 144 | + |
| 145 | +分而治之:将原问题划分成n个规模较小而结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。这个思想非常简单、好理解。 |
| 146 | + |
| 147 | +分治算法的典型的应用场景: |
| 148 | + |
| 149 | +- 指导编码,降低问题求解的时间复杂度 |
| 150 | +- 解决海量数据处理问题。如MapReduce本质就是利用分治思想 |
0 commit comments