热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

排序算法及其在MapReduce的应用

博客公告:(1)本博客所有博客文章搬迁至《博客虫》http:blogchong.com(2)文章对应的源码下载链接参考博客虫网站首页的“代码GIT”;

博客公告:

 (1)本博客所有博客文章搬迁至《博客虫》http://blogchong.com/

                     (2)文章对应的源码下载链接参考博客虫网站首页的“代码GIT”;

                      (3)更多的相关文章更新,以及代码等,请关注博客虫网站,网站中有技术Q群,以及代码共享链接。

                 (4)该博客内容还会继续更新,不过会慢一些。

  

该文档为实实在在的原创文档,转载请注明:http://blog.sina.com.cn/s/blog_8c243ea30102uz42.html

 

1 文档说明

 

        该文档为学习基本排序算法过程中的学习笔记,大部分内容从网络上其他渠道也能得到,仅用于记录备忘之用。

冒泡、选择、插入三种作为基本的排序算法是必须要掌握的,而在MapReduce的实际应用中。在Map阶段,k-v溢写时,采用的正是快排;而溢出文件的合并使用的则是归并;在Reduce阶段,通过shuffle从Map获取的文件进行合并的时候采用的也是归并;最后阶段则使用了堆排作最后的合并过程。

所以快排、归并以及堆排是必须要掌握的排序算法,这都在MapReduce内部使用的排序算法,学习Hadoop的必须过程。

 

2 排序算法

 

2.1算法稳定性

 

所谓算法稳定性即能够保证排序前两个相等的数在排序中的过程中不会改变这两个数的顺序:例如Ai=Aj,Ai原来在Aj之前,但在排序之后Aj排在了Ai之前,这就是不稳定的表现。

不稳定的算法会导致元素交换增多(无效交换)。

 

2.2选择排序

 

2.2.1 设计思想

 

在一个长度为N的无序数组中,在第一趟遍历N个数据,将最小的数值与第一个交换,第二趟遍历N-1次,将剩下中最小的与第二个元素交换...第N-1趟遍历剩下两个元素,判断大小交换位置即可,完成排序。

 

2.2.2 算法分析

 

Ø  平均时间复杂度:O(n2);

Ø  空间复杂度:O(1); //用于交换和记录索引

Ø  稳定性:不稳定;//例如[5,5,3]在第一趟排序中,第一个5和3交换位置,破坏了稳定性

 

2.2.3 算法实现

 

