外排序和内排序是两种截然不同的排序算法,因为外排序是对文件进行排序,通常文件很大我们不能一次性读入内存。因此我们需要使用外排序。外排序常用的算法是多路归并排序。多路归并排序是指把文件分成n个已经排好序的文件,然后对每个子文件同时读入,选出当前指针最小的值输出,然后该指针后移,直到处理完所有子文件。
现在我们模拟外排序,内存使用最多1M,对1 ~ 10000000个数进行外排序。
首先是数据生成部分:
#include <iostream>
#include <time.h>
#include <assert.h>
using namespace std;
const int size = 10000000;
int num[size];
int main() {
int n;
FILE *fp = fopen("data.txt", "w");
assert(fp);
for(n = 1;n <= size;n++) {
num[n] = n;
}
srand((unsigned)time(NULL));
int i, j;
for(n = 0;n < size;n++) {
i = (rand() * RAND_MAX + rand()) % 10000000;
j = (rand() * RAND_MAX + rand()) % 10000000;
swap(num[i], num[j]);
}
for(n = 0;n < size;n++) {
fprintf(fp, "%d ", num[n]);
}
fclose(fp);
return 0;
}
接下来是排序部分,排序的过程分成两部分:切分文件和归并文件。切分文件的原则是将文件切分成内存可以处理的小部分,然后一部分一部分处理并输出。归并文件比较容易思考的是二分归并,但是这样IO操作太多,一般我们可以在内存满足的情况下读入尽可能多的文件处理。
切分文件部分如下:
int sort_num = 10000000;
int memory_size = 250000;
int read_data(FILE *fp, int *space) {
int index = 0;
while (index < memory_size && fscanf(fp, "%d ", &space[index]) != EOF)
index++;
return index;
}
void check_fp(FILE *fp) {
if(fp == NULL) {
cout << "the file pointer is invalid" << endl;
exit(1);
}
}
int compare(const void *first_num, const void *second_num) {
return *(int *)first_num - *(int *)second_num;
}
string new_file_name(int n) {
string file_name = "data" + to_string(n);
return file_name;
}
void write_data(FILE *fp, int *space, int num) {
for(int i = 0;i < num;i++) {
fprintf(fp, "%d " , space[i]);
}
}
int memory_sort() {
FILE *fp_in_file = fopen("data.txt", "r");
check_fp(fp_in_file);
int counter = 0;
while(true) {
int *space = new int[memory_size];
int num = read_data(fp_in_file, space);
if (num == 0) {
break;
}
qsort(space, num, sizeof(int), compare);
string file_name = new_file_name(++counter);
FILE *fp_aux_file = fopen(file_name.c_str(), "w");
check_fp(fp_aux_file);
write_data(fp_aux_file, space, num);
fclose(fp_aux_file);
delete []space;
}
fclose(fp_in_file);
return counter;
}
归并排序代码如下:
void merge_sort(int file_num) {
if (file_num <= 0) {
return;
}
FILE *fp_out_file = fopen("result.txt", "w");
check_fp(fp_out_file);
FILE **fp_array = new FILE *[file_num];
int i;
for(i = 0;i < file_num;i++) {
string file_name = new_file_name(i + 1);
fp_array[i] = fopen(file_name.c_str(), "r");
check_fp(fp_array[i]);
}
int *first_data = new int[file_num];
bool *finish = new bool[file_num];
memset(finish, false, sizeof(bool) * file_num);
for(i = 0;i < file_num;i++) {
fscanf(fp_array[i], "%d ", &first_data[i]);
}
while(true) {
int index = 0;
while(index < file_num && finish[index] ) {
index++;
}
if (index >= file_num) {
break;
}
int min_data = first_data[index];
for (i = index + 1;i < file_num;i++) {
if (min_data > first_data[i] && !finish[i]) {
min_data = first_data[i];
index = i;
}
}
fprintf(fp_out_file, "%d ", min_data);
if (fscanf(fp_array[index], "%d ", &first_data[index]) == EOF) {
finish[index] = true;
}
}
fclose(fp_out_file);
delete []finish;
delete []first_data;
for(int i = 0;i < file_num;i++) {
fclose(fp_array[i]);
}
delete []fp_array;
}