本文主要展示解决海量数据问题的时候使用的技术,注意这是从技术角度进行分析,只是一种思想并不代表业界的技术策略。

常用到的算法策略.

  1. 分治:多层划分、MapReduce
  2. 排序:快速排序、桶排序、堆排序
  3. 数据结构:堆、位图、布隆过滤器、倒排索引、二叉树、Trie树、B树,红黑树
  4. Hash映射:hashMap、simhash、局部敏感哈希

排序

排序:
  将一组无序的集合,根据某个给定的条件,将其变成有序的方法就是排序。从这个我给出的不严谨的定义中排序是方法,目的是让原来无序的集合满足条件有序。
  这里我们基于海量数据的考虑重新思考排序,不会详述每一种排序方法的原理,主要面向的是如何在海量数据情况下使用排序方法。

常用的排序方法:
  插入排序,选择排序,冒泡排序,希尔排序,快速排序,归并排序,堆排序,桶排序,计数排序,基数排序。
下面给出几种排序算法的简单介绍图。
     

既然有这么多的排序方法,我们可以直接读取数据到内存中直接调用语言中封装好的排序方法即可。但是数据量很大,不能将数据同时读入内存。
这就出现了所有的外排序,我们可以用归并排序的思想来解决这个问题,也可以基于数据范围用"计数排序"的思想来解决。
排序真的很重要吗?我一直相信一句话:没有排序解决不了的问题。这里给出几个需求,例如:

  • 取最大的k个数,直接降序排序取前k个即可;
  • 推荐、搜索业务,我们也可以直接排序(精度不高)
  • 二分查找之前也要求数据有序

堆排序

在top k中我们用到了一个数据结构堆(有最大堆和最小堆),这里就先介绍一下这个数据结构的性质,基于最大
堆进行介绍。堆是一个完全二叉树,对于任意的节点,我们可以使用数据来表示最大堆,设置下标从0开始, 满足以下性质:

  • root > left && root > right. (左右节点存在)
  • 根节点:root_index; 左孩子节点:left_index; 右孩子节点:right_index
  • left_index = root_index * 2 + 1
  • right_index = root_index * 2 + 2
  • root_index = (*_index - 1) / 2

在堆的数据结构进行增删改查的过程中,我们始终维护堆的数据结构,定义MaxheapFy(int *A, int i)表示维护第i个节点满足最大堆的性质,注意这里没有考虑到泛型编程,正常应该提供一个比较方法的函数,让使用者自己设置比较方式。

从下面的伪代码中,我们可以知道对于一个大小为n的堆,维护一次堆的性质,最坏时间为O(logn),但是必须保证在改变之前,他是满足堆的性质的。

void MaxheapFy(int *A,int i) {// i 要在A的范围之内,assert(i >= 0);assert(i < n) // 堆的大小l = LEFT(i), r = RIGHT(i); // 得到左右子节点,如果存在now = i;// 找到左右孩子的最大值if(l<=heapsize&&A[l]>A[now]){now=l;//交换A[l]和A[i],并递归维护下一个当前结点now}if(r<=heapsize&&A[r]>A[now]){now=r;//交换A[l]和A[i],并递归维护下一个当前结点now}if(now != i) { // 交换,递归维护swap(A[i], A[now]);MaxheapFy(A, now);}
}

基于上面的这个维护的性质,我们可以直接对于长度为n的数组建立最大堆,我们知道当只有一个元素的时候,一定满足最大堆的性质,基于这个性质,我们对于长度为n的数组A,从 n / 2向前维护每一个节点的性质,就可以得到最大堆.从下面给出的最大堆的构建代码,我们可以分析建堆的时间复杂度是O(nlogn).因为每次维护是O(logn),维护n次,(这里计算时间复杂度的时候,忽略常数系数)。

