热门标签 | 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的过程至关重要。而插入排序、冒泡排序以及选择排序则作为最基本的排序算法更是应更掌握的。

 

 


推荐阅读
  • 在OpenCV 3.1.0中实现SIFT与SURF特征检测
    本文介绍如何在OpenCV 3.1.0版本中通过Python 2.7环境使用SIFT和SURF算法进行图像特征点检测。由于这些高级功能在OpenCV 3.0.0及更高版本中被移至额外的contrib模块,因此需要特别处理才能正常使用。 ... [详细]
  • 深入解析WebP图片格式及其应用
    随着互联网技术的发展,无论是PC端还是移动端,图片数据流量占据了很大比重。尤其在高分辨率屏幕普及的背景下,如何在保证图片质量的同时减少文件大小,成为了亟待解决的问题。本文将详细介绍Google推出的WebP图片格式,探讨其在实际项目中的应用及优化策略。 ... [详细]
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • 本文介绍了一个使用Slideview组件实现循环轮播效果的例子,并将其作为ListView顶部的一项。此ListView包含了两种不同的模板设计,一种以Slideview为核心,另一种则是标准的单元格模板,包含按钮和标签。 ... [详细]
  • 本文详细记录了 MIT 6.824 课程中 MapReduce 实验的开发过程,包括环境搭建、实验步骤和具体实现方法。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • Requests库的基本使用方法
    本文介绍了Python中Requests库的基础用法,包括如何安装、GET和POST请求的实现、如何处理Cookies和Headers,以及如何解析JSON响应。相比urllib库,Requests库提供了更为简洁高效的接口来处理HTTP请求。 ... [详细]
  • AI炼金术:KNN分类器的构建与应用
    本文介绍了如何使用Python及其相关库(如NumPy、scikit-learn和matplotlib)构建KNN分类器模型。通过详细的数据准备、模型训练及新样本预测的过程,展示KNN算法的实际操作步骤。 ... [详细]
  • Web动态服务器Python基本实现
    Web动态服务器Python基本实现 ... [详细]
  • 本文详细介绍了如何正确设置Shadowsocks公共代理,包括调整超时设置、检查系统限制、防止滥用及遵守DMCA法规等关键步骤。 ... [详细]
  • 理解浏览器历史记录(2)hashchange、pushState
    阅读目录1.hashchange2.pushState本文也是一篇基础文章。继上文之后,本打算去研究pushState,偶然在一些信息中发现了锚点变 ... [详细]
  • 精选10款Python框架助力并行与分布式机器学习
    随着神经网络模型的不断深化和复杂化,训练这些模型变得愈发具有挑战性,不仅需要处理大量的权重,还必须克服内存限制等问题。本文将介绍10款优秀的Python框架,帮助开发者高效地实现分布式和并行化的深度学习模型训练。 ... [详细]
  • 本文详细介绍了如何在ARM架构的目标设备上部署SSH服务端,包括必要的软件包下载、交叉编译过程以及最终的服务配置与测试。适合嵌入式开发人员和系统集成工程师参考。 ... [详细]
  • 深入理解云计算与大数据技术
    本文详细探讨了云计算与大数据技术的关键知识点,包括大数据处理平台、社会网络大数据、城市大数据、工业大数据、教育大数据、数据开放与共享的应用,以及搜索引擎与Web挖掘、推荐技术的研究及应用。文章还涵盖了云计算的基础概念、特点和服务类型分类。 ... [详细]
  • 计算机学报精选论文概览(2020-2022)
    本文汇总了2020年至2022年间《计算机学报》上发表的若干重要论文,旨在为即将投稿的研究者提供参考。 ... [详细]
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社区 版权所有