归并排序的外部排序算法实现

Posted 4nc414g0n

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了归并排序的外部排序算法实现相关的知识,希望对你有一定的参考价值。

外部排序概念

参考一眨眼的功夫了解什么是外部排序算法


按照内存大小,将大文件分成若干长度适当(小于内存可使用的大小)的子文件,再使用内部排序算法(快排,堆排…)对单个子文件进行排序,并将这些子文件写入辅助存储器。最后对这些子文件进行合并,直到得到一个有序的文件

场景

当待排序的文件比内存的可使用容量还大时,文件无法一次性放到内存中进行排序,需要借助于辅助存储器时,就需要用到外部排序


外排效率取决于读写辅助存储器的次数而归并的次数就是访问辅助存储器的次数


n路平衡归并(n为每次归并的临时子文件个数)
下图为4路平衡归并

拓展

所以为提高效率(我们可以根据实际情况)

  1. 增加子文件的大小以减少子文件的个数
    参考置换选择排序算法
  2. 减小归并次数
    参考多路平衡归并排序算法(多路归并排序、胜者树、败者树)
  3. 参考最佳归并树

实现函数

这里我们准备100个随机数字模拟大文件,并将其拆分为10个文件使用如下图方式对其进行操作


归并外排序主函数

注意点

  1. 文件操作函数的具体使用方式(返回值,参数等特性)
  2. 每次对文件写入10个数控制if条件i < n-1而不是i<n(在else中进行最后一个数的写入),防止每次多读取一个数,导致最后一个子文件未能成功创建
  3. 对文件的命名如sprintf(mfile, "%s%d", mfile, i+1)每次是把mfile的名称i+1结合起来,例如mfile原来是sub_sort0,i为0,新的mfile为sub_sort01,但是如果以多余的字符命名会出现sub_sortsub_sort012的情况
//归并外排序主函数
void MergeSortFile(const char* file)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		printf("fail main_fout");
		exit(-1);
	}
	//
	int n = 10;//每个文件n个数
	int a[10];//分为10个文件夹
	int i = 0;
	int num = 0;//用来存放每次读取的数值(假设有100个)
	char subfile[20];//快排完写入的文件名
	int filei = 0;//用来创建文本文件命名 如:sub_sort1, sub_sort2, sub_sort3...(中的1,2,3...)

	//memset(a, 0, sizeof(int)*n);
	while (fscanf(fout, "%d\\n", &num) != EOF)//从输入流fout中读入数据,存储到num中
	{
		if (i < n-1)//每个文件10个数(i<9)防止i加到10
		{
			a[i++] = num;//最后一次i=8赋值后i加到9
		}
		else
		{
			a[i] = num;//每个文件10个数(i<9)防止i加到10
			
			PartSort3_right(a, 0, n - 1);
			//对每次分到的10个元素排序(快排前后指针法,key在最右边)
			sprintf(subfile, "%d", filei++);
			//sprintf 按 sub\\\\sub_sort%d 此格式  
			//(\\\\是盘符后面的\\ 有两根是转义字符,防止识别不了)写入subfile
			FILE* fin = fopen(subfile, "w");
			if (fin == NULL)
			{
				printf("fail fin_main\\n");
				exit(-1);
			}
			for (int i = 0; i < n; i++)
			{
				fprintf(fin, "%d\\n", a[i]);
				//排完序写入此文件
			}
			fclose(fin);
			i = 0;//i置为0 准备写到下一个文件
			//memset(a, 0, sizeof(int)*n);
		}
	}
	//以上为拆分文件并分别快排
	
	//以下利用互相归并到文件,实现整体有序
	char mfile[100] = "01";
	char file0[100] = "0";
	char file1[100];
	for (int i = 1; i < n; i++)
	{
		sprintf(file1, "%d", i);
		_MergeFile(file0, file1, mfile);
		strcpy(file0, mfile);
		sprintf(mfile, "%s%d", mfile, i+1);
		//注意txt问题和sub\\\\sub_sort的坑
	}
	fclose(fout);
}

将传入两个文件归并入mfile

注意点

  1. 第一个循环不能按照普通的归并逻辑,因为fscanf相当于自动++,while (fscanf(fout1, "%d", &num1) != EOF && fscanf(fout2,"%d",&num2) != EOF) 会导致在写入小的一组后大的那一组也会++,读丢元素
  2. 如果后面的两个while循环直接while (fscanf(fout1, “%d”, &num1) != EOF),会导致写入的合并文件最后少一个,因为当倒数第二个数被写入后fscanf会马上读取接下来的最后一个数,马上此文件在为空了,然而在while里直接用fscanf的话就是EOF,剩下的最后一个数不会被写入合并文件
//传两个文件进行归并
void _MergeFile(const char* file1, const char* file2, const char* mfile)
{
	int num1 = 0;
	int num2 = 0;
	FILE *fout1 = fopen(file1, "r");
	if (fout1 == NULL)
	{
		printf("failed to open fout1\\n");
		exit(-1);
	}
	FILE *fout2 = fopen(file2, "r");
	if (fout2 == NULL)
	{
		printf("failed to open fout2\\n");
		exit(-1);
	}
	FILE *fin= fopen(mfile, "w");
	if (fin == NULL)
	{
		printf("failed to open fin\\n");
		exit(-1);
	}

	//这里不能按照普通的归并逻辑,因为fscanf相当于自动++,
	//while (fscanf(fout1, "%d", &num1) != EOF && fscanf(fout2,"%d",&num2) != EOF) 会导致在写入小的一组后大的那一组也会++,读丢元素
	int ret1 = fscanf(fout1, "%d", &num1);
	int ret2 = fscanf(fout2,"%d",&num2);
	while (ret1 != EOF && ret2 != EOF)
	{
		if (num1 < num2)
		{
			fprintf(fin, "%d\\n", num1);
			ret1 = fscanf(fout1, "%d", &num1);
		}
		else
		{
			fprintf(fin, "%d\\n", num2);
			ret2 = fscanf(fout2, "%d", &num2);
		}
	}
	//注意:如果这里直接while (fscanf(fout1, "%d", &num1) != EOF),会导致写入的合并文件最后少一个,
	//因为当倒数第二个数被写入后fscanf会马上读取接下来的最后一个数,马上此文件在为空了,
	//然而在while里直接用fscanf的话就是EOF,剩下的最后一个数不会被写入合并文件
	while (ret1 != EOF)
	{
		fprintf(fin, "%d\\n", num1);
		ret1 = fscanf(fout1, "%d", &num1);
	}
	while (ret2 != EOF)
	{
		fprintf(fin, "%d\\n", num2);
		ret2 = fscanf(fout2, "%d", &num2);
	}
	fclose(fout1);
	fclose(fout2);
	fclose(fin);
}

完成外排序

以上是关于归并排序的外部排序算法实现的主要内容,如果未能解决你的问题,请参考以下文章

10分钟看懂10大经典算法(Swift代码实现)

排序之归并排序的算法思想及实现代码

(附代码)动图图解 | 十大经典排序算法Python版实现

排序算法之归并排序

[ 数据结构 -- 手撕排序算法第六篇 ] 归并排序(上)--递归方法实现

PHP实现归并排序算法(代码实例)