数据结构与算法笔记:基础篇 - 分治算法:谈一谈大规模计算框架MapReduce中的分治思想

概述

MapReduce 是 Google 大数据处理的三姐马车之一,另外两个事 GFS 和 Bigtable。它在倒排索引、PageRank 计算、网页分析等搜索引擎相关的技术中都有大量的应用。

尽管开发一个 MapReduce 看起来很高深。实际上,万变不离其宗,它的本质就是本章要学的这种算法思想,分支算法。


如何理解分支算法?

为什么说 MapReduce 的本质就是分治算法呢?先来看看什么事分治算法?

分治算法(divide and conquer)的核心思想其实就是四个字,分而治之,也就是将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

这个定义看起来有点类似递归地定义。关于分治和递归,我们在 排序(下) 的时候讲过,分治算法是一种处理问题的思想,递归是一种编程技巧。实际上,分治算法一般都比较适合用递归来实现。分治算法的递归实现中,每一次递归都会涉及三个操作:

  • 分解:将原问题分解为一系列子问题;
  • 解决:递归地求解各个子问题,若子问题足够小,则直接求解;
  • 合并:将子问题的结果合并成原问题。

分治算法能解决的问题,一般要满足以下几个条件:

  • 原问题与分解成的小问题具有相同的模式;
  • 原问题分解的子问题可以独立解决,子问题之间没有相关性,这一点是分治算法跟动态规划的明显区别,等讲到动态规划,会详细对比这两种算法;
  • 具有分解终止条件,也就是说,当问题足够小时,可以直接求解;
  • 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减少算法总体复杂度的效果了。

分治算法应用举例分析

理解分治算法的原理并不难,但是要想灵活应用并不容易。所以,接下来,我会带你用分治算法解决我们在讲排序的时候设计的一个问题,加深你对分治算法的理解。

还记得我们在排序算法里讲到的数据的有序度、逆序度的概念吗?我当时讲到,我们用有序度表示一组数据的有序程度,用逆序度表示一组数据的无序程度。

假设我们有 n 哥数据,我们期望数据从小到大排列,那完全有序的数据的有序度就是 n(n-1)/2,逆序度等于 0;相反,倒序排列的数据的有序度就是 0,逆序度就是 n(n-1)/2。除了这两种极端情况外,我们通过计算有序对或逆序对的个数,来表述数据的有序度或逆序度。

在这里插入图片描述

现在的问题是,如何编码求出一组数据的有序对个数或者逆序对个数呢? 因为有序对个数和逆序对个数的求解方式是类似的,所以你可以之思考逆序对个数的求解方法。

最笨的方法是,拿每个数组跟它后面的数字比较,看看有几个比它小。我们把比它小的数字的个数记作 k,通过这样的方式,把每个数字都考察一遍之后,然后对每个数字对应的 k 值求和,最后得到的总和就是逆序对个数。不过,这样擦做的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。有没有更加高效的处理方法呢?

我们用分治法来试试。我们套用分治法的思想来求数组 A 的逆序对个数。我们可以将数组分成前后两半 A1 和 A2,分别计算 A1 和 A2 之间的逆序对个数 K1 和 K2,然后再计算 A1 和 A2 之间逆序对个数 K3。那数组 A 的逆序对个数就等于 K1+K2+K3。

我们前面讲过,使用分治算法其中一个要求是,子问题合并的代价不能太大,否则就起不到了降低时间复杂度的效果了。那回到这个问题,如何快速计算出两个子问题 A1 与 A2 之间的逆序对个数呢?

这里就要借助归并排序算法了。你可以先试着想想,如何借助归并排序算法来解决呢?

归并排序中有一个非常关键的操作,就是将两个有序的小数组,合并成一个有序的数组。实际上,在这个合并的过程中,我们就可以计算这连个小数组的逆序对个数了。每次合并操作,我们都计算逆序对个数,把这些计算出来的逆序对个数求和,就是这个数组的逆序对个数了。

在这里插入图片描述

