(排序10)归并排序的外排序应用(文件排序)

TIPS

  1. 在一些文件操作函数当中,fputc与fgetc这两个函数都是针对字符的,如果说你需要往文件里面去放入整形啊等等,不是字符的类型,这时候就用fprintf,fscanf在参数里面数据类型控制一下就可以。但是话说回来,数据文件的话分为文本文件与二进制文件,文件里面的信息唯有二进制信息或者字符ASCII码,然后当你点开文件解析出来的时候都是强制解析成字符的。
  2. 对于C文件操作我还是想再补充几点:首先对于这两个函数fgetc,fputc他们针对的都是字符,比如说可以从内存当中往文件(以文件作为外部输出设备的例子)当中去放字符,也可以从文件当中往内存里面读出来一个字符,但无论如何,他针对的数据类型只有字符;
  3. 然后对于这两个函数fgets与fputs,他们针对的是一行字符串的输入与输出,这边尤其要注意在文件当中每一行字符串的末尾都是默认带有一个换行符\n,这个换行符的话也是需要计数的(参照fgets的参数),并且如果说条件允许也是会被拷贝到内存当中,并且文件指针一旦碰到了这个换行符,它也会换行;
  4. 对于fprintf, fscanf这两个函数来说的话,他们针对的是格式化字符串的输入输出,并且最大的优势与亮点就是说他们的类型很是自由,比如说我想往文件里面放入一个数字,然后我假设要从文件里面过一会儿读出这个数字重新到内存里面来,这时候就不好用fgetc,fputc。因为那两个的话是针对于字符的,这时候就用fprintf,fscanf加%d格式化非常nice。
  5. 最后对于这两个函数sscanf,sprintf来说,这两个函数的功能分别是从字符串当中去抠东西下来放到变量当中,粘连一些东西或者自己添油加醋一些合成一个新字符串。
    在这里插入图片描述
    在这里插入图片描述

归并排序的外排序(文件排序)

  1. 文件排序的话,主要是针对大文件,如果你针对小文件的话就没有意义了。但这边总不能真正的给500个g的数据吧,我们这边就模拟一下
  2. 现在假设有海量的排序数据,那么他这个数据肯定是在文件当中,在磁盘当中,因为磁盘相对于内存大太多了。然后如果我们要对这些数据进行排序的话,不能把他们全部一下子加载到内存里面,因为内存没有这么大
  3. 这时候我们只能用归并排序的思想,归并排序就是说比如说有两段有序的数据,这一段是有序的,那一段也是有序的。然后我们就开一个新的地方,然后每次取小的下来给他放到新的地方里面,就这样把两个有序区间给他归并到一个有序区间。好好去想一想归并的四个生动表述过程。
    在这里插入图片描述
  4. 归并排序的话,既可以把它看成内排序,它也可以搞成外排序。但无论如何他都具有O(N)的空间复杂度,所以说是蛮消耗存储空间的。所以说在内排序当中,它跟快速排序相比的话逊一点,内排序综合快排取胜
  5. 然后现在当数据在文件当中的话,前面的那些快的排序:堆排啊,快排啊都不好弄了,堆排与快排的话都需要随机访问这个大前提,像数组的话很友好,能够支持随机访问,但文件的话就不能进行随机访问了,就算你可以去移动文件指针那也非常非常慢,磁盘的速度相比较于内存而言慢太多了,差异在几百几千倍。所以外排用归并。
  6. 但在归并的过程当中,如果说你用递归的思想,比如说把十个g的文件给他,先分成5个g,5个g,那我如果想要归并的话,首先得确保这两个五个g的文件数据是有序的。那该怎么确保呢?是不是相当于又要继续划分下去,那这样划分下去,不是要划分死了吗?而且我们真正在归并的时候,为了效率提高,我们一定要借助于内存,而不是全部在慢吞吞的磁盘里面。
  7. 所以总的思路就是这样:首先把大文件给他平均分割成N份,然后保证每一份的大小都可以加载到内存当中(我必须借助于内存,没办法,不然纯硬盘里面就慢到猴年马月了),然后因为我等会儿要依托这个每一份的划分出来的小文件为基准量不断向上归并,所以说前提是这些划分出来的小文件必须有序,由于他们现在倒是能够加载到内存里面,所以说先把他们读到内存里面用快速排序把他们先排成有序的
  8. 然后在内存里面给它全部把数据排成有序之后再写回小文件当中。那么这时我们就达到了文件中归并的先决条件。现在都是一个一个有序的小文件了。
  9. 首先就是对磁盘当中的海量数据不断的读,然后用一个变量去控制一下,比如说我现在假定每个小文件里面的数据个数是十个,那么在不断读取的过程当中,每读到十个的时候,然后此时此刻停顿一下,把这十个数据给他,在内存当中快速排序一下,并且给他去创建一个新的文件,并且把这十个在内存当中已经有序的数据给他读到那个文件里面去。当然里面有一些具体的细节的话,就去处理一下就可以,包括边界问题呀等等代码逻辑处理好就可以。
  10. 然后接下来就是文件之间的归并。这时候就肯定不能在内存当中了,因为内存里面已经放不下两个文件合起来这么一个数据量。因为归并排序的话,它的空间复杂度必须是O(N)。然后如果说两两一归并的话,有个很恶心的问题,就是取名字的问题。我们虽然采用非递归归并,但其实也可以不用去两两一归并,可以如下:
    在这里插入图片描述

