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