尽管画了张图来解释,但个人觉得,对于工程师来说,看代码更好理解一些,所以我们把这个过程翻译成了代码,你可以结合着图和文字描述一起看下。

    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) {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;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[i++];}}while (i <= q) { // 处理剩下的tmp[k++] = a[i++];}while (j <= r) { // 处理剩下的tmp[k++] = a[j++];}for (i = 0; i <= r-p; i++) { // 从tmp拷贝回aa[p+i] = tmp[i];}}

很多同学经常会说,某某算法思想如此巧妙,我是怎么也想不到的。实际上,确实是的。有些算法确实非常巧妙,并不是每个人短时间都能想到的。比如这个问题,并不是每个人都能想到可以借助归并排序算法来解决。不夸张地说,如果之前没接触过,觉得部分人都想不到。但是,如果我告诉你可以借助归并排序算法来解决,那你就应该想到如何改造归并排序,来求解这个问题了,只要你能做到这一点,我觉得就很棒了。

关于分治算法,还有两道比较经典的问题:

  • 二维平面上有 n 个点,如何快速计算出两个距离最近的点对?
  • 有两个 nn 的矩阵 A,B,如何快速求解两个矩阵的成绩 C=AB?

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

分治算法思想的应用是非常广发的,并不仅限于指导编程和算法设计。它还经常用在海量数据处理的场景中。我们前面讲的数据结构和算法,大部分都是基于内存存储和单机处理。但是,如果要处理的数据量非常大,没法一次性放到内存中,这个时候,这些数据结构和算法就无法工作了。

比如,给 10GB 订单文件按照金额排序这样一个需求,看似是一个很简单的排序问题,但是因为数据量大,有 10GB,而我们机器的内存可能只有 2、3GB 这样子,无法一次性加载到内存,也就无法通过单纯地使用快排、归并排序等基础算法来解决了。

要解决这种数据量大到内存装不下的问题,我们就可以利用分治的思想。我们可以将海量的数据集合根据某种方法,划分为几个小的数据集合,每个小的数据集合单独加载到内存来解决,然后再将小数据集合合并成大数据集合。实际上,利用这种分治的处理思路,不仅仅能克服内存的限制,还能利用多线程或者多机处理,加快处理的速度。

比如刚刚的例子,给 10GB 的订单排序,我们可以先扫描一遍订单,根据订单的金额,将 10GB 的文件划分为几个金额区间。比如订单金额 1 到 100 元的放到一个小文件,101 到 200 的放到另一个文件,以此类推。这样每个小文件都可以单独加载到内存排序,最后将这些有序的小文件合并,就是最终有序的 10GB 订单数据了。

如果订单存储在类似 GFS 这样的分布式系统上,当 10GB 订单被划分成多个小文件的时候,每个文件可以并行加载到多态机器上处理,最后再将结果合并在一起,这样并行处理的速度也加快了很多。不过,这里有一点要注意,就是数据的存储与计算所在的机器是同一个或在网络中靠的很近(比如一个局域网内,数据存取速度很快),否则就会因为数据访问的速度,导致整个处理过程不但不会变快,反而有可能变慢。

你可能还有印象,这个就是我们在讲线性排序的时候举的例子。实际上,在前面已经学习的课程中,还讲了很多利用分治算法来解决的问题。

谈一谈大规模计算框架MapReduce中的分治思想

刚刚举的订单的例子,数据有 10GB 大小,可能给你的感受还不强烈。那如果我们要处理的数据时 1T、10T、100T 这样的数据,那一台机器处理的效率肯定是非常低的。而对于谷歌搜索引擎来说,网页爬取、清洗、分析、分词、计算权重、倒排索引等等各个环节中,都会面对如此海量的数据(比如网页)。所以利用集群并行处理显然是大势所趋。

一台机器过于低效,那我们就把人去拆分到多态机器上来处理。如果拆分之后的小人物之间互不干扰,独立计算,最后再将结果合并。这不就是分治思想吗?

实际上,MapReduce 框架只是一个任务调度器,底层依赖 GFS 来存储数据,依赖 Borg 管理机器。它从 GFS 中拿数据,交给 Brog 中的机器执行,并且时刻监控机器执行的进度,一旦机器出现宕机、进度卡壳等,就重新从 Brog 中调度一台机器执行。

尽管 MapReduce 的模型非常简单,但是在 Google 内部应用非常广泛。它除了可以用来处理这种数据与数据之间存在关系的任务,比如 MapReduce 的经典例子,统计文件中单词出现的频率。此外,它还可以用来处理数据与数据之间没有关系的任务,比如对网页分析、分词等,每个网页可独立的分析、分词,而这两个网页之间没有关系。网页几十亿、上百亿,如果单机处理,效率地下,我们就可以利用 MapReduce 提供可靠、高性能、高容错的并行计算框架,并行地处理这几十亿、上百亿的网页。

小结

本章讲了一种应用非常广泛的算法思想,分治算法。

分治算法用四个字概况就是 “分而治之”,将原问题划分成几个规模较小而结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。这个思想非常简单、好理解。

本章讲解了两种分治算法的经典的应用场景,一个是用来指导编码,降低问题求解的时间复杂度,另一个是解决海量数据处理问题。比如 MapReduce 本质上就是利用了分治思想。

我们也时常感叹 Google 的创新能力如此之强,总是在引领技术的发展。实际上,创新并非离我们很远,创新的源泉来自对事物本质的认识。无数优秀架构设计的思想来源都是基础的数据结构和算法,这本身就是算法的一个魅力所在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/29945.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重磅!首个跨平台的通用Linux端间互联组件Klink在openKylin开源

随着智能终端设备的普及&#xff0c;多个智能终端设备之间的互联互通应用场景日益丰富&#xff0c;多设备互联互通应用场景需要开发者单独实现通讯协议。因此&#xff0c;为解决跨平台互联互通问题&#xff0c;由openKylin社区理事单位麒麟软件旗下星光麒麟团队成立的Connectiv…

<provider>标签的android:authorities属性,和androidx.appcompat.androidx-startup库有什么关系?

<provider> 标签的 android:authorities 属性在 Android 中是用于唯一标识一个 ContentProvider 的。这与库或特定的组件&#xff08;如 androidx.appcompat&#xff09;本身并没有直接的关系&#xff0c;除非该库或组件内部定义了一个 ContentProvider 并指定了它的 aut…

2024下《网络工程师》50个高频考点汇总,背就有效!

宝子们&#xff01;上半年软考已经结束一段时间了&#xff0c;准备考下半年软考中级-网络工程师的小伙伴们可以开始准备了&#xff0c;这里给大家整理了50个高频考点&#xff0c;涵盖全书90%以上重点&#xff0c;先把这个存下&#xff01;再慢慢看书&#xff0c;边看书边背这个…

数据治理创新路:建设数据集市,强化数据报送一致性新实践

随着信息化和数字化的飞速发展&#xff0c;数据已经成为企业运营和决策的核心要素。然而&#xff0c;数据治理的复杂性和多样性给企业带来了不小的挑战。为了更好地应对这些挑战&#xff0c;许多企业开始探索数据治理的创新路径&#xff0c;其中建设数据集市和强化数据报送一致…

各类存储器类型(RAM、ROM、FLASH、DRAM、SRAM)

1 计算机存储类型构成 在计算机中&#xff0c;各类存储器构成了计算机能高速高效运转程序的基石。 计算机的存储体系中&#xff0c;从速度慢到速度快对应着容量大到小&#xff0c;也就是说&#xff0c;速度越快容量越小&#xff1b;容量越大的&#xff0c;速度越慢。两者互相…

C# OpenCvSharp 矩阵计算-compare、repeat、PSNR、cross、dot

🌟详细解释OpenCV函数🌟 在这篇文章中,我将详细解释并给出每个函数的示例,包括运算过程和运算结果。💻✨ 🔍函数:compare 定义: void compare(InputArray src1, InputArray src2, OutputArray dst, int cmpop); 参数: src1:输入图像或矩阵 src2:输入图像或矩阵…

mtk android开机及重启速度优化

zygote&#xff1a;预加载类和资源。加载过多会影响开机时间。 查看 /sdcard/mtklog/bootprof 文件。减少预加载的类和资源&#xff0c;确保没有冗余的资源图片。开机动画进程&#xff1a;bootanimation 或 mtkbootanimation&#xff0c;走 android 流程比 movie 流程占用内存资…

java 判断字符串是否以两个中文字符开头

在Java中&#xff0c;可以使用正则表达式来判断一个字符串是否以两个中文字符开头。 以下是一个简单的Java代码示例&#xff1a; public class Main {public static void main(String[] args) {String testString "中文字符开头的文本";boolean isChineseCharacte…

JavaScript算法题

排序算法 1.冒泡排序 从开始位置两两比较&#xff0c;持续n轮 // 冒泡排序let bubbleSort (arr) > {const len arr.lengthfor (let i 0; i < len - 1; i) {// 外层循环控制轮数for (let j 0; j < len - i - 1; j) {// 内层循环控制比较次数if (arr[j] > arr[…

Unity3D 打造3D路径编辑系统详解

在Unity3D中打造3D路径编辑系统&#xff0c;对于游戏开发者来说是一项重要且实用的任务。无论是实现敌人的AI巡逻、角色的自定义移动路线&#xff0c;还是物体的动态轨迹&#xff0c;一个强大的路径编辑系统都能大大提高开发效率。本文将详细介绍如何使用Unity3D打造一个3D路径…

echarts 折线图 实现某两个点之间不要连线

通过插入null或NaN的数据点来实现"断开"的效果 const data [[a, 1], [b, 2], [c, 3], [d, 4], [e, 5]] data.splice(2, 0, NaN) option {xAxis: {type: "category",data: [a, b, c, d, e]},yAxis: {},series: [{data,type: "line"}] }

大语言模型架构---Transformer 模型

文章目录 输入编码多头自注意力机制前馈网络层编码器解码器当前主流的大语言模型都基于 Transformer 模型进行设计的。Transformer 是由多层的多头自注意力(Multi-head Self-attention)模块堆叠而成的神经网络模型。原始的 Transformer 模型由编码器和解码器两个部分构成,而…

多目标跟踪中检测器和跟踪器如何协同工作的

多目标跟踪中检测器和跟踪器如何协同工作的 flyfish 主要是两者 接口间的交互 假设 原始图像尺寸&#xff1a;1920&#xff08;宽&#xff09;x 1080&#xff08;高&#xff09; 模型输入尺寸&#xff1a;640&#xff08;宽&#xff09;x 640&#xff08;高&#xff09; 检…

中国各区域人口密度可视化图

原文链接https://mp.weixin.qq.com/s?__bizMzUyNzczMTI4Mg&mid2247674303&idx1&sn830304f80a0429406c4a5e38dc7750ec&chksmfa777682cd00ff9434e4660bb52ab2bf19913b6732083de061664401a9ac0fa46581cd9e5e86&token1445576002&langzh_CN&scene21#we…

如何在Python中使用Pip换源

在Python开发中&#xff0c;我们经常需要使用pip来安装各种第三方库。然而&#xff0c;默认的pip源&#xff08;Python Package Index&#xff0c;简称PyPI&#xff09;位于国外&#xff0c;有时会遇到网络不稳定或速度较慢的问题。为了解决这些问题&#xff0c;我们可以使用国…

为什么网络安全缺口很大,而招聘却很少?

2020年我国网络空间安全人才数量缺口超过了140万&#xff0c;就业人数却只有10多万&#xff0c;缺口高达了93%。这里就有人会问了&#xff1a; 1、网络安全行业为什么这么缺人&#xff1f; 2、明明人才那么稀缺&#xff0c;为什么招聘时招安全的人员却没有那么多呢&#xff1…

PHP 文件上传:全面指南与最佳实践

PHP 文件上传是指使用 PHP 脚本处理用户通过 HTML 表单上传到服务器的文件。这个过程涉及前端 HTML 表单的设计、后端 PHP 脚本的编写以及服务器端的配置和安全措施。文件上传功能广泛应用于各种网站和应用程序&#xff0c;如图片上传、文档共享、媒体管理等。实现这一功能不仅…

软件测试-unittest框架

unittest框架 文章大致内容 unittest框架的组成断言跳过&#xff08;某些用列由于某些原因不想执行&#xff09;参数化测试报告 UnitTest框架 什么是Unittest框架&#xff1f; 概念&#xff1a;unittest是python自带的一个单元测试框架&#xff0c;用他来做单元测试 unitte…

嵌入式学习记录6.17(qss练习)

一思维导图 二.练习 widget.h #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowFlag(Qt::FramelessWindowHint);this->setAttribute(Qt:…

五种HTTP数据传输方式

在前端开发过程中,后端主要提供 http 接口来传输数据,而这种数据传输方式主要有五种: url paramqueryform-urlencodedform-datajson 下面就让我们一起来了解一下在Nest.js中如何使用这五种HTTP数据传输方式: 一,创建项目 使用nest new 创建一个nest的项目 nest new 项目名称 …