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

前言

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

主要思路:

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

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

过程演示

归并排序(递归实现、非递归实现)、磁盘中的外排序_第1张图片


递归实现归并排序

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);
}

非递归实现归并排序

过程如下图!!!

归并排序(递归实现、非递归实现)、磁盘中的外排序_第2张图片

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);
}

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

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

思路:

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

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

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

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

过程实现如下图!!!

归并排序(递归实现、非递归实现)、磁盘中的外排序_第3张图片


C语言代码实现

#include
#include
#include

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
			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、稳定性:稳定。

你可能感兴趣的:(数据结构,排序算法)