文章目录
-
-
- 外排序(外部排序)
-
- 文件拆分并排序
- 归并文件
-
- 两个文件归并
- 多文件归并
- 优化
-
萌新的学习笔记,写错了恳请斧正。
外排序(外部排序)
当数据量非常庞大以至于无法全部写入内存时,我们应该怎么排序这些数据呢?
这时,就要学会直接进行在外存中排序,也就是外部排序。
外排序不是一种独立的排序算法,还是要依赖于之前的那些排序方式。
其基本思想是这样的:
- 将庞大的数据拆分为一个一个子文件,使得每一个子文件的数据量在内存排序可承受的范围内。
- 将每一个文件进行正常的排序,可以选用之前学的任何排序方法。
- 将排序好的文件再进行归并,最终合成为包含有序的所有数据的文件。
看起来不是很复杂,但是归并部分还是很有东西的。下面我们逐步讲解:
文件拆分并排序
这并不复杂,比方说我们将文件拆分为splitCount个:
void FileSort(const char* filename, int splitCount)
{
clock_t start = clock();
//分割文件
FILE* fp = fopen(filename, "r");
if (fp == NULL)
{
perror("fail to open");
exit(EXIT_FAILURE);
}
int* num = (int*)malloc(sizeof(int) * N / splitCount); //存放每个分割文件的数据
int iNum = 0; //num数组的下标
int iFile = 0; //分割文件的下标
int n = 0; //读取的数据
char splitFileName[50] = { 0 }; //分割文件名
while (fscanf(fp, "%d", &n) != EOF)
{
if (iNum N / splitCount - 1)
{
num[iNum++] = n;
}
else
{
num[iNum] = n;
//排序
HeapSort(num, N / splitCount);
//写入文件
sprintf(splitFileName, "SubSortsort_split%d.txt", iFile++);
FILE* splitFile = fopen(splitFileName, "w");
if (splitFile == NULL)
{
perror("fail to open");
exit(EXIT_FAILURE);
}
for (int i = 0; i N / splitCount; i++)
{
fprintf(splitFile, "%dn", num[i]);
}
fclose(splitFile);
printf("已分割第%d个文件,总共需分割%d个文件。n", iFile, splitCount);
iNum = 0;
}
}
//归并文件
//这里插入归并文件的代码
//关闭文件
fclose(fp);
clock_t end = clock();
printf("排序完成,共%d个数据,分割为%d个文件,归并为1个文件。n", N, splitCount);
printf("耗时:%f秒。n", (double)(end - start) / CLOCKS_PER_SEC);
}
归并文件
这是重点也是最复杂的地方。
两个文件归并
我们知道两个排序文件归并的方法:两边一起读取,一直对比两边读取的数,不断取小写入输出文件即可。
void MergeFile(char* file1, char* file2, char* mergeFileName)
{
FILE* fp1 = fopen(file1, "r");
FILE* fp2 = fopen(file2, "r");
if (fp1 == NULL)
{
perror("fail to open");
exit(EXIT_FAILURE);
}
if (fp2 == NULL)
{
perror("fail to open");
exit(EXIT_FAILURE);
}
FILE* fin = fopen("tmp.txt", "w");
if (fin == NULL)
{
perror("fail to open");
exit(EXIT_FAILURE);
}
int n1, n2;
int ret1 = fscanf(fp1, "%d", &n1);
int ret2 = fscanf(fp2, "%d", &n2);
while (ret1 != EOF && ret2 != EOF)
{
if (n1 n2)
{
fprintf(fin, "%dn", n1);
ret1 = fscanf(fp1, "%d", &n1);
}
else
{
fprintf(fin, "%dn", n2);
ret2 = fscanf(fp2, "%d", &n2);
}
}
while (fscanf(fp1, "%d", &n1) != EOF)
{
fprintf(fin, "%dn", n1);
}
while (fscanf(fp2, "%d", &n2) != EOF)
{
fprintf(fin, "%dn", n2);
}
fclose(fp1);
fclose(fp2);
fclo服务器托管se(fin);
remove(file1);
remove(file2);
rename("tmp.txt", mergeFileName);
}
注意:这里我们先写入数据至tmp.txt中,最后在将其改名为mergeFileName是为了防止mergeFileName就是file1或者file2的情况(也即直接把file2归并到file1中而不创建新的文件,或者反过来)。
多文件归并
但是我们往往不仅仅要把数据切分为两个,而是几十个上百个。这时我们就需要使用多文件归并的方法。
-
直接逐个归并
这是最容易理解也是最差的方法。就是把拆分文件1和拆分文件2归并,结果再和拆分文件3归并,结果再和拆分文件4归并……最终得到完整的归并文件。
傻子都能看出来这个方法的不靠谱,但是我们还是将代码贴出来:
//归并文件,这段代码填入上方FileSort函数中归并的留空位置 char* mergeFileName = "sort_merge.txt"; char file1[50] = "SubSortsort_split0.txt", file2[50] = {0}; for (int i = 1; i splitCount; i++) { sprintf(file2, "SubSortsort_split%d.txt", i); MergeFile(file1, file2, mergeFileName); strcpy(file1, mergeFileName); printf("已归并%d个文件,总共需归并%d个文件。n", i + 1, splitCount); }
这个方法是真的不靠谱,一亿个数据半小时还没归并完。
-
分治归并
就是不断地两两分组归并,比方说9至16个文件归并成5至8个,5至8个归并位3至4个,再归并为2个,最后合成完整的排序后文件。
//分治归并文件,这段代码填入上方FileSort函数中归并的留空位置 MergeFileR(0, splitCount - 1); remove("SubSortsort_split0.txt");
其中MergeFileR的定义如下:
void MergeFileR(int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; //递归归并 MergeFileR(left, mid); MergeFileR(mid + 1, right); //归并 char file1[50] = { 0 }, file2[50] = { 0 }, mergeFileName[50] = { 0 }; sprintf(file1, "SubSortsort_split%d.txt", left); sprintf(file2, "SubSortsort_split%d.txt", mid + 1); sprintf(mergeFileName, "SubSortsort_split%d.txt", left); MergeFile(file1, file2, mergeFileName); printf("已归并%d与%d文件至%d。n", left, mid + 1, left); }
这个方法要靠谱一点,测试如下:
优化
进一步优化可以将基础操作从两个文件归并变成3个文件归并或者更多,这样分治时就是3合一或者更多。这样多路归并会增加内存开销和内部时间开销,但是会大大减少外部读写的开销。
更进一步优化可以引入败者树、最佳归并树等,我还不会。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
网络爬虫(Web Crawler),又称网络蜘蛛(Web Spider)或网络机器人(Web Robot),是一种按照一定的规则自动抓取万维网资源的程序或者脚本,已被广泛应用于互联网领域。搜索引擎使用网络爬虫抓取Web网页、文档甚至图片、音频、视频等资源,通过…