126.【C语言】数据结构之归并排序非递归解法

发布于:2025-07-09 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

1.知识回顾

2.非递归解法

如果数组元素个数为2的次方倍

单次归并的框架代码(针对于2的次方倍个元素)

 完整代码

如果数组的元素个数不是2的次方倍

​编辑

1.归并完了后再拷贝到原数组中(麻烦,不建议写)

处理end1越界

处理begin2越界

处理end2越界

修正后的完整代码

2.一边归并一边拷贝到原数组中(推荐,处理方法简单)

处理end1越界和begin2越界

处理end2越界

修正后的完整代码


1.知识回顾

归并排序的递归解法:125.【C语言】数据结构之归并排序递归解法

2.非递归解法

一般有两种改非递归的写法:1.用栈 2.用循环

对于归并排序而言,用栈来存储左右区间是不可以的,因为栈模拟的是前序遍历,而归并排序类似后序遍历

可以使用循环:两个两个归并→四个四个归并→……

如果数组元素个数为2的次方倍

以int arr[] = {10,6,7,1,3,9,4,2}为例,从小到大排序

两个两个归并:{10,6,7,1,3,9,4,2}→{6,10,1,7,3,9,2,4}

四个四个归并:{6,10,1,7,3,9,2,4}→{1,6,7,10,2,3,4,9}

八个八个归并:{1,6,7,10,2,3,4,9}→{1,2,3,4,6,7,9,10}

可以设一个变量gap来控制每次归并的个数(gap n.间隙)

稍微改改125.【C语言】数据结构之归并排序递归解法文章的代码即可写成一个框架

一个一个归并→两个两个归并→四个四个归并→八个八个归并→...... 发现归并的个数是有规律的,遵循2^{gap}个规律

gap==1 2^1==2
gap==2 2^2==4
gap==4 2^4==16

例如gap==2时,每组数据个数为2(即==gap),两个一组归并,

gap初始值设为1,每次大循环结束后都自乘2,这样i+=2*gap就能实现gap个一组归并了

单次归并的框架代码(针对于2的次方倍个元素)

void  non_recursive_mergesort(int* arr,int size)
{
	int* tmp = (int*)malloc(sizeof(int) * size);
	int gap = 1;
	for (int i = 0; i < ; i += 2 * gap)
	{
		int begin1=, end1 = ;
		int begin2=, end2 = ;
        int j = i;
		while (begin1 <= end1 && begin2 <= end2)
		{
			if (arr[begin1] < arr[begin2])
				tmp[j++] = arr[begin1++];
			else
				tmp[j++] = arr[begin2++];
		}

		while (begin1 <= end1)
			tmp[j++] = arr[begin1++];
		while (begin2 <= end2)
			tmp[j++] = arr[begin2++];

		gap *= 2;
	}
}	

重点是填写begin1、end1、begin2、end2的值,依照每组数据个数为gap个的规则,画图分析:

(自右向左,gap个归并) 

则有:

int begin1= i, end1 = i+gap-1;
int begin2= i + gap, end2 =i+2*gap-1 ;

 完整代码

gap为1,归并一次;gap位2,归并一次......

显然要有一个外循环来控制gap

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void  non_recursive_mergesort(int* arr, int size)
{
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	printf("debug info:\narr\'s size=%d\n",size);
	while (gap < size)
	{
		printf("gap-%d\n", gap);
		for (int i = 0; i < size; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				printf("[begin1, end1]=[%d,%d],[begin2,end2]=[%d,%d]\n", begin1, end1, begin2, end2);
				if (arr[begin1] < arr[begin2])
					tmp[j++] = arr[begin1++];
				else
					tmp[j++] = arr[begin2++];
			}

			while (begin1 <= end1)
			{
				printf("[begin1, end1]=[%d,%d]\n", begin1, end1);
				tmp[j++] = arr[begin1++];
			}
				
			while (begin2 <= end2)
			{
				printf("[begin2,end2]=[%d,%d]\n", begin2, end2);
				tmp[j++] = arr[begin2++];
			}
				
		}
		memmove(arr, tmp, sizeof(int)*size);
		gap *= 2;
	}
	free(tmp);
}

int main()
{
	int arr[] = { 10,6,7,1,3,9,4,2 };
	non_recursive_mergesort(arr, sizeof(arr)/sizeof(int));
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
		printf("%d ", arr[i]);
	return 0;
}

运行结果:

如果数组的元素个数不是2的次方倍

例如,上述数组改成:int arr[] = { 10,6,7,1,3,9,4,2,99 };运行时会出问题

