排序算法及其在MapReduce的应用
(2014-08-17 12:25:36)
标签:
hadoop排序算法快速排序堆排序归并排序mapreduce排序 |
分类: 数据结构及算法 |
博客公告:(1)本博客所有博客文章搬迁至《博客虫》http://www.blogchong.com/
(2)文章对应的源码下载链接参考博客虫网站首页的“代码GIT”直通车;
(3)更多的相关文章更新,以及代码等,请关注博客虫网站,网站中有技术Q群,以及代码共享链接。
//代码部分缩进之类的各式貌似乱了,有兴趣的可以参考PDF文档,网盘中
目录
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 算法分析
Ø
Ø
Ø
2.2.3 算法实现
void SelectionSort(int *pDataArray, int iDataNum) {
for (int i = 0; i < iDataNum, i++) {
int key = i; //用于交换的索引
for (int j = i + 1; j < iDataNum; j++) {
if (pDataArray[j] < pDataArray[key])
key = j;
}
if (key != i)
{
int tmp = pDataArray[key];
pDataArray[key] = pDataArray[i]; //交换i值
pDataArray[i] = tmp;
}
}
}
2.2.4 实际应用:
属于基本排序,性能较差,较少使用。
2.3 冒泡排序
2.3.1 设计思想
长度为N的无序数组,第一堂从1到N,依次和旁边的比较,大数右移,最后将最大的那个值滚动到N位置;第二趟类似前面,将第二大的值放到N-1位...直到第N-1趟完成排序。整个过程类似一个水泡依次网上冒,直到冒到最大的位置上。
2.3.2 算法分析
Ø
Ø
Ø
2.3.3 算法实现
void BubbleSort(int *pDataArray, int iDataNum) {
for (int i = 0; i < iDataNum - 1; i++) {
for (int j = 0 ; j < iDataNum - 1 - i; j++) {
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 算法分析
Ø
Ø
Ø
2.4.3 算法实现
void InsertSor(int *pDataArray, int iDataNum) {
for (int i = 1; i < iDataNum; i++) { //从第二个元素开始pDataArray[0]只有一个元素的数列,有序
int j = i -
1;
int tmp = pDataArray[i];
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, int SearchData) {
int mid = (begin + end)/2;
if (pDataArray[mid] == SearchData) return mid;
else if (pDataArray[mid] < SearchData) return BinSearch(pDataArray, mid + 1, end, SearchData);
else return BinSearch(pDataArray, begin, mid -1, SearchData);
}//采用递归的方式进行二分查找,减少比较次数
2.4.4 实际应用
属于基本排序算法,性能较低,较少使用
2.5 快速排序
2.5.1 设计思想
采用分而治之的思想,将无序数组进行分割,选择一个元素value(通常是第一个元素),第一次将小于Value的放在前段,大于value的放在后端;第二次排序分别对两段进行重复如上操作...进行递归操作,直到粒度划分到最小两个元素。
2.5.2 算法分析
Ø
Ø
Ø
2.5.3 算法实现
//采用类似二分查找的递归方式(也是分段)
int Split (int *pDataArray, int Begin, int End) {
int key=pDataArray[Begin];
while (Begin < End) {
while (Begin < End && pDataArray[End] >= key) //从后查找小于key的值并且缩小end值
End--;
if ( Begin != End) {
pDataArray[Begin] = pDataArray[End];
Begin++;
while (Begin < End && pDataArray[Begin] <= key)
Begin++;
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);
}
2.5.4 实际应用
比较常用的排序算法,Hadoop中Map阶段第一次排序默认使用的就是快排。
2.6 归并排序
2.6.1 设计思想
归并排序是将两个有序表合并成一个新的有序表,即把待排序的序列分成若干个有序的子序列,再把有序的子序列合并为整体有序序列。
而自底向上的归并则是将长度为N的无序数组切分成若干个N个有序子序列,再两两合并(起始时单元素为一个子序列),然后再将合并后的N/2(或者N/2+1)个子序列进行两两合并,依次类推得到一个完整的有序数组。
2.6.2 算法分析
Ø
Ø
Ø
2.6.3 算法实现
//自底向上的归并
void Merge (int *pDataArray, *int pTempArray, int bIndex, int mIndex, int eIndex) {
int mLenth = eIndex - bIndex;
int i =
0;
int j =
bIndex;
int k = mIndex;
while (j < mIndex && k < eIndex) {
if (pDataArray[j] <= pDataArray[k]) {
pTempArray[i++] = pDataArray[j];
j++;
} else {
pTemArray[i++] = pDataArray[k];
k++;
}
if (j == mIndex)
while (k < eIndex)
pTempArray[i++] = pDataArray[k++];
if (k == eIndex)
while (j < eIndex)
pTempArray[i++] = pDataArray[j++];
if (i = 0; i < mLenth;
i++)
pDataArray[bIndex+i] = pTempArray[i];
}
}//只是完成两个有序子序列的排序
void BottomUpMergeSort (int *pDataArray, int iDataNum) {
int *pTempArray = (int *) malloc (sizeof (int) *
iDataNum);
int length = 1; //初始子序列的长度为1,都为有序(单元素)
while (length < iDataNum) { //子序列不能大于整个无序数组的长度
int i = 0;
for (; i + 2*length < iDataNum; i += 2*length)
Merge(pDataArray, pTempArray, i, i + length, i + 2*length);
//子序列的长度成倍数增长,1-->2-->4-->8,注意i的增长规律
if (i + length < iDataNum)
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 设计思想
Ø
Ø
Ø
Ø
2.7.2 算法分析
Ø
Ø
Ø
2.7.3 算法实现
Void HeapAdjust (int *pDataArray, int i, int iDataNum)
{
}
if (rchild <= iDataNum && pDataArray[rchild] > pDataArray[max]) {
//root依次和左子树,右子树比较,三者中找出最大值,进行max标记
}
if (max != i)
{
}
}//函数执行一次只进行一次交换(排除递归),进行递归的话则顺着max值往下走,直到形成大顶堆
Void HeapSort (int *pDataArray, int iDataNum)
{
}
for (i = iDataNum; i >= 1; i--) {
}
}
2.7.4 实际应用
3 MapReduce中排序应用
3.1 MapReduce简单过程
3.1.1 Map阶段
Read(读取)
Read:
Collect:分为map过程和partition过程,map根据inputSplit生成KV对,而Partition添加分区标记(辅助排序用),并写入环形缓存区;
Spill:
3.1.2 Shuffle阶段
Shuffle阶段主要就是一个数据拷贝的过程,Map端合成的大文件之后,通过HTTP服务(jetty server)拷贝到Reduce端。
3.1.3 Reduce阶段
3.2 补充
如上可以看到,一个MapReduce过程涉及到了一次快排、两次归并以及一次堆排的操作。
因此在学习Hadoop的过程中,掌握这些基本的排序算法还是非常有用的。
4 文档小结