提示:算法实践实验
文章目录
一、问题描述
利用快排实现外部排序
二、算法思想
1.将存储划分为四个部分,首先从disk中读取数据写满middle
2.接下来的数据读满input,依次与middle中数据比较,若大于middle中最大值放入large,若小于middle中最小值放入small,若处于中间值,将middle中最小值放入small
3.若small或large写满则写回disk
4.全部读取并处理完成后将small,middle,large中剩余数据全部写回disk中
5.此时middle中写回的数据已经有序,再递归地处理两侧的数据直至全部处理完成
三、函数设计
Middle
由于middle中要取最大值或最小值与读入数据进行比较,因此,采用最大最小堆,可以方便的得出所需值,用vector数组来存储其中的数据,方便增删,不受大小影响
int min();//取最大值
int max();//取最小值
void insert(int val);//插入新值
void delete_min();//删除最小值
void delete_max();//删除最大值
(其余函数请见最后部分的详细代码)
Disk
Cache
首先考虑input,large,small,temp,temp2均采用cache结构体,其中包含的内容有:
int cache[CACHE_SIZE];
int size=CACHE_SIZE;//指可以存的最大长度
int diskStart=0;//对应磁盘的开始位置
int diskEnd=0;
int startPoint=0;
int endPoint=-1;//用来计算cache中存了多少数据
实现函数
(1)关键问题
Q1:disk中数据如何存储
使用txt文件进行存储,每个数字占用一行,便于读取和修改。
Q2:small和large怎么写回?
使用int型变量diskStart和diskEnd记录所对应的在磁盘上的位置,写回时从diskStart所对应行开始写入即可。
Q3:large写回时覆盖未读取的数据
因此引入了temp,大小与large相同,当将large写回时,若所对应处的数据还未读取入input,则将其先存储在temp中,同时,每次处理数据时先处理temp,再处理input。
Q4:处理temp中数据时large再次需要写回
因此引入了temp2,此情况发生时先将数据存入temp2中,处理数据顺序为temp,temp2,input,由于large与temp大小相同,因此不会再出现两个temp都满了又覆盖数据的情况。
Q5:由于覆盖提前处理的数据再次读入input
因此引入了end,代表数据读取的结束位置,每次large写入,则前移写入数据的长度,防止再次读取。
Q6:cache采用数组只能覆盖数据不能删除,如何防止数据被重复处理
每次处理后将endpoint设置为-1,代表无将要处理的数据。
(2)函数及注释
class Disk
{
friend class min_max_heap;
public:
Disk();
void adddiskIOCount();
void readToInputCache(int start,int length);//从disk读到inputcache中
void readToTempCache(Cache&tempCache,int start,int end);//从disk读到temp中
void readFromInputCache(Cache&tempCache,int cur);//把input中的数据读进去
void readToMiddle(int start,int length);//从disk直接读到middle中
void writeFromCache(Cache&smallCache,int start,bool isSmall);//smallcache中数据写回disk
void writeFromLargeCache(int end,int cur);//largecache中数据写回disk
void writeFromMiddle(min_max_heap middleCache,int start,int end);//把middle里的写回
void quickSort(int start,int end);
std::string charToStr(char*contentChar);
int getdiskIOCount();
private:
Cache inputCache;
Cache smallCache;
Cache largeCache;
Cache tempCache;//用于存放后面被占去位置的数据
Cache tempCache2;
min_max_heap middleCache;
int diskIOCount=0;
};
四、测试结果和分析
有序/无序数据
有序
递减:
所得:
递增:
所得:
无序(随机生成)
所得:
大/小数据量
小数据
大数据
不同存储空间分配
较小的Cache
较大的Cache
五、项目总结
通过对结果的分析可以得到和内部快排相似的结论,有序的数据反而会耗费更多的io次数与更多时间,不适合使用快排。而随着数据量的增大,io次数也是呈爆炸式增长,我们可以得知对于磁盘上的数据处理,限制不再是cpu而是io次数。
对于整个实验过程,深深让我体会到不积跬步无以至千里的深刻道理,一开始没有在意边界问题,导致end指针究竟是指向最后一个数字还是最后一个数字后面在各个函数中没有统一,多个函数在递归中的反复调用造成了更多的bug,修改过程中也是极为痛苦的,看似没有大问题,实则处处是问题,在一次次的实验中失去了脾气,不知道测试了多少遍、打了多少断点,终于完成了这个实验。
思路上有参考:学长的文章
六、代码参考
Disk中关键函数实现:
#include "Disk.hpp"
#include<iostream>
#include<fstream>
#include<cstdlib>
#include<ctime>
#include<algorithm>
using namespace std;
Disk::Disk()
{
ofstream outfile;
outfile.open("quicksort.txt");
srand(time(NULL));
for(int i=0;i<DISK_SIZE;i++)
outfile<<rand()%1000<<endl;
outfile.close();
}
int Disk::getdiskIOCount()
{
return diskIOCount;
}
void Disk::adddiskIOCount()
{
diskIOCount++;
}
void Disk::readToInputCache(int start,int length)
{
adddiskIOCount();
ifstream infile;
infile.open("quicksort.txt");
char buf[1024];
for(int i=0;i<start;i++)
infile.getline(buf,sizeof(buf));
for(int i=0;i<length;i++)
{
infile>>inputCache.cache[i];
}
inputCache.diskStart=start;
inputCache.diskEnd=start+length-1;
inputCache.endPoint=length-1;
infile.close();
}
void Disk::readToTempCache(Cache&tempCache,int start, int end)
{
adddiskIOCount();
ifstream infile;
infile.open("quicksort.txt");
char buf[1024];
for(int i=0;i<start;i++)
infile.getline(buf,sizeof(buf));
for(int i=0;i<=end-start;i++)
{
infile>>tempCache.cache[i];
}
tempCache.diskStart=start;
tempCache.diskEnd=end;
tempCache.endPoint=end-start;
infile.close();
}
void Disk::readFromInputCache(Cache&tempCache,int cur)
{
for(int i=tempCache.startPoint;i<=tempCache.endPoint;i++)
{
if(tempCache.cache[i]<=middleCache.min())
{
smallCache.endPoint++;
if(smallCache.endPoint==smallCache.size)
{
writeFromCache(smallCache,smallCache.diskStart,true);
smallCache.endPoint=0;
smallCache.diskStart+=smallCache.size;
smallCache.diskEnd=smallCache.diskStart;
}
smallCache.cache[smallCache.endPoint]=tempCache.cache[i];
smallCache.diskEnd=smallCache.diskStart+smallCache.endPoint;
}
else if (tempCache.cache[i]>=middleCache.max())
{
largeCache.endPoint++;
if(largeCache.endPoint==largeCache.size)
{
writeFromLargeCache(largeCache.diskEnd,cur);
largeCache.endPoint=0;
largeCache.diskEnd-=largeCache.size;
largeCache.diskStart=largeCache.diskEnd;
}
largeCache.cache[largeCache.endPoint]=tempCache.cache[i];
largeCache.diskStart=largeCache.diskEnd-largeCache.endPoint;
}
else{
int small=middleCache.min();
middleCache.delete_min();
middleCache.insert(tempCache.cache[i]);
smallCache.endPoint++;
if(smallCache.endPoint==smallCache.size)
{
writeFromCache(smallCache,smallCache.diskStart,true);
smallCache.endPoint=0;
smallCache.diskStart+=smallCache.size;
smallCache.diskEnd=smallCache.diskStart;
}
smallCache.cache[smallCache.endPoint]=small;
// int large=middleCache.max();
// middleCache.delete_max();
// middleCache.insert(tempCache.cache[i]);
// largeCache.endPoint++;
// if(largeCache.endPoint==largeCache.size)
// {
// writeFromLargeCache(largeCache.diskEnd,cur);
// largeCache.endPoint=0;
// largeCache.diskEnd-=largeCache.size;
// largeCache.diskStart=largeCache.diskEnd;
// }
// largeCache.cache[largeCache.endPoint]=large;
}
}
tempCache.endPoint=-1;
}
void Disk::readToMiddle(int start,int length)
{
adddiskIOCount();
ifstream infile;
infile.open("quicksort.txt");
char buf[1024];
for(int i=0;i<start;i++)
infile.getline(buf,sizeof(buf));
for(int i=0;i<length;i++)
{
string yyh;
getline(infile,yyh);
middleCache.insert(atoi(yyh.c_str()));
}
infile.close();
middleCache.diskStart=start;
middleCache.diskEnd=start+length-1;
}
void Disk::writeFromCache(Cache&smallCache,int start,bool isSmall)
{
adddiskIOCount();
ifstream infile;
char buf[1024];
infile.open("quicksort.txt");
int i=0;
string tempStr;
while(infile.getline(buf,sizeof(buf)))
{
if(i!=start)
{
// cout<<buf<<endl;
tempStr+=charToStr(buf)+'\n';
i++;
// cout<<i<<":"<<tempStr;
}
else{
for(int j=0;j<smallCache.endPoint-1;j++)
{
string omg=to_string(smallCache.cache[j]);
tempStr+=omg+'\n';
// cout<<i+j<<":"<<tempStr<<endl;
infile.getline(buf,sizeof(buf));
}
string omg=to_string(smallCache.cache[smallCache.endPoint-1]);
tempStr+=omg+'\n';
i+=smallCache.endPoint;
}
}
infile.close();
// cout<<tempStr;
ofstream outfile;
outfile.open("quicksort.txt");
outfile<<tempStr;
outfile.close();
if(isSmall)
{
middleCache.diskStart+=smallCache.endPoint;
middleCache.diskEnd+=smallCache.endPoint;
}
smallCache.endPoint=-1;
}
string Disk::charToStr(char*contentChar)
{
string tempStr;
for(int i=0;contentChar[i]!='\0';i++)
tempStr+=contentChar[i];
return tempStr;
}
void Disk::writeFromLargeCache(int end,int cur)
{
int start=end-largeCache.endPoint+1;
if(cur<start)
{
if(tempCache.endPoint==tempCache.size-1)
readToTempCache(tempCache2, start, end);
else readToTempCache(tempCache,start, end);
}
else{
if(tempCache.endPoint==tempCache.size-1)
readToTempCache(tempCache2, cur, end);
else readToTempCache(tempCache,cur, end);
// readToTempCache(cur, end);
}
writeFromCache(largeCache, start,false);
}
void Disk::writeFromMiddle(min_max_heap middleCache, int start, int end)
{
adddiskIOCount();
ifstream infile;
char buf[1024];
infile.open("quicksort.txt");
int i=0;
string tempStr;
while(infile.getline(buf,sizeof(buf)))
{
if(i!=start)
{
tempStr+=charToStr(buf)+'\n';
i++;
}
else{
for(int j=start;j<end;j++)
{
string omg=to_string(middleCache.min());
tempStr+=omg+'\n';
middleCache.delete_min();
infile.getline(buf,sizeof(buf));
}
string omg=to_string(middleCache.min());
tempStr+=omg+'\n';
middleCache.delete_min();
i+=end-start+1;
}
}
infile.close();
ofstream outfile;
outfile.open("quicksort.txt");
outfile<<tempStr;
outfile.close();
}
void Disk::quickSort(int start,int end)
{
int cur=start;
int end2=end;
smallCache.diskStart=start;
smallCache.diskEnd=start;
largeCache.diskStart=end;
largeCache.diskEnd=end;
// middleCache.diskStart=(end-start+1-HEAP_SIZE)/2;
// middleCache.diskEnd=(end-start+1+HEAP_SIZE)/2;不对,不一定在中间
readToMiddle(cur, min(end-start+1,HEAP_SIZE));
cur+=min(end-start+1,HEAP_SIZE);
while(cur<=end)
{
readToInputCache(cur,min(end-start+1,CACHE_SIZE));
//需要考虑如果到middle就读满了怎么办
if(tempCache.endPoint!=-1)
{
end-=tempCache.endPoint+1;
readFromInputCache(tempCache,cur);
tempCache.endPoint=-1;
}
if(tempCache2.endPoint!=-1)
{
end-=tempCache2.endPoint+1;
readFromInputCache(tempCache2,cur);
tempCache2.endPoint=-1;
}
if(cur<=end)
readFromInputCache(inputCache,cur);
cur+=min(end-start,CACHE_SIZE);
}
if(smallCache.endPoint>=0)
{
smallCache.endPoint++;
writeFromCache(smallCache,smallCache.diskStart,true);//把small里的写回disk
// smallCache.endPoint=-1;
}
writeFromMiddle(middleCache, middleCache.diskStart, middleCache.diskEnd);
for(int i=0;i<=middleCache.diskEnd-middleCache.diskStart;i++)
middleCache.delete_min();
if(largeCache.endPoint>=0)
{
largeCache.endPoint++;
int start=largeCache.diskEnd-largeCache.endPoint+1;
writeFromCache(largeCache, start,false);
// largeCache.endPoint=-1;
}
int a=middleCache.diskStart;
int b=middleCache.diskEnd;
if((a-start)>1)
quickSort(start, a-1);
if((end2-b)>1)
quickSort(b+1, end2);
}