void SelectionSort(int *pDataArray, int iDataNum) {

for (int i = 0; i    //从第一个元素开始

int key = i; //用于交换的索引

for (int j = i + 1; j //i+1之前的元素已经排好序

if (pDataArray[j]

key = j;        //动态更新key索引,指向最小索引

}

if (key != i){         //若key和i不重叠,则交换

int tmp = pDataArray[key];       //将最小值放在tmp中

pDataArray[key] = pDataArray[i]; //交换i值

pDataArray[i] = tmp;                //最小值放入i中,这一趟结束

}

}

}

2.2.4 实际应用:

 

属于基本排序,性能较差,较少使用。

 

2.3冒泡排序

 

2.3.1 设计思想

 

长度为N的无序数组,第一堂从1到N,依次和旁边的比较,大数右移,最后将最大的那个值滚动到N位置;第二趟类似前面,将第二大的值放到N-1位...直到第N-1趟完成排序。整个过程类似一个水泡依次网上冒,直到冒到最大的位置上。

 

2.3.2 算法分析

 

Ø  平均时间复杂度:O(n2);

Ø  空间复杂度:O(1); //用于交换的额外空间开销

Ø  稳定性:稳定;

 

2.3.3 算法实现

 

void BubbleSort(int *pDataArray, int iDataNum) {

for (int i = 0; i                    //必须进行N-1次的比较

for (int j = 0 ; j //iDataNum - 1 - i之后的元素已经有序

if (pDataArray[j] > pDataArray[j+1]) { //相邻两数进行比较,若前大后小则进行交换

int tmp = pDataArray[j];

pDataArray[j] = pDataArray[j+1];

pDataArrary[j+1] =tmp;            //完成交换

}

}

}

}

2.3.4 实际应用

 

属于基本排序,性能较差,较少使用。

 

2.4插入排序

 

2.4.1 设计思想

 

所谓插入排序即认为一个子序列是有序的,将一个数值插入到其中合适的位置中形成一个新的有序数列。长度为N的数组中,第一趟认为第一个数值是有序的,从第二个元素开始进行插入;第二趟从第三个元素插入...依次直到第N-1趟,第N个元素插入前面的有序数列完成排序。

 

2.4.2 算法分析

 

Ø  平均时间复杂性:O(n2);

Ø  空间复杂度:O(1);

Ø  稳定性:稳定;

 

2.4.3 算法实现

 

void InsertSor(int *pDataArray, int iDataNum) {

for (int i = 1; i

int j = i -1;                       //j为比较下标,从后往前比较,初始i-1

int tmp = pDataArray[i];    //将预插元素放入tmp中

if (j >= 0 && pDataArray[j] > tmp) {

pDataArray[j+1] = pDataArray[j]; //将元素往后移,第i个元素已经放入tmp,不会被覆盖

j--;

}

if ( j != i - 1)//只要j改变了,则需要换位

pDataArray[j] = tmp; //将元素插入合适的位置

}

}

//在查找的过程中,考虑是否可以用二分查找的方式查找插入位置,但时间复杂度不变

二分查找:

int BinSearch(int *pDataArray, int begin, int end, intSearchData) {

int mid = (begin + end)/2;

if (pDataArray[mid] == SearchData) return mid;

else if (pDataArray[mid]

else return BinSearch(pDataArray, begin, mid -1,SearchData);

}//采用递归的方式进行二分查找,减少比较次数

 

2.4.4 实际应用

 

属于基本排序算法,性能较低,较少使用

 

2.5快速排序

 

2.5.1 设计思想

 

采用分而治之的思想,将无序数组进行分割,选择一个元素value(通常是第一个元素),第一次将小于Value的放在前段,大于value的放在后端;第二次排序分别对两段进行重复如上操作...进行递归操作,直到粒度划分到最小两个元素。

 

2.5.2 算法分析

 

Ø  平均时间复杂度:O(nlog2n);

Ø  空间复杂度:O(n);

Ø  稳定性:不稳定;

 

2.5.3 算法实现

 

//采用类似二分查找的递归方式(也是分段)

int Split (int *pDataArray, int Begin, int End) {   //将数组以Value(begin元素)分成前后两段

int key=pDataArray[Begin];              //以第一个元素begin作为key或者说value值

while (Begin  //在交换位置的过程中,不断缩小Begin和End的差值,直到相等才结束

while (Begin = key)//从后查找小于key的值并且缩小end值

End--;           //找到小于key的那个元素停止

if ( Begin != End) {

pDataArray[Begin] = pDataArray[End]; //将查找到的那个end元素与begin交换,第一次交换其实是将key的元素位覆盖了,不过它已经放入key

Begin++;                                          //将Begin右移一位

while (Begin

Begin++;                                 //从左往右查找比key大的值,找到后停止

if (Begin != End) {

pDataArray[End] = pDataArray[Begin];

End--;//将查找到的Begin元素放到之前的那个End位置(End位置元素已经移走可覆盖)

}

}

}

pDataArray[Begin] =key;//最终Begin=End,退出while,而Begin位为空,刚好把key填入

return Begin;

}//针对一次排序

 

Void QSort (int *pDataArray, int Begin, int End){            //用于递归

if (End > Begin) {

int Mid = Split(pDataArray, Begin, End);    //获取折半位置

QSort (pDataArray, Begin, Mid - 1);//以该位置将以value值划分的数组分两段分别进行递归

QSort (pDataArray, Mid + 1, End);  //这是后段

}

}

 

void QuickSort (int *pDataArray, int iDataNum){     //快排入口

QSort(pDataArray, 0, iDataNum - 1);         //初始位置0到iDataNum-1

}

2.5.4 实际应用

 

比较常用的排序算法,Hadoop中Map阶段第一次排序默认使用的就是快排。

 

2.6归并排序

 

2.6.1 设计思想

 

归并排序是将两个有序表合并成一个新的有序表,即把待排序的序列分成若干个有序的子序列,再把有序的子序列合并为整体有序序列。

而自底向上的归并则是将长度为N的无序数组切分成若干个N个有序子序列,再两两合并(起始时单元素为一个子序列),然后再将合并后的N/2(或者N/2+1)个子序列进行两两合并,依次类推得到一个完整的有序数组。

 

2.6.2 算法分析

 

Ø  平均时间复杂度:O(nlog2n);

Ø  空间复杂度:O(n); //用于存储有序子序列合并后的有序子序列

Ø  稳定性:稳定

 

2.6.3 算法实现

 

//自底向上的归并

void Merge (int *pDataArray, *int pTempArray, int bIndex, intmIndex, int eIndex) {

int mLenth = eIndex - bIndex;    //合并后的有序序列长度

int i =0;                                    //记录合并后插入数据的偏移

int j =bIndex;                         //记录子序列1插入数据的偏移

int k = mIndex;  //记录子序列2插入数据的偏移(初始mIndex为两个序列的中间位置,子序列1尾,2的首)

while (j         //只要两者的偏移不超过自身子序列的长度即可

if (pDataArray[j] <= pDataArray[k]) {

pTempArray[i++] = pDataArray[j];    //子序列小,则将对应数据插入临时数组

j++;       //将子序列1继续偏移

} else {

pTemArray[i++] = pDataArray[k];

k++;            //两种情况,要么插子序列1,要么子序列2

}

if (j == mIndex)   //说明子序列1已经插入完毕,但还剩下有部分子序列2未插入

while (k

pTempArray[i++] = pDataArray[k++];      //将剩下的子序列2插入

if (k == eIndex)   //说明子序列2已经插入完毕,但还剩下有部分子序列1未插入

while (j

pTempArray[i++] = pDataArray[j++];      //将剩下的子序列1插入

if (i = 0; i            //将合并后的数组重新放入pDataArray

pDataArray[bIndex+i] = pTempArray[i]; //注意pDataArray的起始位置是bIndex

}

}//只是完成两个有序子序列的排序

 

void BottomUpMergeSort (int *pDataArray, int iDataNum) {

int *pTempArray = (int *) malloc (sizeof (int) *iDataNum);  //临时存放合并后的有序序列

int length = 1; //初始子序列的长度为1,都为有序(单元素)

while (length

int i = 0;

for (; i + 2*length

Merge(pDataArray, pTempArray, i, i + length, i + 2*length);

//子序列的长度成倍数增长,1-->2-->4-->8,注意i的增长规律

if (i + length

Merge(pDataArray, pTempArray, i, i + length, iDataNum);

//最后一部分不成倍数的末尾部分(从i+length到iDataNum),直接归并

length *= 2; //子序列的长度增长规律,2倍增长

}

free (pTempArray); //释放内存

}

2.6.4 实际应用

 

在MapReduce的Reduce溢出文件Merge的过程中,默认使用的就是归并排序,将Map结果合并,所以掌握归并排序至关重要。

 

2.7堆排序

 

2.7.1 设计思想

 

Ø 先把长度为N的数组调整成N个节点的组成的大顶推(若是降序排则调整为小顶堆),即根节点大于左右子树的完全二叉树。

Ø 将堆顶元素(对大值)与最后一个元素N交换,这样就形成了1~(N-1)以及N两个序列,一个是无序的,另一个是有序的。

Ø 将N-1个元素的新堆重新调整为大顶堆(为了再次找出最大的那个值),然后堆顶元素再次与最后一个元素(第N-1位)交换位置,则又形成了一个新的堆和一个新的有序数组(第N个元素和第N-1个元素)。

Ø 依次按照路上步骤进行操作,直到有序数组长度为N-1,则堆的size为1,即留下最后一个最小的值。至此,排序完成(从后往前排)。

 

2.7.2 算法分析

 

Ø  平均时间复杂度:O(nlog2n);

Ø  空间复杂度:O(1);

Ø  稳定性:不稳定;

 

2.7.3 算法实现

 

Void HeapAdjust (int *pDataArray, int i, int iDataNum){                //堆调整函数

        int ichild =2*i;                   //i的左子树

        int rchild = 2*i +1;           //i的右子树

        int max = i;                        //用于存储最大值,随时调整

        if (lchild <= iDataNum && pDataArray[lchild] >pDataArray[max]) {//lchild不超范围且大于堆顶

                  max =lchild;    //进行最大值标记

}

if (rchild <= iDataNum && pDataArray[rchild] >pDataArray[max]) {

//root依次和左子树,右子树比较,三者中找出最大值,进行max标记

        max = rchild;

}

if (max != i){    //i不等于max说明,左右子树中存在大于root的节点

        swap(pDataArray[i],pDataArray[max]);                  //节点值进行交换

        HeapAdjust(pDataArray, max, iDataNum);

        //存在交换则进行递归,不过root切换为之前的max

}

}//函数执行一次只进行一次交换(排除递归),进行递归的话则顺着max值往下走,直到形成大顶堆

 

Void HeapSort (int *pDataArray, int iDataNum){   //堆排入口函数

        inti;         //用于初始化大顶堆

        if (i = iDataNum/2; i >= 1; i--) { //注意pDataArray的位置是从1开始的

                  HeapAdjust (pDataArray, i, iDataNum);

                  //iDataNum/2为最后一个非叶子节点(可以研究下完全二叉树的特点),依次(i--)从非叶子节点开始构造,第一次只有三个节点,进行一次HeapAdjust之后形成一个三节点的大顶堆,最后形成了一个大顶堆

}

for (i = iDataNum; i >= 1; i--) { //i初始时是最后一个元素,所以为iDataNum

        swap(pDataArray[1],pDataArray[i]);     //第一个元素和最后一个交换

        HeapAdjust(pDataArray, 1, i -1);            //交换元素之后进行堆调整

}

}

 

2.7.4 实际应用

 

        在数据量比较大的时候经常会使用堆排序进行数据排序,这是一种比较常用的排序算法。在MapReduce的内部实现中,在Reduce阶段最后文件合并的过程,即使用堆排序进行文件内部数据排序。

 

3 MapReduce中排序应用

 

3.1 MapReduce简单过程

 

3.1.1 Map阶段

 

Read(读取)     ==> Collect(生成K-V)    ==>  Spill(溢写)

Read:   从HDFS读取inputSplit(由InputFormat根据文件生成);

Collect:分为map过程和partition过程,map根据inputSplit生成KV对,而Partition添加分区标记(辅助排序用),并写入环形缓存区;

Spill:     分为sort过程、comparess过程以及combine过程。数据不断的写入环形缓存区,达到阈值之后开始溢写,在溢写的过程中进行一次Sort,这里使用的排序是快排(QuickSort);一次溢出生成一个file,并且在生成file的过程中进行压缩(compress);多个file又会进行一次文件合并,在文件合并的过程中进行排序,这里使用的排序是归并排序(MegerSort)。

 

3.1.2 Shuffle阶段

 

Shuffle阶段主要就是一个数据拷贝的过程,Map端合成的大文件之后,通过HTTP服务(jettyserver)拷贝到Reduce端。

        拷贝到Reduce端的数据并不是马上写入文件,而是同样放在缓存中,达到阈值则进行溢写。

 

3.1.3 Reduce阶段

 

        合并溢写生成的file,这里使用的排序为归并排序(MegerSort),生成一些更大的文件(进一步减少文件个数)。

        在归并之后留下少量的大文件,最后对大文件进行一次最终合并,合并成一个有序的大文件(只有一个),这里使用的排序算法为堆排序(HeapSort)。

 

3.2 补充

 

如上可以看到,一个MapReduce过程涉及到了一次快排、两次归并以及一次堆排的操作。

因此在学习Hadoop的过程中,掌握这些基本的排序算法还是非常有用的。

 

4 文档小结

 

        从第三章我们可以看出掌握快排、归并以及堆排对深度理解MapReduce的过程至关重要。而插入排序、冒泡排序以及选择排序则作为最基本的排序算法更是应更掌握的。

 

 


推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 前言折腾了一段时间hadoop的部署管理,写下此系列博客记录一下。为了避免各位做部署这种重复性的劳动,我已经把部署的步骤写成脚本,各位只需要按着本文把脚本执行完,整个环境基本就部署 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • MR程序的几种提交运行模式本地模型运行1在windows的eclipse里面直接运行main方法,就会将job提交给本地执行器localjobrunner执行-- ... [详细]
  • 我们在之前的文章中已经初步介绍了Cloudera。hadoop基础----hadoop实战(零)-----hadoop的平台版本选择从版本选择这篇文章中我们了解到除了hadoop官方版本外很多 ... [详细]
  • MapReduce工作流程最详细解释
    MapReduce是我们再进行离线大数据处理的时候经常要使用的计算模型,MapReduce的计算过程被封装的很好,我们只用使用Map和Reduce函数,所以对其整体的计算过程不是太 ... [详细]
  • MapReduce 切片机制源码分析
     总体来说大概有以下2个大的步骤1.连接集群(yarnrunner或者是localjobrunner)2.submitter.submitJobInternal()在该方法中会创建 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有