外排序

原始文件存储了每个特征项及其对应的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);这两句作的乱。但我实在是不知道“乱”在哪里。

posted @ 2011-11-06 22:06  张朝阳  阅读(7356)  评论(1编辑  收藏  举报