算法笔记-分治算法

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 的值。在归并排序中,两个有序的数组合并成一个有序数组,在合并的过程中,计算出两个数组之间的逆序度。

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

详细代码实现:

	//逆序度数值
	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 提供的高可靠,高性能,高容错的并行计算框架,可以并行的处理几十亿甚至是上百亿的网页。

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

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

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

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

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值