排序-----归并排序,外排序

归并排序,外排序

归并排序

基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and

Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有

序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

思维导图

在这里插入图片描述
在这里插入图片描述

特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

  2. 时间复杂度:O(N*logN)

  3. 空间复杂度:O(N)

  4. 稳定性:稳定

代码实现(递归)

//归并排序单趟思想:合并两段有序的数组,合并以后依旧有序
void _mergesort(int* a, int left, int right, int* tmp)
{
    //递归终止条件,当区间不存在或者只剩下一个数,递归终止
	if (right <= left)
	{
		return;
	}
	int mid = left + (right - left) / 2;
	//[left,mid][mid+1,right] 有序,则可以合并,若无序,用子问题解决

	_mergesort(a, left, mid, tmp);
	_mergesort(a, mid + 1, right, tmp);

	//归并[left,mid][mid+1,right] 有序
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
    //i tmp数组的下标
	int i = left;
	while (begin1<=end1&&begin2<=end2)
	{
		//排升序,将小的数放在前面
		if (a[begin1]<a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//将还没走到最后面的那个数组剩下的值全部放到tmp中
	while (begin1<=end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2<=end2)
	{
		tmp[i++] = a[begin2++];
	}
    //将归并好的区间拷贝回原数组
    //归并好的区间:begin1——end2
	for (int j = left; j <= right; j++)
	{
		a[j] = tmp[j];
	}
	
}

//归并排序
//时间复杂度:O(N*logN)
//深度为logN  每一层都有N个数被归并到上一层,故时间复杂度为O(N*logN)
void mergesort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_mergesort(a, 0, n - 1, tmp);
	

	free(tmp);
	tmp = NULL;
}

归并排序非递归思维导图

在这里插入图片描述

代码实现(非递归)

//归并排序(非递归)
//一组归并完,整体拷贝回原数组
//end1和begin2越界都需要归并
void mergesortnotR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	
	while (gap<n)
	{
		//[i,i+gap-1] [i+gap,i+2*gap-1]
		for (int i = 0; i < n; i+=2*gap)
		{
			int begin1 = i, end1 = i+gap-1;
			int begin2 = i+gap, end2 = i+2*gap-1;
			int index = i;

			//end1越界
			if (end1>=n)
			{
				end1 = n - 1;
			}

			//begin2越界
			if (begin2>=n)
			{
				begin2 = n;
				end2 = n - 1;
				//使这个区间不存在,防止tmp数组越界
			}

			//end2越界
			if (end2>=n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				//排升序,将小的数放在前面
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//将还没走到最后面的那个数组剩下的值全部放到tmp中
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
		}
		//一组归并完,整体拷贝回原数组
		for (int j = 0; j < n; j++)
		{
			a[j] = tmp[j];
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}


//归并排序(非递归)
//归一部分,拷贝一部分回原数组
//end1和begin2越界都不需要归并
void mergesortnotR1(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;

	while (gap < n)
	{
		//[i,i+gap-1] [i+gap,i+2*gap-1]
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int index = i;

			//end1和begin2越界都不需要归并,直接跳出循环
			if (end1>=n||begin2>=n)
			{
				break;
			}

			//end2越界,需要归并
			if (end2>=n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				//排升序,将小的数放在前面
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//将还没走到最后面的那个数组剩下的值全部放到tmp中
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//归一部分,拷贝一部分回原数组
			for (int j = i; j <=end2; j++)
			{
				a[j] = tmp[j];
			}
		}
		
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

外排序

基本思想

当文件中待排序数据空间超过计算机内存的大小时,可以将文件数据切分为N块,每一块数据大小与内存一样大,

再用快速排序对每一块数据进行排序,最后,在文件中用归并排序将N块有序的数据归并到原来的文件中。

思维导图

在这里插入图片描述

代码实现

void _mergefile(const char* file1, const char* file2, const char* mfile)
{
    //FILE * fopen ( const char * filename, const char * mode );
	//filename: 文件名
	//mode:打开的方式
    //r 以读的方式打开  w 以写的方式打开
	FILE* fin1 = fopen(file1, "r");
	FILE* fin2 = fopen(file2, "r");
	FILE* fout = fopen(mfile, "w");
	int num1, num2;
	//读写文件的时候,文件指针都会自动往后移动
    //fscanf  将fin1文件中的数据读取到num1中
	int ret1=fscanf(fin1, "%d\n", &num1);
	int ret2=fscanf(fin2, "%d\n", &num2);
	while (ret1!=EOF&&ret2!=EOF)
	{
		if (num1<num2)
		{
            //fprintf  将num1中的数据写入到fout文件中
			fprintf(fout, "%d\n", num1);
			ret1 = fscanf(fin1, "%d\n", &num1);
		}
		else
		{
			fprintf(fout, "%d\n", num2);
			ret2 = fscanf(fin2, "%d\n", &num2);
		}
	}
	while (ret1!=EOF)
	{
		fprintf(fout, "%d\n", num1);
		ret1 = fscanf(fin1, "%d\n", &num1);
	}
	while (ret2!=EOF)
	{
		fprintf(fout, "%d\n", num2);
		ret2 = fscanf(fin2, "%d\n", &num2);
	}
    //打开文件后,需要用close关闭文件
	fclose(fin1);
	fclose(fin2);
	fclose(fout);
}


//外排序
void mergesortfile(const char*file)
{
    //分为10 组
	int n = 10;
	FILE* fout = fopen(file, "r");
	assert(fout);
	int num = 0;
    //每组存放10个数据
	int a[10];
    //数组a的下标
	int i = 0;
    //文件名下标
	int filei = 1;
    //存放文件名的数组
	char subfile[20];
    //读取数据过程中,若为EOF,则表示文件读取结束
	while (fscanf(fout,"%d\n",&num)!=EOF)
	{
        //每读取一个数,文件指针都会移动一下
        //if中读9个数,else中再读一个数
        //因为无论执行if还是执行else 文件都会读取数据
        //若if中读取10个数,在执行else时不读取数的话,每次读就会丢失一个数
		if (i<n-1)
		{
			a[i++] = num;
		}
		else
		{
			a[i] = num;
            //对每组数进行快排,使其变得有序
			quicklysort(a,0,n - 1);
            //sprintf  将filei转化为字符串,并存入subfile数组
			sprintf(subfile, "ssort\\sub_sort%d", filei++);
            //排完序后,将有序的这个序列写入文件中
			FILE* fin = fopen(subfile, "w");
			assert(fin);
			for (int i = 0; i < n; i++)
			{
				fprintf(fin, "%d\n", a[i]);
			}
			fclose(fin);
			i = 0;
		}
	}

	//利用互相归并到文件,实现整体有序
	char file1[100]= "ssort\\sub_sort1";
	char mfile[100]="ssort\\sub_sort12";
	char file2[100];
	for (int i = 2; i <= n; i++)
	{
		sprintf(file2, "ssort\\sub_sort%d", i);
		//读取file1和file2,进行归并处mfile
		_mergefile(file1, file2, mfile);

		//迭代
		strcpy(file1, mfile);
		sprintf(mfile, "%s%d", mfile, i + 1);
	}

	fclose(fout);
}

总结

归并排序的核心思想是:将两个有序的数组进行归并,归并后还有序。对于无序的数组,可先用分治的思想,将其分为不可再分的子区间后,再进行归并,从而达到整体有序。归并的非递归可以用栈或者队列来实现,实现方法于快排的非递归法差不多,都是用栈或者队列保存子区间下标,但也可以不用栈和队列,可直接用gap来表示每次递归的间距,通过循环使gap变化,也能产生同样的效果。外排序这一块,主要逻辑到不是很难,只是对文件的操作比较生疏,得加强这一方面的练习!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值