当需要用竞赛树来写一个外部排序的时候,涉及到文件处理的知识全程懵逼,在这里好好复习一下。
1.对于像我这样的小白来说,一定不要搞混哪个函数是写入文件,哪个是从文件中读取;
(1)几个文件读出和写入之间的区别
int fscanf ( FILE *fp, char * format, ... ); ----------需要从文件中读取数据写入到变量或者数组中
int fprintf ( FILE *fp, char * format, ... );----------从数组或者变量或者键盘中得到的数据读入到文件中
fp表示指针变量,FILE是有效的一个文件标识,若用fopen打开文件成功,则返回一个有效的FILE*指针,若打开不成功,则返回NULL,说白了,文件指针是用于标注文件当前读写位置的
这两个从哪个输出到哪个比较容易混淆,可以这样理解,fscanf表示将文件中的内容写入内存,fprintf表示将内存中的内容读出到文件中
若是二进制文件,则读和写不太一样
将数据写入到文件中,用fwrite
将文件中的数据读出,用fread
fread ( void * ptr, size_t size, size_t count, FILE * stream )
fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
其中,ptr:指向保存数据的指针;size:每个数据类型的大小;count:数据的个数;stream:文件指针
函数返回写入数据的个数
fputs和fgets的优势是可以确定需要读出或者写入的个数
fgets(字符数组名,n,文件指针)--------需要从文件中读出n-1个字符到字符数组中
fputs(字符数组名,文件指针)--------需要向指定的文件中写入字符串
注意:从文件中读出的字符串不超过 n-1个字符。在读入的最后一个字符后加上串结束标志'/0';在读出n-1个字符之前,如遇到了换行符或EOF,则读出结束
(2)FILE常见的几个操作函数:
1>fopen("文件名“,”打开方式")——>常见的打开方式参数:r/rt--------(read)只读方式打开,若文件不存在,则打开失败;
w/wt-------------------以只写方式打开,若文件不存在,则新建立文件,若已存在,则删除掉原有数据
若带有+,比如r+,则表示还可以写,w+表示可以随意修改已存在的数据
若带有b,比如rb,则表示二进制的读入
2>fclose("文件名")表示关闭文件
3>rewind(文件名)文件指针重置为0
4>fseek(文件名,偏移量,起始位置)从起始位置开始,移动偏移量个字节,其中起始位置0-------开头,1------当前位置,2-----结尾
5>ftell(文件名)-------返回文件的长度
(3)在c++中还有文件流的存在
文件流是以外存对象为输入输出的流
输出流-------从内存到外存的数据--------ofstream,有一个put指针,指向下一个将要被写入的元素
输入流------从外存写入到内存的数据----------ifstream,有一个get指针,指向下一个将要被读取的元素
外部排序思路
(1)最小竞赛树其实是完全二叉树的一种类型,每一个内部节点所记录的是比赛的赢者,n个选手的最小竞赛树有n个外部节点和n-1个内部节点,树结构可以用数组来存储,二叉树的元素可以按照其编号存储在数组的相应位置;
(2)由于外排序所面向的数据量比较大,所以我们应该将其存储在文件中,从文件读入然后进行排序,在进行内排序的时候也是将从最小竞赛树中取得的元素放到相应的文件中,然后再将这些文件进行k路归并,直到剩余1个文件为止;
(3)因为最小竞赛树中的根节点是n个外部节点的编号,而放入文件的是该编号所对应的值,所以需要定义一个结构体,并且在设计最小竞赛树的结构的时候需要设定为template类型,后边所用到的最小竞赛树的类型是结构体和int类型两种;
(4)在进行最小竞赛树结构的时候,成员函数需要有初始化和重构,在根节点编号所对应的大小改变的时候需要将该条路径上的父节点进行重构;
(5)最小竞赛树的结构搭建好之后,需要在最小竞赛树的基础上进行内部排序和k路归并函数;
(6)在程序开头,设定了全局变量,其中count1表示需要排序的文件数值个数,number_to_sort表示每个内部排序的文件中的最大的int类型的个数,MAX表示归并过程中一个文件中的。数据归并完之后需要压入一个MAX;
内部排序
将number_to_sort个数进行初始化,然后从根节点编号所对应的值输出到相应的文件中,将该输出的值所对应的编号进行重构,
在重构之前将MAX赋给该编号所对应的值,直到头节点输出的值不为MAX为止,则该文件中的内部排序完成
completeWinnerTree<int> *w=new completeWinnerTree<int> (array,n);
for(int i=0;i<n;i++)
{
int index=w->winner();
arrays[i]=array[index];
array[index]=MAX;
w->rePlay(index);
}
k路归并
1.首先将内部排序完成的所有文件中的第一个数字取出来,构建一颗最小竞赛树来进行初始化;
2.将树的头节点中所对应的值找到放到文件中,找到该编号所对应的文件号,然后将相同文件的下一个元素赋给该编号所对应的值;
3.直到取得的编号所对应的值为MAX为止。
下边附上外部排序的代码(其中对于文件的处理部分参考其他前辈的博客)
#include<iostream>
#include<string>
#include<ctime>
#include<cstring>
#include<algorithm>
#include<fstream>
#include<cassert>
#define MIN -1 //这里开始的时候出现了一个BUG,如果定义的MIN大于等于待排序的数,则会是算法出现错误
#define MAX 10000000 //最大值,附加在归并文件结尾
#define random(a,b) (rand()%(b-a+1)+a)
using namespace std;
const unsigned int count1 = 5000;
const unsigned int number_to_sort = 1000; //在内存中一次排序的数量
const char* unsort_file = "unsort_data.txt"; //原始未排序的文件名
const char* sort_file = "sort_data.txt";//已排序的文件名
class illegalParameterValue
{
public:
illegalParameterValue(string theMessage = "Illegal parameter value")
{message = theMessage;}
void outputMessage() {cout << message << endl;}
private:
string message;
};//异常处理
template<class T>
class winnerTree
{
public:
virtual ~winnerTree() {}
virtual void initialize(T *thePlayer, int theNumberOfPlayers) = 0;//初始化
virtual int winner() const = 0;//返回最小赢者树的最小值
virtual void rePlay(int thePLayer) = 0;//赢者树的重构
};
template<class T>
class completeWinnerTree : public winnerTree<T>
{
public:
completeWinnerTree(T *thePlayer, int theNumberOfPlayers)
{
tree = NULL;
initialize(thePlayer, theNumberOfPlayers);//构造函数
}
~completeWinnerTree() {delete [] tree;}
void initialize(T*, int);//初始化
void rePlay(int thePLayer);
int winner() const
{return tree[1];}//返回赢者的索引
int winner(int i) const
{return (i < numberOfPlayers) ? tree[i] : 0;}
void output() const;
int number()
{
return numberOfPlayers;
}
private:
int lowExt; //最底层的外部节点
int k;//归并段
int offset; // 2^log(n-1) - 1
int *tree; // 用于存储内部节点的数组
int numberOfPlayers; //外部节点个数
T *player; // 用于存储外部节点的数组
void play(int, int, int);
};
template<class T>
void completeWinnerTree<T>::play(int p, int leftChild, int rightChild)//根据leftchild队和rightchild队成绩确定内部节点p的大小
{//最小赢者树
tree[p] = (player[leftChild] <= player[rightChild]) ?
leftChild : rightChild;
while (p % 2 == 1 && p > 1)
{
tree[p / 2] = (player[tree[p - 1]] <= player[tree[p]]) ?
tree[p - 1] : tree[p];
p /= 2; //p是内部节点的右孩子,需要确定该内部节点的父节点
}
}
template<class T>
void completeWinnerTree<T>::initialize(T *thePlayer,
int theNumberOfPlayers)
{//创建赢者树的外部节点
int n = theNumberOfPlayers;
if (n < 2)
throw illegalParameterValue("must have at least 2 players");
player = thePlayer;
numberOfPlayers = n;
delete [] tree;
tree = new int [n];//重新构件内部节点
//估算s = 2^log (n-1)
int i, s;//s表示最底层最左端的内部节点
for (s = 1; 2 * s <= n - 1; s += s);
lowExt = 2 * (n - s);//(n-s)表示最底层内部节点的个数,lowExt表示最底层外部节点的个数
offset = 2 * s - 1;
for (i = 2; i <= lowExt; i += 2)//找最底层外部节点的右孩子
play((offset + i) / 2, i - 1, i);//创建最底层外部节点的内部节点
if (n % 2 == 1)//如果外部节点有奇数个,则最底层最左端的内部节点和一个外部节点有相同的内部节点
{
play(n/2, tree[n - 1], lowExt + 1);
i = lowExt + 3;
}
else i = lowExt + 2;
for (; i <= n; i += 2)
play((i - lowExt + n - 1) / 2, i - 1, i);
}
template<class T>
void completeWinnerTree<T>::rePlay(int thePlayer)
{// 最小赢者树的重构
int n = numberOfPlayers;
if (thePlayer <= 0 || thePlayer > n)
throw illegalParameterValue("Player index is illegal");
int matchNode, //寻找赢者的内部节点的位置
leftChild,
rightChild;
if (thePlayer <= lowExt)//最底层开始
{
matchNode = (offset + thePlayer) / 2;
leftChild = 2 * matchNode - offset;
rightChild = leftChild + 1;
}
else
{
matchNode = (thePlayer - lowExt + n - 1) / 2;
if (2 * matchNode == n - 1)
{
leftChild = tree[2 * matchNode];
rightChild = thePlayer;
}
else
{
leftChild = 2 * matchNode - n + 1 + lowExt;
rightChild = leftChild + 1;
}
}
tree[matchNode] = (player[leftChild] <= player[rightChild])//先给最下边需要改变的内部节点进行赋值
? leftChild : rightChild;
if (matchNode == n - 1 && n % 2 == 1)//如果内部节点的孩子是由一个内部节点和一个外部节点构成
{
matchNode /= 2;
tree[matchNode] = (player[tree[n - 1]] <=
player[lowExt + 1]) ?
tree[n - 1] : lowExt + 1;
}
matchNode /= 2;
for (; matchNode >= 1; matchNode /= 2)//改变该条路径上的所有内部节点
tree[matchNode] = (player[tree[2 * matchNode]] <=
player[tree[2 * matchNode + 1]]) ?
tree[2 * matchNode] : tree[2 * matchNode + 1];
}
int read_data(FILE* f, int a[], int n)
{
int i=0;
while(i<n&&(fscanf(f, "%d", &a[i]) != EOF))
i++;
return i;
}
void write_data(FILE* f, int a[], int n)
{
for (int i = 0; i < n; ++i)
fprintf(f, "%d ", a[i]);
}
char* temp_filename(int index)
{
char *tempfile = new char[100];
sprintf(tempfile, "temp%d.txt", index);
return tempfile;
}
struct node
{
int key;
int element;
operator int()const
{
return element;
}
};
int getshunchuancount()
{
int *array=new int[number_to_sort];
int *arrays=new int[number_to_sort];
int count=0;
FILE* fin = fopen(unsort_file, "rt");
int n=0;
while ((n = read_data(fin, array,number_to_sort)) > 0)
{
int t=0;
completeWinnerTree<int> *w=new completeWinnerTree<int> (array,n);
for(int i=0;i<n;i++)
{
int index=w->winner();
arrays[i]=array[index];
array[index]=MAX;
w->rePlay(index);
}
char* fileName = temp_filename(count);
FILE* tempFile = fopen(fileName, "w");
free(fileName);
write_data(tempFile, arrays, n);
fclose(tempFile);
count++;
}
delete[] array;
delete[] arrays;
fclose(fin);
return count;
}
void merge()
{
FILE* fout=fopen(sort_file,"wt");
int k=getshunchuancount();
FILE **arrayf=new FILE*[k];
node *array=new node[k];
int b[k];
for(int i=0;i<k;i++)
b[i]=i;
int j=0;
for(int i=0;i<k;i++)
{
char *filename=temp_filename(i);
arrayf[i]=fopen(filename,"rt");
free(filename);
}
for(int i=0;i<k;i++)//得到k路归并中的每个文件中的最小值
{
fscanf(arrayf[i],"%d",&array[i].element);
array[j++].key=i;
if(fscanf(arrayf[i],"%d",&array[i].element)==EOF)
return;
}
completeWinnerTree<node> *w=new completeWinnerTree<node>(array,k);
int location;
int ans=-1;
while(ans!=MAX)
{
int next;
location=w->winner();
ans=array[location].element;//找到该编号所对应的值
fprintf(fout, "%d ", ans);
int weizhi=array[location].key;// 找到该编号所对应得文件号
fscanf(arrayf[weizhi],"%d",&next);//将该文件的开头元素赋给next变量
array[location].element=next;//改变最小竞赛树的该编号所对应的值
w->rePlay(location);//重构
}
for(int i=0;i<k;i++)
fclose(arrayf[i]);
delete[] arrayf;
fclose(fout);
}
int main()
{
srand((unsigned)time(NULL));
FILE* f = fopen(unsort_file, "wt");
for (int i = 0; i < 5000; i++)
fprintf(f, "%d ", random(1,1000));
fclose(f);
time_t start =clock();// time(NULL);
int k = getshunchuancount(); //将文件内容分块在内存中排序,并写入临时文件
merge();
time_t end = clock();//time(NULL);
cout << "total time:" <<(double)(end - start)/ CLOCKS_PER_SEC << endl;
}