归并排序(递归实现、非递归实现)、磁盘中的外排序

本文详细介绍了归并排序的原理和两种实现方式,包括递归和非递归,并展示了C语言代码。同时,讨论了如何利用归并排序解决磁盘中的外排序问题,给出了具体的步骤和代码实现。最后总结了归并排序的时间复杂度和稳定性特点。
摘要由CSDN通过智能技术生成

前言

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。其中二路归并类似二叉树的后序遍历。归并排序的思考更多的是解决在磁盘中的外排序问题。

主要思路:

先将待排序的序列分割为小的序列,使每个子序列有序。

将小的子序列合并为更大的子序列,即使子序列段间有序。合并到最后整个序列有序。

过程演示

在这里插入图片描述


递归实现归并排序

C语言代码实现

//将已有序的小区间合并为更大的有序区间,其中合并取得是闭区间。
void MergeArr(int* a, int begin1, int end1, int begin2, int end2, int *temp) {
	int index = begin1;
	int left = begin1, right = end2;
	while (begin1 <= end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2]) temp[index++] = a[begin1++];
		else temp[index++] = a[begin2++];
	}
	while(begin1<=end1)temp[index++] = a[begin1++];
	while(begin2<=end2)temp[index++] = a[begin2++];
	for (int i = left; i <= right;++i) a[i] = temp[i];
}
//递归处理更小的区间序列
void _MergeSort(int *a,int left,int right,int *temp) {
	if (left >= right) return;//当区间只有一个元素或没有元素时返回
	int mid = (left + right) >> 1;//以每个区间的中点分割区间
	_MergeSort(a,left,mid,temp);//递归处理左区间
	_MergeSort(a,mid+1,right,temp);//递归处理右区间
	MergeArr(a,left,mid,mid+1,right,temp);//合并为更大的有序区间
}

//归并递归
void MergeSort(int *a,int n) {
	assert(a);
	int* temp = (int*)malloc(sizeof(int)*n); 
	if(temp==NULL){
		printf("申请空间失败!!!\n");
		exit(-1);
	}
	_MergeSort(a,0,n-1,temp);
	free(temp);
}

非递归实现归并排序

过程如下图!!!

在这里插入图片描述

C语言代码实现

//将已有序的小区间合并为更大的有序区间,其中合并取得是闭区间。
void MergeArr(int* a, int begin1, int end1, int begin2, int end2, int *temp) {
	int index = begin1;
	int left = begin1, right = end2;
	while (begin1 <= end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2]) temp[index++] = a[begin1++];
		else temp[index++] = a[begin2++];
	}
	while(begin1<=end1)temp[index++] = a[begin1++];
	while(begin2<=end2)temp[index++] = a[begin2++];
	for (int i = left; i <= right;++i) a[i] = temp[i];
}
//归并非递归实现
void MergeSortNonR(int* a, int n) {
	assert(a);
	int* temp = (int*)malloc(sizeof(int)*n);
	if(temp==NULL){
		printf("申请空间失败!!!\n");
		exit(-1);
	}
	int gap = 1;//其中gap的含义代表本次循环合并的数字个数
	while (gap < n) {
		for (int i = 0; i < n; i += 2 * gap) {
			//因为是闭区间,所以end的大小要 -1;
			int begin1=i, end1 =i+gap-1;
			
			int begin2=i+gap, end2 =i+2*gap-1;
			
			if (begin2 >= n) break;//当第二段区间不存在时,只存在第一段区间时,结束循环,如果只存在第一段区间则不用合并!!!!
			if (end2 >= n) end2 = n - 1;//当第二段区间存在并且end2>=n 时缩小第二段区间的范围。
			
			MergeArr(a,begin1,end1,begin2,end2,temp);
		}
		gap *= 2;
	}
	free(temp);
}

使用归并排序完成磁盘中的外排序

问题:当有一个文件中放置着以某种格式存在的海量数据,我们无法一次性将数据全部加载进入内存中,此时要求我们完成对这个文件的排序。

思路:

将大文件先分割成多个小文件。

将大文件中的部分数据加载进入内存中,为了节省空间我们可以使用快速排序,将这部分数据排序。然后将排好的数据输出到小文件中。

重复上述过程直到将大文件中的数据全部形成小文件。

合并小文件形成更大的文件,然后将这个更大的文件接着和剩下的小文件进行合并。重复此过程直到小文件最终被合并完毕,这样就形成了最终排好序的大文件。

过程实现如下图!!!


C语言代码实现

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