实际代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define ALL_NUMBER 10000
#define EVERY_NUMBER 1000
#define NAME_MAX 100
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int GetMidNumi(int* arr, int left, int right)
{
	int mid = (left + right) / 2;

	if (arr[left] < arr[mid])
	{
		if (arr[left] > arr[right])
		{
			return left;
		}
		else
		{
			return arr[mid] < arr[right] ? mid : right;
		}
	}
	else
	{
		if (arr[right] > arr[left])
		{
			return left;
		}
		else
		{
			return arr[mid] > arr[right] ? mid : right;
		}
	}
}
void QuickSort(int* arr, int left, int  right)
{
	if (left >= right)
	{
		return;
	}
	int begin = left;
	int end = right;
	int midi = GetMidNumi(arr, left, right);
	Swap(arr + left, arr + midi);
	int keyi = left;
	while (left < right)
	{
		while (left < right && arr[right] >= arr[keyi])
		{
			right--;
		}
		while (left < right && arr[left] <= arr[keyi])
		{
			left++;
		}
		Swap(arr + left, arr + right);
	}
	Swap(arr + left, arr + keyi);
	keyi = left;
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}
void CreateNumber()
{
	srand((unsigned int)time(NULL));
	FILE* pf = fopen("number.txt", "w");
	if (pf == NULL)
	{
		perror("fopen failed");
		return;
	}
	for (int i = 0; i < ALL_NUMBER; i++)
	{
		int num = rand();
		fprintf(pf, "%d\n", num);
	}
	fclose(pf);
	pf = NULL;
}
void MergeSortFile(char* file1, char* file2, char* file)
{
	FILE* pf1 = fopen(file1, "r");
	if (pf1 == NULL)
	{
		perror("fopen failed");
		return;
	}
	FILE* pf2 = fopen(file2, "r");
	if (pf2 == NULL)
	{
		perror("fopen failed");
		return;
	}
	FILE* pf = fopen(file, "w");
	if (pf == NULL)
	{
		perror("fopen failed");
		return;
	}
	int num1 = 0;
	int num2 = 0;
	int ret1 = fscanf(pf1, "%d", &num1);
	int ret2 = fscanf(pf2, "%d", &num2);
	while (ret1 != EOF && ret2 != EOF)
	{
		if (num1 < num2)
		{
			fprintf(pf, "%d\n", num1);
			ret1 = fscanf(pf1, "%d", &num1);
		}
		else
		{
			fprintf(pf, "%d\n", num2);
			ret2 = fscanf(pf2, "%d", &num2);
		}
	}
	while (ret1 != EOF)
	{
		fprintf(pf, "%d\n", num1);
		ret1 = fscanf(pf1, "%d", &num1);
	}
	while (ret2 != EOF)
	{
		fprintf(pf, "%d\n", num2);
		ret2 = fscanf(pf2, "%d", &num2);
	}

	fclose(pf1);
	fclose(pf2);
	fclose(pf);
}
void Cover(char* file1, char* file2)
{
	FILE* pf1 = fopen(file1, "r");
	if (pf1 == NULL)
	{
		perror("fopen failed");
		return;
	}
	FILE* pf2 = fopen(file2, "w");
	if (pf2 == NULL)
	{
		perror("fopen failed");
		return;
	}
	int num = 0;
	int res = 0;
	while (fscanf(pf1, "%d", &num) != EOF)
	{
		fprintf(pf2, "%d\n", num);
	}
	fclose(pf1);
	fclose(pf2);
}
int main()
{
	CreateNumber();
	FILE* pf = fopen("number.txt", "r");
	if (pf == NULL)
	{
		perror("fopen failed");
		return;
	}
	int arr[EVERY_NUMBER] = { 0 };
	char file1[NAME_MAX] = { 0 };
	char file2[NAME_MAX] = { 0 };
	char file[NAME_MAX] = { 0 };
	int res = 0;
	int i = 0;
	int count = 0;
	while ((res = fscanf(pf, "%d", &arr[i++])) != EOF)
	{
		if (i == EVERY_NUMBER)
		{
			count++;
			if (count == 1)
			{
				sprintf(file1, "%d", count);
			}
			sprintf(file2, "%d", count);
			QuickSort(arr, 0, EVERY_NUMBER - 1);
			i = 0;
			FILE* _pf = fopen(file2, "w");
			if (_pf == NULL)
			{
				perror("fopen failed");
				return 1;
			}
			for (int j = 0; j < EVERY_NUMBER; j++)
			{
				fprintf(_pf, "%d\n", arr[j]);
			}
			fclose(_pf);
			if (count > 1)
			{
				sprintf(file, "%s%s", file1, file2);
				MergeSortFile(file1, file2, file);
				strcpy(file1, file);
			}
		}
	}
	fclose(pf);
	Cover(file, "number.txt");
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

絕知此事要躬行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值