外部排序
一、定义问题
外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。外部排序最常用的算法是多路归并排序,即将原文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序。然后,对已经排序的子文件进行多路归并排序。
二、处理过程
- 按可用内存的大小,把外存上含有n个记录的文件分成若干个长度为L的子文件,把这些子文件依次读入内存,并利用有效的内部排序方法对它们进行排序,再将排序后得到的有序子文件重新写入外存;
- 对这些有序子文件逐趟归并,使其逐渐由小到大,直至得到整个有序文件为止。
三、示例
假设有两个存储了有序数列的文件:source1.txt和source2.txt,以及一个存入排序结果的destination.txt文件。
source1.txt文件存储内容如下所示:
source2.txt文件存储内容如下所示:
归并(Merge)操作将两个有序数列合并为一个新的有序数列。其过程大致是:从source1中取出最小的元素x,从source2取出最小的元素y,比较x和y的大小,将较小的数存入destination文件,如果该值为x,则取source1中紧邻x的下一个数值与y比较,以此类推。下面是一个简单的代码实现:
//将字符串转变为int型变量 int str2int(string str) { int ret=0; for(int i=0;i!=str.length();++i) ret=ret*10 + str[i]-'0'; return ret; } int main() { ifstream fsource1; ifstream fsource2; ofstream fdestination; fsource1.open("source1.txt"); fsource2.open("source2.txt"); fdestination.open("destination.txt"); if(!fsource1 || !fsource1 ||!fdestination) { cout<<"打开文件失败!"; return; } string str1; string str2; int num1=0; int num2=0; fsource1>>str1; fsource2>>str2; while(fsource1 || fsource2) { if(fsource1 && fsource2) { num1 = str2int(str1); num2 = str2int(str2); if(num1<=num2) { fdestination<<num1<<" "; fsource1>>str1; } else { fdestination<<num2<<" "; fsource2>>str2; } } else if(fsource1) { fdestination<<str1<<" "; fsource1>>str1; } else { fdestination<<str2<<" "; fsource2>>str2; } } return 0; }
程序运行后destination.txt文件存储内容如下所示:
归并排序
下面将讲述归并排序作为内部排序时的运作原理。在开始讲解之前,我们先来看一副转自维基百科的归并排序动态图,直观地感受一下归并排序。
用作内部排序时,归并排序把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
假设我们有一个没有排好序的序列,那么首先我们使用分割的办法将这个序列分割成一个一个已经排好序的子序列,然后再利用归并的方法将一个个的子序列合并成排序好的序列。分割和归并的过程可以看下面的图例。
从上图可以看出,我们首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的数据,再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。
下面给出一个归并排序类模板的实现:
template<class T> void MergeSort(vector<T>& vec) { vector<T> temp(vec.size()); MergeSort(vec,0,vec.size()-1,temp); } template<class T> void MergeSort(vector<T>& vec,int begin,int end,vector<T>& temp) { if(begin<end) { int middle = begin + (end-begin)/2; MergeSort(vec,begin,middle,temp); MergeSort(vec,middle+1,end,temp); MergeSort(vec,begin,middle,end,temp); } } template<class T> void MergeSort(vector<T>& vec,int lbegin,int lend,int rend,vector<T>& temp) { int rbegin = lend+1; int index = lbegin; int i =lbegin; while(lbegin<=lend && rbegin<=rend) { if(vec[lbegin]<=vec[rbegin]) temp[index++] = vec[lbegin++]; else temp[index++] = vec[rbegin++]; } while(lbegin<=lend) temp[index++] = vec[lbegin++]; while(rbegin<=rend) temp[index++] = vec[rbegin++]; for(;i<=rend;++i) vec[i]=temp[i]; }
下面是测试程序以及运行结果:
int a[]={14,12,15,13,11,16}; vector<int> vec(a,a+sizeof(a)/sizeof(*a)); for (vector<int>::const_iterator iter = vec.begin();iter!=vec.end();++iter) cout<<*iter<<" "; cout<<endl; MergeSort(vec); for (vector<int>::const_iterator iter = vec.begin();iter!=vec.end();++iter) cout<<*iter<<" "; cout<<endl;
算法复杂度
归并排序算法的最差时间复杂度和平均时间复杂度均为O(nlogn),最优时间复杂度为O(n)。空间复杂度为O(n)。归并排序是稳定的排序算法。
参考书目及文章
《C++数据结构导引》
排序总结五(归并排序)
维基百科之归并算法