void BuildMaxHeap(int *A,int n){//A[1..n]heapsize=n;//全局变量,表示最大堆的大小for(int i=n/2;i>=1;i--){//从n/2..1维护堆中每个节点的最大堆性质:结点的值大于起孩子的值MaxheapFY(A,i);}
}

建成最大堆之后,从最大堆的性质我们知道,A[0]一定是最大值,如果要堆A升序排序,就可以swap(A[0], A[n-1]);继续维护A[0],直到堆中只是一个元素,这就完成了堆排序。从这个思路出发,对于top k问题,我们为什么要维护一个最小堆呢,因为我们要过滤所有的数据,保证每次弹出一个最小值,之后剩下的k个一定是top k的最大值,但是这k个不一定有序,如果需要我们可以堆这k进行任何排序,因为我们通过过滤,数据已经很少了,时间复杂度就是从n个中过滤出来k个。首先任选k个构建最小堆, 时间复杂度O(klogk), 用最小堆过滤n-k个数字,每次维护堆的性质,时间O((n-k)logk).总的时间复杂度O(klogk + (n-k)logk)。(注意当k多大时,我们不在使用堆的数据结构,这里留给读者计算)。

void HeapSort(int *A,int n){BuildMaxHeap(A,n);//建立最大堆for(int i=n;i>=2;i--){//cout<<A[1]<<" ";swap(A[1],A[i]);//交互A[1]和A[i],使得A[i]中为当前最大的元素heapsize--;//堆大小减去1,便于下次操作去掉已经排好序的元素MaxheapFY(A,1);//此时A[1]不一定满足最大堆的性质,重新维护下标1的最大堆的性质}
}

快速排序

快速排序是对冒泡排序的改进。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

伪代码

int partition(array,left,right){index = left;i = left+1, j = right;while(1) {while(i<j && array[i]<array[index]) ++i;while(i<j && array[j]>array[index]) ++i;if(i>j) break;else{swap(array[i],array[j]);++i; --j;}}swap(array[index], array[j]);return j
}// Qsort(array,0,n-1)
void Qsort(array, left, right){if(left < right) {mid = partition(array, left, right);Qsort(array, left, mid-1);Qsort(array, mid+1, right);}
}

快速排序在海量数据处理的过程中,一般不会直接使用,因为快速排序在基于内存的排序时,性能很好,是最常用的方法,例如我们对大数据进行划分后,可以对单个小文件应用快速排序。其实应用多还有就是快速排序中的一次划分很重要,比如我们有很多性别{男,女},请将所有的女性放到男性的前面,我们只需要才有划分思想就OK了。

桶排序

桶排序的工作原理是将数据分装到有限数量的桶里,对每个桶分别进行排序,如果能将数据均匀分配,排序的速度将是很快的。

伪代码

bucket_sort(array):buckets[10]; // 申请10个桶for d in array:index = function(d) // 将d划分到每一个桶中buckets[index].append(index)// 对每一个桶分别进行排序for i in {1...10}:sort(buckets[i])// concat所有结果,这里是连接不是归并,// 我们划分的时候保证buckets[i] < buckets[i+1]

位图排序

使用提示,如何想到使用计数排序或者在海量数据处理方面使用计数排序的思想呢?如果我们知道所有的数字只出现一次,我们就可以只使用计算排序中的记录函数,将所有存在的值对应的位置设置为1,否则对应为0,扫描整个数组输出位置为1对应的下标即可完成排序。这种思想可以转为位图排序。

我们使用一个位图来表示所有的数据范围,01位串来表示,如果这个数字出现怎对应的位置就是1,否则就是0.例如我们有一个集合S = {1,4,2,3,6,10,7}; 注意到最大值10,用位图表示为1111011001,对应为1的位置表示这个数字存在,否则表示这个数字不存在。

伪代码

// step 1, 初始化为0
for(i = 0; i < n; i++){bit[i] = 0;
}// step 2, 读取数据,对应设置为1
for d in all file_read:bit[d] = 1// step3, 对应为1的位置写入文件
for(i = 0; i < n; i++) {if(bit[i] == 1) {write i to sort_file}
}

