深入理解数据结构第六弹——排序(3)——归并排序

这篇具有很好参考价值的文章主要介绍了深入理解数据结构第六弹——排序(3)——归并排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

排序1:深入了解数据结构第四弹——排序(1)——插入排序和希尔排序-CSDN博客

排序2:深入理解数据结构第五弹——排序(2)——快速排序-CSDN博客

前言:

在前面,我们已经学习了插入排序、堆排序、快速排序等一系列排序,今天我们来讲解一下另一个很高效的排序方法——归并排序

目录

一、归并排序的思想

二、归并排序的递归实现

三、归并排序的非递归实现

四、完整的代码实例

五、总结


一、归并排序的思想

归并排序是一种经典的排序算法,它采用了分治法的思想。分治法的核心是“分而治之”,即将一个复杂的问题分解成两个或多个相同或相似的子问题,将这些子问题逐个解决,最后将子问题的解合并以解决原问题。

归并排序的基本思想如下:

  1. 分解(Divide)

    • 将待排序的数组从中间分成两半,递归地对这两半分别进行归并排序。
    • 一直分解,直到每个子数组只包含一个元素,因为一个元素的数组自然是有序的。
  2. 解决(Conquer)

    • 当分解到最小子问题时,即每个子数组只有一个元素时,开始解决这些小问题。
    • 解决的方式是合并(Merge)两个有序的子数组,从而得到一个更大的有序数组。
  3. 合并(Merge)

    • 合并过程是归并排序的关键步骤。它将两个有序的子数组合并成一个有序的数组。
    • 通常使用两个指针分别指向两个子数组的起始位置,然后比较两个指针所指向的元素,将较小的元素放入结果数组中,并移动该指针。
    • 重复这个过程,直到一个子数组被完全合并到结果数组中,然后将另一个子数组的剩余元素直接复制到结果数组中。

归并排序的操作如下:

深入理解数据结构第六弹——排序(3)——归并排序,数据结构,排序算法,算法

归并操作其实就是将一组数据通过递归等不断划分成两个部分,直到划分到一个元素之后,再对这两部分排序排进一个数组内,相当于把划分的过程再反过来走了一遍,只是走回去的过程中会把数组一步一步的有序化

二、归并排序的递归实现

递归的实现其实是很有意思的,在上面我们已经讲了递归的思想,其实就是不断的重复划分然后排序的过程,所以我们就可以设计一个递归来实现这种,同时,由于每一步都要进行分区划分,所以我们可以封装一个划分函数(_MergeSort函数)在前,重复这个过程

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	_MergeSort(a, 0, n - 1,tmp);

	free(tmp);
}

1、因为我们在划分结束后,需要将各个小的部分再排序成一个有序的大部分,所以我们创建一个tmp的指针指向一个与原数组一样大小的空间,然后每一次排序放进这个空间,最后再把这个空间中的数据复制回原数组

2、其中_MergeSort函数内参数分别为原数组指针,首元素位置,尾元素位置,tmp指针

然后我们就来实现这个分步函数,这个函数的功能就是实现将一个数组不断分为两个部分,当划分成最小单元时,两个两个比较大小,并且放入tmp中,再复制进原数组中,我们先拿数组 { 8 ,7,6,5,4,3,2,1 } 举个例子

深入理解数据结构第六弹——排序(3)——归并排序,数据结构,排序算法,算法

实现上述过程的代码如下

//归并排序
void _MergeSort(int* a, int begin,int end,int* tmp)
{
	if (begin == end)
		return;

	//小区间优化
	if (end - begin + 1 < 10)
	{
		InsertSort(a + begin, begin - end + 1);
	}

	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1];
			begin1++;
		}
		else
			tmp[i++] = a[begin2++];
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

在这段代码有些部分我们在下面单独讲解一下:

小区间优化是什么及其作用

深入理解数据结构第六弹——排序(3)——归并排序,数据结构,排序算法,算法

三、归并排序的非递归实现

学习完归并排序的递归实现后,我们来看一下归并排序的非递归实现

归并排序由于需要不断划分,可想而知其非递归实现是一定需要用到循环的,但是它其实还是有几个很大的坑等着我们的,归并排序的非递归实现要比其递归实现复杂的多

归并排序非递归实现需要注意的点:

1、在上面举的例子中我们都是举的2的n次方的例子,所以能恰好完全归并,但是我们实际排序时也可能遇到排11个数等,这里就比较麻烦了,所以我们需要分情况处理

