排序扩展-文件递归排序(外排序)
外排序是能够处理极大量数据的排序算法,简单来说,外排序处理的数据就是不能一次性装入内存,只能放在读写较慢的外存储器上(一般是硬盘上),外排序通常采用的是一种“排序-递归”的策略。在排序阶段,先读入能放在内存中的数据量,将排序输出到一个临时文件,依次进行,将待排序数据组织为多个有序的临时文件,然后在归并阶段将这些临时文件组合为一个大的有序文件,即排序结果。由于归并排序不需要随机访问数据,只需要依次按序列读取数据,所以归并排序不仅使内排序还是外排序。
上面一段文字我们基本上没看懂它在说什么,用普通的话来说,就是在平时的工作中我们基本上是有很多的数据,我们需要排序,如果用平时的排序算法会由于数据存放不下而导致无法解决,这时我们就需要借助文件来进行排序。
基本思路:(1)读取n个值排序后写到file1,再读取n个值排序后写到file2;(2)file1和file2利用归并排序的思想,依次读取比较,取小的尾插到mfile,mfile归并为有序文件;(3)将file1和file2删掉,mfile重命名为file1;(4)再读取n个数据排序后写入到file2;(5)继续走file1和file2归并,重复步骤2。直至没有数据。
我们需要先有一个创建数据的函数,开始我们让数据量减少一点,100000个数据我们每一次读取10000个数据进行排序,开始生成的数据放入到data.txt中,然后再和我们之前的想法进行排序、归并等操作,所以最终代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include<stdbool.h>
#include<stddef.h>//NULL
#include<time.h>
//数据总个数
#define N 100000
//每次读取的数据个数
#define m N/10
//创建n个随机数写到文件中
void CreateNDate()
{
//造数据
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
fclose(fin);
return;
}
for (int i = 0; i < N; ++i)
{
//生成随机数
int x = rand() % 30000;
//把数据按照一行一个的方式写入到文件中
fprintf(fin, "%d\n", x);
}
//记得关闭文件
fclose(fin);
}
//排序
int compare(const void* a, const void* b)
{
return (*(int*)a - *(int*)b);
}
//读取m个数据排序
int ReadNDataSort(FILE* fout, const char* file1)
{
FILE* fin = fopen(file1, "w");
if (fin == NULL)
{
perror("fopen error");
fclose(fin);
return 0;
}
int x = 0;
int* a = (int*)malloc(sizeof(int) * m);
if (a == NULL)
{
perror("malloc error");
return 0;
}
//计数(看读取的数据是否到m个
int j = 0;
//从fout(data.txt)中读取m个数据
for (int i = 0; i < m; i++)
{
//可能读到文件结尾了
//所以不能这样写
//fscanf(fout, "%d", &x);
if (fscanf(fout, "%d", &x) == EOF)
{
break;
}
a[j++] = x;
}
if (j == 0)
{
free(a);
a = NULL;
return 0;
}
//排序
// 我们不确定是否有m个数据在文件里面,所以我们要把这个m改为j
//qsort(a, m, sizeof(int), compare);
qsort(a, j , sizeof(int), compare);
//写回file1文件
for (int i = 0; i < j; i++)
{
fprintf(fin, "%d\n", a[i]);
}
free(a);
a = NULL;
return j;
}
//把file1、file2归并到mfile中
void MergeFile(const char* file1, const char* file2, const char* mfile)
{
FILE* fout1 = fopen(file1, "r");
if (fout1 == NULL)
{
perror("file1 open error");
return;
}
FILE* fout2 = fopen(file2, "r");
if (fout2 == NULL)
{
perror("file2 open error");
return;
}
FILE* fout3 = fopen(mfile, "w");
if (fout3 == NULL)
{
perror("mfile open error");
return;
}
//归并
int x1 = 0;
int x2 = 0;
int ret1 = 0;
int ret2 = 0;
ret1 = fscanf(fout1, "%d", &x1);
ret2 = fscanf(fout2, "%d", &x2);
while (ret1 != EOF && ret2 != EOF)
{
if (x1 < x2)
{
fprintf(fout3, "%d\n", x1);
ret1 = fscanf(fout1, "%d", &x1);
}
else
{
fprintf(fout3, "%d\n", x2);
ret2 = fscanf(fout2, "%d", &x2);
}
}
while (ret1 != EOF)
{
fprintf(fout3, "%d\n", x1);
ret1 = fscanf(fout1, "%d", &x1);
}
while (ret2 != EOF)
{
fprintf(fout3, "%d\n", x2);
ret2 = fscanf(fout2, "%d", &x2);
}
fclose(fout1);
fclose(fout2);
fclose(fout3);
}
int main()
{
//造数据
CreateNDate();
const char* file1 = "file1.txt";
const char* file2 = "file2.txt";
const char* mfile = "mfile.txt";
//打开文件
FILE* fout = fopen("data.txt", "r");
if (fout == NULL)
{
perror("fopen error");
return -1;
}
//先进行排序,保证三个文件都不为空
ReadNDataSort(fout, file1);
ReadNDataSort(fout, file2);
while (1)
{
//归并
MergeFile(file1, file2, mfile);
//删除file1、file2
remove(file1);
remove(file2);
//重命名
rename(mfile, file1);
//再读文件入file2
//可能读取不完全
if (ReadNDataSort(fout, file2) == 0)
{
break;
}
}
fclose(fout);
return 0;
}
生成随机数的方式可以改为rand()+i,在我电脑上运行不出来结果,但是我看了很久都觉得代码无误,所以发出来了,最终如果生成成功的话后面你直接到这个代码的文件中去查看就可以了。
这就是全部的结果了,代码的运行结果我就没办法提供了,因为这个不是必须要掌握的,代码和结果不重要,只要知道如何实现的就可以了。
下一节将开始C++的学习了,如果感兴趣的可以继续学习哦,如果方向不一样去学习Java或者Python的也祝你们能更上一层楼吧!