外排序
原始文件存储了每个特征项及其对应的IG值,一行一条记录,约11.7万行。
#ifndef TERM_H #define TERM_H #include<string> using std::string; class term{ private: string word; double ig; public: term(string word,double ig):word(word),ig(ig){ } string getWord() const{ return word; } void setIG(double newig){ ig=newig; } double getIG() const{ return ig; } bool operator < (const term& rhs) const{ return ig<rhs.ig; } bool operator <= (const term& rhs) const{ return ig<=rhs.ig; } bool operator > (const term& rhs) const{ return ig>rhs.ig; } bool operator == (const term& rhs) const{ return ig==rhs.ig; } }; #endif
由于数据量太大,不能在内存中完成排序,所以就有了外排序。其基本思路是:
step1.把原始数据分成M段,每段都排好序,分别存入M个文件中--我们在此称为顺串文件。
step2.从M个顺串文件是读出头条记录,进行M路归并排序,最小的放到输出文件,同时删除对应的顺串文件中的记录。
step1中通常采用替换选择法(replacement selection)来充分利用内存产生尽可能少的顺串文件。
#include<iostream> #include<fstream> #include<sstream> #include<cstdlib> #include<vector> #include<algorithm> #include<ctime> #include"term.h" using namespace std; template<typename Comparable> void percolate(vector<Comparable>& vec,int index,int n){ int i=index; int j=2*i+1; while(j<=n){ if(j<=n-1 && vec[j]>vec[j+1]) j++; if(vec[i]<=vec[j]) break; else{ swap(vec[i],vec[j]); i=j; j=2*i+1; } } } template<typename Comparable> void buildHeap(vector<Comparable>& vec){ int len=vec.size(); int i=(len-1)/2; for(;i>=0;i--) percolate(vec,i,len-1); } //double转换为string string double2string(double d){ ostringstream osstr; osstr<<d; return osstr.str(); } int main(){ clock_t bt=clock(); string inputfile="/home/orisun/master/fudan_corpus/all_terms"; string outputfiles_prefix="/home/orisun/master/fudan_corpus/run"; int runfile_index=0; string outputfile=outputfiles_prefix+double2string(runfile_index); ifstream infile(inputfile.c_str(),ios::in); ofstream outfile(outputfile.c_str(),ios::out); string line,word,sd; double d; vector<term> vec; int heap_size=3; //设置堆的大小 int n=heap_size; int deadspace_size=0; //存储堆的死区中元素的个数 while(getline(infile,line)){ istringstream isstream(line); isstream>>word; isstream>>sd; d=atof(sd.c_str()); term inst(word,d); if(n>0){ vec.push_back(inst); n--; } else{ if(n==0){ buildHeap(vec); n=-1; } outfile<<vec[0].getWord()<<"\t"<<vec[0].getIG()<<endl; //把堆顶元素输出到顺串文件 if(inst>vec[0]){ //如果从输入文件中读取的下一条记录比刚才的堆顶元素大 vec[0]=inst; //用从输入文件中读取的元素替换堆顶元素 percolate(vec,0,heap_size-deadspace_size-1); //调整堆的非死区 } else{ //否则 vec[0]=vec[heap_size-deadspace_size-1]; //把堆中最后一个元素(不包含死区)放到堆顶 vec[heap_size-deadspace_size-1]=inst; //把从文件中读取的元素放到非死区的最后一个位置 deadspace_size++; //死区的范围扩展1个 percolate(vec,0,heap_size-deadspace_size-1); //调整堆的非死区 } if(deadspace_size==heap_size){ //如果堆已经全部变成死区 deadspace_size=0; //死区大小置0 buildHeap(vec); //重新建立堆 runfile_index++; //准备往下一个顺串文件里写内容 outputfile=outputfiles_prefix+double2string(runfile_index); outfile.close(); outfile.clear(); outfile.open(outputfile.c_str(),ios::out); } } } infile.close(); //输入文件已经读完,把堆中剩余的元素按照堆排序的方法输出到一个新的顺串文件中 buildHeap(vec); //由于堆中有死区,所以要重新buildHeap一下 runfile_index++; //准备往下一个顺串文件里写内容 outputfile=outputfiles_prefix+double2string(runfile_index); outfile.close(); outfile.clear(); outfile.open(outputfile.c_str(),ios::out); for(int j=0;j<heap_size;j++){ outfile<<vec[0].getWord()<<"\t"<<vec[0].getIG()<<endl; vec[0]=vec[heap_size-j-1]; percolate(vec,0,heap_size-j-2); } outfile.close(); clock_t et=clock(); cout<<"Time:"<<(double)(et-bt)/CLOCKS_PER_SEC<<" seconds."<<endl; return 0; }
step2中使用败者树进行M路归并排序,可以减少比较的次数。
#include<iostream> #include<fstream> #include<sstream> #include<cstdlib> #include<ctime> #include<vector> #include<algorithm> #include<functional> #include"term.h" using namespace std; const double MAXIG=1; //定义最大的信息增益值 const int K=7; //顺串文件的个数 template<typename Comparable> void adjustLS(vector<Comparable>& b,vector<int>& ls,int index){ int i=index/2; //ls[0]空出来不用 int j=2*i; while(i>0){ if(b[ls[j]]>b[ls[j+1]]) j++; ls[i]=ls[j]; i/=2; j=2*i; } } template<typename Comparable> void buildLoserTree(vector<Comparable>& b,vector<int>& ls){ int k=b.size(); for(int i=k,j=0;i<2*k;i++,j++) ls[i]=j; for(int i=2*k-1;i>=k;i-=2) adjustLS(b,ls,i); } //double转换为string string double2string(double d){ ostringstream osstr; osstr<<d; return osstr.str(); } int main(){ clock_t bt=clock(); string prefix="/home/orisun/master/fudan_corpus/run"; ifstream *infiles = new ifstream[K]; for(int i=0;i<K;i++){ //打开所有的顺串文件 string fn=prefix+double2string(i); infiles[i].open(fn.c_str(),ios::in); } ofstream outfile("/home/orisun/master/fudan_corpus/all_terms_sorted",ios::out); vector<term> b; vector<int> ls(2*K-1); string line,word,sd; double d; //初始化b for(int i=0;i<K;i++){ getline(infiles[i],line); istringstream is(line); is>>word; is>>sd; d=atof(sd.c_str()); term inst(word,d); b.push_back(inst); } //初始化ls buildLoserTree(b,ls); int read=0; //记录已经读完的顺串文件的个数 int flag[K]; for(int i=0;i<K;i++) flag[i]=0; while(read<K){ int index=ls[1]; term tm=b[index]; //把树的根元素写入输出文件 outfile<<tm.getWord()<<"\t"<<tm.getIG()<<endl; if(getline(infiles[index],line)){ //从相应的顺串文件中读出下一个元素 istringstream is(line); is>>word; is>>sd; d=atof(sd.c_str()); term inst(word,d); b[index]=inst; adjustLS(b,ls,K+index); //重新调整失败树 } else{ //如果某个顺串文件读完了 //line="file end."; if(flag[index]==0){ flag[index]=1; read++; b[index].setIG(MAXIG); //把b中相应的元素置为最大 } if(read<K) adjustLS(b,ls,K+index); } } //关闭文件流 outfile.close(); for(int i=0;i<K;i++){ infiles[i].close(); } clock_t et=clock(); cout<<"Time:"<<(double)(et-bt)/CLOCKS_PER_SEC<<" seconds."<<endl; return 0; }
上面K路归并的代码可以产生正确的输出,只是在main函数退出的时候报告内存崩溃,初步排查问题出在line这个变量上,也就是getline(infiles[index],line); istringstream is(line);这两句作的乱。但我实在是不知道“乱”在哪里。
本文来自博客园,作者:张朝阳,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/2238434.html