2、由于上面在循环归并时次数的不确定性,所以我们每一次循环排序结束都要拷贝回原数组

深入理解数据结构第六弹——排序(3)——归并排序,数据结构,排序算法,算法

如图,我们在对两个数组进行归并时是要定义两个数组的起始位置的,但是我们可能会遇到下面三种情况:

1、end1>n,即从end1开始就超出数组长度

2、end1<n,begin2>n,即从begin2开始超出数组长度

3、end2>n,即从end2开始才超出数组长度

不管这上面哪一种,都会导致我们之后的归并排序中会有数组落单,所以我们就需要针对这中情况进行处理

深入理解数据结构第六弹——排序(3)——归并排序,数据结构,排序算法,算法

针对这种情况我们有两种解决方法:

1、跳出:就是当end1或者begin2大于n时跳出不进行处理

2、优化:就是将end1、begin2、end2进行优化处理,让它能够进行操作

具体操作如下:

1、跳出的思想

//非递归的归并排序(跳出的思想)
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		gap *= 2;
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
	}
	free(tmp);
}

2、优化的思想

//非递归的归并排序(修正的思路)
void MergeSortNonR2(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		gap *= 2;
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;

			if (end1 >= n )
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
	}
	free(tmp);
}

四、完整的代码实例

下面我们通过排序数组{ 4,7,1,9,3,6,5,6,8,3,2,0,6 }来检验这三种方法是否成功(递归一种,非递归i两种)

SeqList.h

#include<stdio.h>
//归并排序
void MergeSort(int* a, int n);

test.c

//测试归并排序
void TestMergeSort()
{
	int a[] = { 4,7,1,9,3,6,5,6,8,3,2,0,6 };
	int b[]= { 4,7,1,9,3,6,5,6,8,3,2,0,6 };
	int c[]= { 4,7,1,9,3,6,5,6,8,3,2,0,6 };
	printf("a数组排序前:");
	PrintArray(a, sizeof(a) / sizeof(a[0]));
	MergeSort(a, sizeof(a) / sizeof(a[0]));   //递归法
	printf("a数组排序后(递归法):");
	PrintArray(a, sizeof(a) / sizeof(a[0]));
	printf("\n");

	printf("b数组排序前:");
	PrintArray(b, sizeof(b) / sizeof(b[0]));
	MergeSortNonR(b, sizeof(b) / sizeof(b[0]));   //非递归法(跳出法)
	printf("b数组排序后(跳过法):");
	PrintArray(b, sizeof(b) / sizeof(b[0]));
	printf("\n");

	printf("c数组排序前:");
	PrintArray(c, sizeof(c) / sizeof(c[0]));
	MergeSortNonR2(c, sizeof(c) / sizeof(c[0]));   //非递归法(修正法)
	printf("c数组排序后(修正法):");
	PrintArray(c, sizeof(c) / sizeof(c[0]));
	printf("\n");
}
int main()
{
	
	TestMergeSort();
	
	return 0;
}

SeqList.c

//归并排序
void _MergeSort(int* a, int begin,int end,int* tmp)
{
	if (begin == end)
		return;

	//小区间优化
	if (end - begin + 1 < 10)
	{
		InsertSort(a + begin, begin - end + 1);
	}

	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1];
			begin1++;
		}
		else
			tmp[i++] = a[begin2++];
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	_MergeSort(a, 0, n - 1,tmp);

	free(tmp);
}
//非递归的归并排序(跳出的思想)
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;

	}
	free(tmp);
}
//非递归的归并排序(修正的思路)
void MergeSortNonR2(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;

			if (end1 >= n )
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}
//打印数组
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

程序运行结果:

深入理解数据结构第六弹——排序(3)——归并排序,数据结构,排序算法,算法

五、总结

综合以上,我们其实就可以清楚的认识到归并排序的有趣及其思想,这篇文章并没有将归并排序的时间复杂度和适用场景等问题,我打算在后边写一个总结的文章,将这几种排序放在一起比较,给出他们时间复杂度的快慢和适用场景的不同,敬请期待吧!!!

谢谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!文章来源地址https://www.toymoban.com/news/detail-854336.html