void MergeFile(const char*sin1,const char *sin2,const char*sout) {
	FILE* file1 = fopen(sin1,"r");
	if (file1 == NULL) {
		printf("file1打开文件失败!!\n");
		exit(-1);
	}
	FILE* file2 = fopen(sin2, "r");
	if (file2 == NULL) {
		printf("file2打开文件失败!!\n");
		exit(-1);
	}
	FILE* fout = fopen(sout,"w");
	if (fout == NULL) {
		printf("fout打开文件失败!!\n");
		exit(-1);
	}
	int num1, num2;
	int flag1 = fscanf(file1,"%d ",&num1);
	int flag2 = fscanf(file2, "%d ", &num2);
	while (flag1!=EOF&&flag2!= EOF) {
		if (num1 < num2) {
			fprintf(fout,"%d ",num1);
			flag1 = fscanf(file1, "%d ", &num1);
		}
		else {
			fprintf(fout, "%d ", num2);
			flag2 = fscanf(file2, "%d ", &num2);
		}
	}
	while (flag1 != EOF) {
		fprintf(fout, "%d ", num1);
		flag1 = fscanf(file1, "%d ", &num1);
	}
	while (flag2 != EOF) {
		fprintf(fout, "%d ", num2);
		flag2 = fscanf(file2, "%d ", &num2);
	}
	fclose(file1);
	fclose(file2);
	fclose(fout);
}
//file文件的名字,N生成小文件的数量,Nnum一个小文件中包含多少个数
void MergeSortFile(const char* file, const int N, const int Nnum) {
	FILE* fout = fopen(file, "r"); //读取这个大文件
	int* temp = (int*)malloc(sizeof(int) * Nnum); //保存从文件中输出的数据
	if (temp == NULL) {
		printf("内存不足、申请失败!!\n");
		exit(-1);
	}
	char subfile[20];	//保存每个小文件的文件名
	int i = 0, filei = 1;//i 用作数组索引,filei代表第几个小文件
	int num;	//保存从文件中读入的每个数据
	while (fscanf(fout, "%d ", &num) != EOF) {
		if (i < Nnum - 1) {//i<Nnum-1
			temp[i++] = num;//将从大文件中读入的数据保存到数组中
		}
		else {
			temp[i] = num;
			QuickSortNonR(temp, 0, Nnum - 1);//用快速排序将数组的数据进行排序,这个函数需要自己写。
			sprintf(subfile, "sub\\subsort%d", filei++);//生成文件名
			FILE* fin = fopen(subfile, "w");//打开小文件
			if (fin == NULL) {
				printf("打开文件失败!!!");
				exit(-1);
			}
			for (int j = 0; j < Nnum; j++) {
				fprintf(fin, "%d ", temp[j]);//将数组中的数据保存到小文件中
			}
			fclose(fin);
			i = 0;
			printf("subsort%d已完成排序!!!!\n",filei-1);
		}
	}
	printf("小文件排序完成!!!!\n");
	const char prefixout[50] = "sub\\mergefile";
	const char prefixin[50] = "sub\\subsort";
	char fmout[50]="sub\\mergefile1";
	char fin1[50]="sub\\subsort1";
	char fin2[50]="sub\\subsort2";
	for (int i = 2; i <= N; i++) {
		MergeFile(fin1,fin2, fmout);
		printf("%s合并完毕\n", fmout);
		strcpy(fin1, fmout);
		strcpy(fmout, prefixout);
		strcpy(fin2, prefixin);
		sprintf(fmout,"%s%d%s",fmout,i);
		sprintf(fin2,"%s%d%s",fin2,i+1);
	}
	fclose(fout);
}

int main() {
	srand(time(0));
	char sfile[30] = "e:\\random.txt";	
	const int N=10,Nnum= 5000000;
	FILE* file = fopen(sfile, "w");
	if (file == NULL) {
		printf("open fail!!!\n");
		exit(-1);
	}
	else {
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < Nnum; j++) {
				fprintf(file, "%d ", rand());
			}
		}
		fclose(file);
	}
	//以上过程实现生成一个大文件,
	printf("生成随机数完成!!!!\n");
	printf("小文件开始分解排序!!!!\n");
	MergeSortFile(sfile,N,Nnum);
	printf("小文件合并完成!!!!\n");
	return 0;
}

归并排序的特性总结

1、归并排序的缺点在于需要额外的O(n)的空间,空间复杂度:O(n)。但是有个优点就是可以用作外排序,其中使用外排序实际就是把文件当作数组来进行。我们用归并排序实现外排序可以解决磁盘中的外排序。
2、时间复杂度:O(nlongn),其中最好和最坏时间复杂度都是O(nlogn)。
3、稳定性:稳定。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值