【数据结构排序算法(五)】二路归并排序

基础数据结构之八大排序算法(五)

⑤二路归并排序:

时间复杂度:O(nlogn) 外层函数需要遍历的次数与2的指数次有关(外层的时将复杂度为O(logn)),内层函数需要完全遍历所有数据(内层的时间复杂度为O(n)),两个相乘整体的时间复杂度为O(nlogn)
空间复杂度:O(nlogn) 额外的辅助变量会影响的问题的规模(内层额外辅助空间brr,外层函数所需额外辅助变量i),整体空间复杂度为O(nlogn)
稳定性:不稳定 存在跳跃交换

(以顺序表作为待排序对象)

1.基本思想
(1).一开始将所有数据看作单独的个体(队列),这个时候他们都独自有序。
(2).两个对列两个队列融合(保证融合后有序稳定(不存在跳跃交换))
(3).重复第二步直到所有数据都到一个队列中。

以数组arr[]={21,12,7,9,11,98,56,88,66,68}为例:

【数据结构排序算法(五)】二路归并排序_第1张图片
(这里初始数据一共是10个,第一次1个队列和1个队列融合,第二次2个数据的队列和2个数据的队列融合,第三次4个数据的队列和4个数据的队列融合,第四次8个数据的队列和8个数据(实际上其中只有2个数据)的队列融合),从而看出融合次数的规律:
【数据结构排序算法(五)】二路归并排序_第2张图片
【设 i 为运行次数,当( i * 2 ) < 总数据个数时继续排序】

2.具体合并方法

(1).需要设置一个临时辅助空间 brr 来存放两两队列对比排序完数据。
(2).左右手对比时,先从左右手中各从左到右对比,两手中较小的那个放到辅助空间 brr 中,循环这样比较直到一只手空了,则剩下的那个手里的数据依次放到辅助空间 brr 中。
(3).两手放完后,再去抓另外两组

*设置临时辅助空间 brr 的原因
例如数组arr[]={7,9,11,12,12,11,88,98,66,68}时:
【数据结构排序算法(五)】二路归并排序_第3张图片
当只用arr时会导致无序,不稳定,所以需要设立临时辅助空间brr。

3.代码实现

<1>.所需头文件和宏替换:

#include
#include
#include
#include

#define ar arr[]={21,12,7,9,11,98,56,88,66,68}
#define ELEM_TYPE int

int ar;

<2>.顺序表的建立:

typedef struct Sqlist
{
	ELEM_TYPE* data;
	int length;
	int SIZE;
}Sqlist,*PSqlist;

<3>.顺序表的初始化:

void Init_Sqlist(PSqlist L)
{
	L->data = arr;
	L->length = 0;
	L->SIZE = sizeof(arr) / sizeof(arr[0]);
}

<4>.打印

void Show(PSqlist L)
{
	for (int i = L->length; i < L->SIZE; i++)
	{
		printf("%d ", L->data[i]);
	}
	printf("\n\n");
}

<5>.每一次的归并排序:

void Merge(PSqlist L,int gap)          //控制一次融合,gap是每次融合组中数据的个数
{
	ELEM_TYPE* brr = (ELEM_TYPE*)malloc(sizeof(ELEM_TYPE) * L->SIZE);   //申请额外辅助空间brr
	int i = 0;          //辅助空间的下标

	//左右手的边界
	int low1 = 0;                           
	int high1 = low1 + gap - 1;      //有边界=左边界+偏移量
	int low2 = high1 + 1;
	int high2 = low2 + gap - 1 < L->SIZE ? low2 + gap - 1 : L->SIZE - 1;

	while (low2 <= high2)              //最外层保证可以抓取值
	{
		while (low1 <= high1 && low2 <= high2)        //内层保证两只手都有值可以比较大小
		{
			if (arr[low1] <= arr[low2])
			{
				brr[i++] = arr[low1++];
			}
			else
			{
				brr[i++] = arr[low2++];
			}
		}
		//内层循环退出的时候代表有一只手为空,这时直接将不为空的手里的数据依次放到brr中

		while (low1<=high1)         //左手不空时
		{
			brr[i++] = arr[low1++];
		}

		while (low2<=high2)         //右手不空时
		{
			brr[i++] = arr[low2++];
		}

		//已经将这次的左右手都放到brr中了,就让左右手再去抓另外的组
		low1 = high2 + 1;
		high1 = low1 + gap - 1;
		low2 = high1 + 1;
		high2 = low2 + gap - 1 < L->SIZE ? low2 + gap - 1:L->SIZE - 1;    //这里是决定外层循环退出的关键

	}
	//外层循环退出代表两种情况:1.此时全部数据都已经排完,目前左右手都是空的,这种情况不用管
	                          //2.此时左手有值右手没有值,这时只需要将左手的值依次放进brr就可以

	while (low1 < L->SIZE)
	{
		brr[i++] = arr[low1++];
	}

	//此时brr已经满了,只需将brr都覆盖arr即可
	for (int k = 0; k < L->SIZE; k++)
	{
		arr[k] = brr[k];
	}

	//最后将临时辅助空间释放防止内存泄露
	free(brr);
}

<6>.二路归并排序:

void MergeSort(PSqlist L)          //控制总的融合次数
{
	for (int i = L->length + 1; i < L->SIZE; i = i * 2)
	{
		Merge(L, i);
	}
}

<7>.主函数:

int main()
{
	Sqlist head;
	Init_Sqlist(&head);
	printf("初始数据为:");
	Show(&head);

	MergeSort(&head);
	printf("二路归并排序后数据为:");
	Show(&head);
	return 0;
}

<8>.运行结果:
【数据结构排序算法(五)】二路归并排序_第4张图片
希望这篇博客能帮助大家更好的学习和理解二路归并排序
感谢阅读!

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