到了这里,关于深入理解数据结构第六弹——排序(3)——归并排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】带你深入理解栈

    栈是一种特殊的线性表。其只允许在固定的一端进行插入和删除元素的操作,进行数据的插入和删除的一端称作 栈顶 ,另外一端称作 栈底 。 栈不支持随机访问 ,栈的数据元素遵循 后进先出 的原则,即 LIFO(Late In First Out)。 也许有人曾经听说过 压栈 和 入栈 的术语,以

    2024年02月03日
    浏览(33)
  • 【脚踢数据结构】深入理解栈

    (꒪ꇴ꒪ ),Hello我是 祐言QAQ 我的博客主页:C/C++语言,Linux基础,ARM开发板,软件配置等领域博主🌍 快上🚘,一起学习,让我们成为一个强大的攻城狮! 送给自己和读者的一句鸡汤🤔: 集中起来的意志可以击穿顽石! 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏

    2024年02月13日
    浏览(31)
  • 【数据结构】 顺序表详解!深入理解!

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 数据结构解析 🌄 莫道桑榆晚,为霞尚满天! ​ 什么是数据结构?我们为什么要学数据结构?数据结构中的顺序表长什么样子?它是怎么运用? ​ 本期我们将对这些一一讲解,彻底明白数据结构的重要性,以及顺序表是一种什么的数据

    2024年02月08日
    浏览(33)
  • 深入了解数据结构第四弹——排序(1)——插入排序和希尔排序

    前言: 从本篇开始,我们就开始进入排序的学习,在结束完二叉树的学习之后,相信我们对数据在内存中的存储结构有了新的认识,今天开始,我们将进入排序的学习,今天来学习第一篇——插入排序 目录 什么是插入排序? 一、直接插入排序 1、直接插入排序的实现 2、直

    2024年04月11日
    浏览(23)
  • [数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)

    目录 1、常见的排序算法 1.1 交换排序基本思想 2、快速排序的实现方法 2.1 基本思想 3 hoare(霍尔)版本 3.1 实现思路 3.2 思路图解 3.3 为什么实现思路的步骤2、3不能交换 3.4 hoare版本代码实现 3.5 hoare版本代码测试 4、挖坑法 4.1 实现思路 4.2 思路图解 4.3 挖坑法代码实现 4.4 挖坑

    2024年02月16日
    浏览(31)
  • 深入理解数据结构:队列的实现及其应用场景

    队列(Queue)是一种具有先进先出(FIFO)特性的数据结构。在队列中,数据的插入和删除操作分别在队列的两端进行。插入操作在队列的尾部进行,而删除操作则在队列的头部进行。这种特性使得队列在很多实际应用中非常有用,比如任务调度、缓冲区管理等。 线性表是一种

    2024年04月28日
    浏览(34)
  • (一)深入理解Mysql底层数据结构和算法

    索引是帮助MySQL高效获取数据的排好序的数据结构 数据结构模拟网站:Data Structure Visualization 二叉树 不适合做自增ID的数据结构。如下示意图,假设采用二叉树作为表自增主键ID的数据存储结果如下:当查询id为5的数据时,其查询次数为5次 红黑树 不适合做mysql的索引,因为当

    2024年01月25日
    浏览(38)
  • 深入理解数据结构第一弹——二叉树(1)——堆

    前言: 在前面我们已经学习了数据结构的基础操作:顺序表和链表及其相关内容,今天我们来学一点有些难度的知识—— 数据结构中的二叉树 ,今天我们先来学习 二叉树中堆 的知识,这部分内容还是非常有意思的,下面我们就开始慢慢学习 准备工作:本人习惯将文件放在

    2024年04月17日
    浏览(31)
  • 深入理解哈希表:数据结构中的重要角色

    目录 一. 哈希表的原理与结构 哈希函数 存储数组 哈希冲突与解决方法 总结 二. 哈希函数的作用与设计 哈希函数的作用: 哈希函数的设计: 常见的哈希函数设计方法包括: 三. 哈希冲突与解决方法 1. 开放寻址法(Open Addressing) 2. 链地址法(Chaining) 四. 哈希表的应用 五

    2024年02月11日
    浏览(40)
  • 探索数据结构:顺序串与链式串的深入理解

    ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:数据结构与算法 贝蒂的主页:Betty’s blog 串是一种特殊的 顺序表 ,即每一个元素都是单独一个 字符 。在C语言中我们学习的字符串便是串的一种,它在我们的数据搜索与文本编译中起着不

    2024年04月17日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包