归并排序

归并排序: 建立在归并操作上的一种排序算法,该方法采用分治法的一个非常典型应用,一般我们都是使用二路归并,就是将数据划分成两部分进行处理,但是注意我们可以是多路归并,不要让二路归并排序限制我们的思想。
从下面的伪代码中,我们可以很容易看到二路归并排序只有两个部分,一个是递归划分,一个是归并操作,这就是我们最长用到的归并排序。但是在海量数据的排序过程中,我们可以使用二路归并,当然我也可以选择多路归并排序。

伪代码:

// 归并
merge(array, left, mid, right):tmp = new int[right - left + 1] // 申请复制空间i = left, j = mid+1, k = 0;while(i <= mid && j <= right) {if(array[i] < array[j]) tmp[k++] = array[i++];else tmp[k++] = array[j++];}// 处理尾部,可能会有一部分没有处理结束while(i <= mid) tmp[k++] = array[i++];while(j <= right) tmp[k++] = array[j++];// copy回到原来的数据, tmp -> arraycopy(array[left, right], tmp[0,k-1])// 调用
merge_sort(array, left, right):if(left < right) {mid = (left + right) / 2;merge_sort(array, left, mid);merge_sort(array, mid+1, right);// 调用归并函数merge(arr, left, mid, right);}

倒排索引

倒排索引是一种索引方法,常用在搜索引擎中,这个数据结构是根据属性值来确定记录的位置。对于一批文档,我们的属性值就是关键字,对应值是包含该属性的文档的ID或者文化的位置。

例如:


T0 = {a,b,c}
T1 = {a,d}
T2 = {a,b,c,e}

构建倒排索引

a: {0,1,2}
b: {0,2}
c: {0,2}
d: {1}
e: {1}

检索的时候可以根据关键字的交集或者并集进行检索,可以看出,倒排索引就是正向索引的相反。原理其实很简单,可以通过学习或者问题的性质,来发现什么时候使用倒排碎索引,最重要的倒排索引怎么优化,在内存中和文件上如何分配,才能满足快速的检索。倒排索引的构建可以根据自己的业务,决定需要存储什么信息,但是属性值是确定的,对应的集合中可以保留出现的次数等信息。

字典树

字典树,Trie树是一种前缀树,我们之前也有介绍过,一般应用在快速查询中,例如搜索提示,当你输入前半部分,会提示后半部分的内容。字典树用一句话表示就是根据字符串的前缀构成的树结构。
格式定义

template<typename T>
struct TreeNode {int flag; // {1,0}1:表示存在,0:表示不存在int count: // 表示这个字符串出现的次数struct TreeNode **childs; // 索引的孩子节点T value;
};

搜索字典项目的方法为:(来自百度百科)

  1. 从根结点开始一次搜索;
  2. 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
  3. 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
  4. 迭代过程……
  5. 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。

面试问题

  1. 求top k, 可以用到堆数据结构.

例如我们有100个有序(降序)的数组,现在从这个100个数组中找到最大的k个元素。这就是上述问题的抽象。使用100路归并(后面的归并排序)。

用一个大小为k的最大堆,每次弹出一个最大值,记录是那个队列中的值,直到出现k个数,就结束。
这里面的两个思想:

  • 归并,不能处理的大问题,分成多个小问题并行处理,之后归并结果,比如外排序
  • 堆,帮助我们找到top k,k要相对n较小。

  1. 中位数

在一个大小为10GB的文件中有一堆整数,乱序排列,要求找出中位数。内存限制2GB。
这个问题,我们可以使用外排序,并且记录元素的各种,最后得到中位数即可。这里我们使用桶排序的思想。

  • 将所有的数据根据前8位进行分桶,最多有255个桶,并且记录每个桶中元素的格式。这里的桶是文件表示。
  • 根据划分性质,我们有buckets[i] < buckets[i+1]; count[i]:个数
  • 如果sum{count{1,k}} < sum(count{1,n}) / 2 <= sum{count(1,k+1)},得到中位数在k+1个桶中,
    +将k+1个桶中读取内存(假设小于2GB,否则要根据次8位进行继续分桶),找到第m个数字,sum{count{1,k}}+m对应的是中位数的下标。
  1. 基于位图的排序

给你一个文件,里面有n个不重复的正整数,而且每一个数都小于等于n(10^7)。请最多使用1M的内存空间,对这个文件进行排序。

可以使用归并排序,但是时间应该慢,我们这里使用位图排序,107/8=1.25Mb10^7 / 8 = 1.25Mb107/8=1.25Mb, 我们只有1M内存空间,这里可以分成两个读取文件,(1,5∗106)(1, 5*10^6)(1,5∗106)和(5∗106,107)(5*10^6, 10^7)(5∗106,107)进行分开使用位图,空间占用0.625Mb.

  1. 大文件排序

海量数据排序,使用归并排序的思想进行排序,例如我们现在有一个5G的数据文件,每一行有一个32位的正整数,现在要求只能使用1G的内存空间,对这个文件排序。

我们有大数据处理的经验都知道,内存放不下,只能将大文件分成几个小文件,这就是划分,之后对每个文件进行排序,最后归并这几个小文件的排序结果,叫做多路归并。上述的过程可以叫做外排序,即借助外部的文件进行排序。

从这个题目出发我们使用之前介绍过的大数据处理技术完成这个排序过程。

  1. 划分成5个小文件,5G / 1G = 5

  2. 将单个文件读入内存,进行排序,写入文件

  3. 使用5路归并,将每个文件作为一路排序,归并最后得到结果

  4. 串的快速检索

    给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。

  5. “串”排序

    给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。

  6. 最长公共前缀

    对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。

  7. 搜索引擎

    一个搜索引擎执行的目标就是优化查询的速度:找到某个单词在文档中出现的地方。以前,正向索引开发出来用来存储每个文档的单词的列表,接着掉头来开发了一种反向索引。

    正向索引的查询往往满足每个文档有序频繁的全文查询和每个单词在校验文档中的验证这样的查询。实际上,时间、内存、处理器等等资源的限制,技术上正向索引是不能实现的。为了替代正向索引的每个文档的单词列表,能列出每个查询的单词所有所在文档的列表的反向索引数据结构开发了出来。随着反向索引的创建,如今的查询能通过立即的单词标示迅速获取结果(经过随机存储)。随机存储也通常被认为快于顺序存储。

参考

  1. 数据结构: 构建和使用堆
  2. 《算法导论》第六章:堆排序
  3. 《编程之美:面试与算法心得》

大数据的相关技术(Technology)相关推荐

  1. 浅谈大数据及相关技术在计算机专业的应用

    信息时代,各种高新科技高速发展,现代科学技术发展进入了"快车道",科学技术的实践化应用改变了人们的生活,其中就包括人工智能技术的应用,如智能家居系统.智能汽车.智慧城市等. 所谓人 ...

  2. 什么是大数据以及大数据的相关技术?

    所谓大数据,通俗理解就是短时间内快速产生的海量数据的各种有价值的数据,关键词是时间短,快速,海量,有价值,数据,通过大数据技术分析海量数据来得到其中有价值的数据用于商业途径,譬如分析用户的行为来进行定 ...

  3. 合肥市电力大数据应用工程技术研究中心成立

    11月6日,安徽省合肥市电力大数据应用工程技术研究中心在合肥供电公司正式成立.该电力大数据研究机构,对提高合肥电网的运营效率.服务质量和能源利用率等具有重要意义. 合肥供电公司申报的合肥市电力大数据应 ...

  4. 兼顾稳定和性能,58大数据平台的技术演进与实践

    http://www.infoq.com/cn/articles/58-big-data-platform-technology 主要内容分为三方面:58大数据平台目前的整体架构是怎么样的:最近一年半 ...

  5. 独家 | 大数据与AI技术在金融科技的应用

    独家 | 大数据与AI技术在金融科技的应用 [导读]本文选自百融金服CEO张韶峰和CRO季元于2017年9月14日晚在清华大数据"技术·前沿"系列讲座--大数据与AI技术在金融科技 ...

  6. 连载:阿里巴巴大数据实践—实时技术

    简介:相对于离线批处理技术,流式实时处理技术作为一个非常重要的技术补充,在阿里巴巴集团内被广泛使用. 前言: -更多关于数智化转型.数据中台内容请加入阿里云数据中台交流群-数智俱乐部 和关注官方微信公 ...

  7. 稳定和性能如何兼顾?58大数据平台的技术演进与实践

    作者|赵健博 编辑|尚剑 本文将为你分享58大数据平台在最近一年半内技术演进的过程,包括:58大数据平台目前的整体架构是怎么样的:最近一年半的时间内我们面临的问题.挑战以及技术演进过程:以及未来的规划 ...

  8. 《大数据》编辑推荐 | 基于遥感大数据的信息提取技术综述

    主题词 遥感大数据:目标识别:地物分割:变化检测 遥感大数据虽然能够多粒度.多时相.多方位和多层次地反映地物信息,但其数据量与日俱增.种类日益增多,且存在大量的冗余信息,使得从遥感数据中提取有效信息具 ...

  9. 处理大数据需要哪些技术

    大数据之所以能够从概念走向落地,说到底还是因为大数据处理技术的成熟,面对海量的数据,在有限的硬件条件下,以低成本满足大数据处理的各种实际需求.那么具体处理大数据需要哪些技术,今天我们来简单介绍一下大数 ...

最新文章

  1. 零基础入门学习Python(35)面向对象编程
  2. mysql修改编码格式6_修改编码格式MySQL
  3. 证书重复冲突问题:Command /usr/bin/codesign failed with exit code 1
  4. 03-29复利计算单元测试
  5. python抽奖概率设计_通过python分析原神,结果出现了“这种情况”
  6. 推动Windows的限制:物理内存
  7. js负数比较大小_【建阳童小|阅享数学(第十一期)】负数的由来
  8. android+残留软件包名,关于使用pm hide伪卸载系统软件的一些包名
  9. springcloud工作笔记105---springboot2.x实现优雅的关闭服务
  10. 第三十一章 线程------GIL、线/近程池、异/同步、异步回调
  11. 冲刺周期二--站立会议03
  12. 认识ruquest对象
  13. [入门] Delphi XE2 的控件安装方法。
  14. Java语言的技术平台:JavaSE、JavaEE和JavaME
  15. qqxml图片代码_动态图的QQXML代码示例——篮球规范动作示范!
  16. 兔子能不能跑得过乌龟
  17. 反相器有时候为了强调低电平有效,将反相器的图形符号中的小圆圈画在输入端,如数电中的画法。有时候小圆圈在前面只表示低电平有效,没有反相的意思,例如与非门组成的RS触发器
  18. 吊打面试官的硬核法宝~阿里架构师最新总结软件测试面试宝典
  19. 怎样学好高一数学,首先掌握这4种方法
  20. 数字信号处理第一课(转)

热门文章

  1. 人月神话阅读笔记07
  2. poj1129Channel Allocation
  3. SurfaceView 间取得焦点
  4. User Profile Data Web Part 读取属性字段
  5. 用XML反序列化快速完成ASP.NET配置文件
  6. EasyPHP-2.0b1+ Mantis-1.1.0安装及技巧
  7. Datawhale组队学习周报(第020周)
  8. 网络设置巨形帧_Trunk的概念与设置
  9. 利用 OpenCV+ConvNets 检测几何图形
  10. API 大赛决赛名单出炉,速来围观!