发现:begin1没有越界(因为begin=i,i没有越界),但end1、begin2、end2均出现越界情况,因此需要分类讨论不同的越界情况

一共有两种写法,不同写法对不同的越界情况的处理不一样

1.归并完了后再拷贝到原数组中(麻烦,不建议写)

则memove应该写在while (gap < size)循环的外面:

处理end1越界

那begin2和end2肯定越界

如果越界了,不用归并,但必须先拷贝到tmp,因为归并完了后tmp要拷贝到原数组中,如果不先拷贝到tmp,那tmp的数据拷贝到原数组会让原数组丢失数据!

继续归并,更正end1为size-1;修改begin2和end2为不存在的区间就不能归并了(让begin2>end2即可,注意不能begin2==end2,因为是[begin2,end2]闭区间,begin2==end2时区间内仍然有一个元素)

if (end1 >= size)
{
	end1 = size - 1;
	begin2 = size;
	end2 = size - 1;
}
处理begin2越界

那end2肯定越界

 修改begin2和end2为不存在的区间

if (begin2 >= size)
{
	begin2 = size;
	end2 = size - 1;
}
处理end2越界

继续归并,更正end2为size-1

if (end2 >= size)
{
	end2 = size - 1;
}
修正后的完整代码
void  non_recursive_mergesort(int* arr, int size)
{
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	printf("debug info:\narr\'s size=%d\n",size);
	while (gap < size)
	{
		printf("gap-%d\n", gap);
		for (int i = 0; i < size; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			if (end1 >= size)
			{
				end1 = size - 1;
				begin2 = size;
				end2 = size - 1;
			}
			if (begin2 >= size)
			{
				begin2 = size;
				end2 = size - 1;
			}
			if (end2 >= size)
			{
				end2 = size - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				printf("[begin1, end1]=[%d,%d],[begin2,end2]=[%d,%d]\n", begin1, end1, begin2, end2);
				if (arr[begin1] < arr[begin2])
					tmp[j++] = arr[begin1++];
				else
					tmp[j++] = arr[begin2++];
			}

			while (begin1 <= end1)
			{
				printf("[begin1, end1]=[%d,%d]\n", begin1, end1);
				tmp[j++] = arr[begin1++];
			}
				
			while (begin2 <= end2)
			{
				printf("[begin2,end2]=[%d,%d]\n", begin2, end2);
				tmp[j++] = arr[begin2++];
			}
		}
		memmove(arr, tmp, sizeof(int)*size);
		gap *= 2;
	}
	free(tmp);
}

再次测试数组int arr[] = { 10,6,7,1,3,9,4,2,99 };运行结果:

提交到LeetCode的912. 排序数组题上测试:

2.一边归并一边拷贝到原数组中(推荐,处理方法简单)

则memove应该写在while (gap < size)循环的里面:

每次只memmove从begin1到end2之间的元素

处理end1越界和begin2越界

让[begin1,end1]和[begin2,end2]归并,但第一个区间有越界的部分,不需要归并(归并一定是两个区间归并),因此都停止归并(注意:不用拷贝到tmp中,因为没有必要)

if (end1 >= size || begin2 >= size)
{
	break;
}

其实可以简写成: if (begin2 >= size),end1如果越界,那么begin2一定越界

处理end2越界

 继续归并,更正end2为size-1

if (end2 >= size)
{
	end2 = size - 1;
}
修正后的完整代码
void  non_recursive_mergesort(int* arr, int size)
{
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	printf("debug info:\narr\'s size=%d\n",size);
	while (gap < size)
	{
		printf("gap-%d\n", gap);
		for (int i = 0; i < size; i += 2 * gap)//{ -2,3,-5 }
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			if (end1 >= size || begin2 >= size)
			{
				break;
			}
			if (end2 >= size)
			{
				end2 = size - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				printf("[begin1, end1]=[%d,%d],[begin2,end2]=[%d,%d]\n", begin1, end1, begin2, end2);
				if (arr[begin1] < arr[begin2])
					tmp[j++] = arr[begin1++];
				else
					tmp[j++] = arr[begin2++];
			}

			while (begin1 <= end1)
			{
				printf("[begin1, end1]=[%d,%d]\n", begin1, end1);
				tmp[j++] = arr[begin1++];
			}
				
			while (begin2 <= end2)
			{
				printf("[begin2,end2]=[%d,%d]\n", begin2, end2);
				tmp[j++] = arr[begin2++];
			}
			memmove(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

 再次测试数组int arr[] = { 10,6,7,1,3,9,4,2,99 };运行结果:

 提交到LeetCode的912. 排序数组题上测试:


网站公告

今日签到

点亮在社区的每一天
去签到