数据结构从入门到精通——归并排序

归并排序

  • 前言
  • 一、归并排序的基本思想
  • 二、归并排序的特性总结
  • 三、归并排序的动画展示
  • 四、递归实现归并排序的具体代码展示
  • 五、非递归实现归并排序


前言

归并排序是一种分治策略的排序算法。它将一个序列分为两个等长(几乎等长)的子序列,分别对子序列进行排序,然后将排序结果合并起来,得到完全有序的序列。这个过程递归进行,直到整个序列有序。归并排序的时间复杂度为O(nlogn),空间复杂度为O(n)。


一、归并排序的基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序的基本思想是将两个或两个以上的有序表合并成一个新的有序表。这个思想可以递归地应用于子序列的排序,最终使得整个序列有序。

具体来说,归并排序可以分为两个主要步骤:分解和合并。

分解步骤是将待排序的序列不断分解成两个子序列,直到子序列的长度为1。这个过程可以通过递归实现,每次递归都将当前序列的中间点作为分割点,将序列分成左右两个子序列。由于子序列的长度为1,因此它们本身就被视为有序序列。

合并步骤是将两个有序子序列合并成一个新的有序序列。这个过程可以通过迭代实现,每次迭代都取两个子序列中的第一个元素,比较它们的大小,将较小的元素添加到新序列中,并将其从原序列中移除。这个过程一直持续到其中一个子序列为空,然后将另一个子序列中剩余的元素全部添加到新序列中。

归并排序的时间复杂度为O(nlogn),其中n是待排序序列的长度。这是因为分解步骤需要递归地将序列分解成子序列,这个过程的复杂度为O(logn);而合并步骤需要将两个子序列合并成一个新序列,这个过程的复杂度为O(n)。由于这两个步骤都需要进行logn次,因此总的时间复杂度为O(nlogn)。

归并排序是一种稳定排序算法,即相等元素的相对顺序在排序前后保持不变。这是因为在合并步骤中,当两个子序列中出现相等元素时,我们总是先取左子序列中的元素,因此相等元素在左子序列中的相对顺序会被保留下来。

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

二、归并排序的特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

归并排序的特性总结起来主要有四点:稳定性、时间复杂度、空间复杂度和递归性。

首先是稳定性。归并排序是一种稳定的排序算法,即相同元素的相对顺序在排序过程中不会改变。这一特性使得归并排序在处理需要保持原始顺序的数据时非常有用,比如在数据库查询、文件处理等场景中,保持数据的原始顺序往往是非常重要的。

其次是时间复杂度。归并排序的时间复杂度为O(nlogn),其中n是待排序数据的数量。这意味着无论数据是已经部分排序还是完全无序,归并排序都能保持较高的效率。这种优良的时间复杂度使得归并排序在处理大规模数据时具有显著优势。

再次是空间复杂度。归并排序的空间复杂度为O(n),因为它需要额外的空间来合并两个已排序的子数组。这意味着在内存有限的情况下,使用归并排序可能需要额外的考虑。然而,在大多数情况下,这种空间消耗是可以接受的,因为归并排序的高效性和稳定性往往能够抵消其空间复杂度的不足。

最后是递归性。归并排序是一种典型的分治算法,它通过递归地将问题分解为更小的子问题来解决。这种递归性使得归并排序的实现相对简单明了,也易于理解和维护。然而,递归也可能导致栈空间的消耗,因此在处理大规模数据时需要注意递归深度的问题。

综上所述,归并排序作为一种高效稳定的排序算法,在实际应用中具有广泛的应用场景。其稳定的特性使得它能够保持数据的原始顺序不变;优良的时间复杂度使得它能够处理大规模数据;额外的空间消耗在大多数情况下是可以接受的;递归性则使得归并排序的实现简单明了。了解这些特性并合理利用它们,可以让我们在实际编程中更加高效地使用归并排序算法。

三、归并排序的动画展示

归并排序是一种分治策略的排序算法。动画展示中,初始时,列表被分为单个元素的子列表。然后,相邻的子列表通过归并操作合并为有序的较长子列表,这一过程递归进行,直至整个列表有序。动画生动展示了如何通过将小有序片段合并为更大有序片段来实现整个列表的排序。

归并排序

四、递归实现归并排序的具体代码展示

#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include<time.h>

void MergeSort(int* a, int n);
void _MergeSort(int* a, int begin, int end, int* tmp);
void PrintArray(int* a, int n);
void TestMergeSort()
{
	int a[] = { 5, 13, 9, 16, 12, 4, 7, 1, 28, 25, 3, 9, 6, 2, 4, 7, 1, 8 };
	//int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	PrintArray(a, sizeof(a) / sizeof(int));

	MergeSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}


void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
void MergeSort(int* a, int n)
{
	int* tmp = malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror;
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);
}
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin == end)return;
	int mid = (end + begin) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	int begin1 = begin, begin2 = mid + 1, end1 = mid, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[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 TestOP()
{
	srand(time(0));
	const int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}


	int begin1 = clock();
	MergeSort(a1, N);
	int end1 = clock();



	printf("MergeSort:%d\n", end1 - begin1);

	free(a1);
}

int main()
{
	TestMergeSort();
	TestOP();

	return 0;
}

该代码实现了归并排序算法。归并排序是一种分治算法,首先将原始数组递归地分成两个子数组,然后对子数组进行排序,最后将排序好的子数组合并成一个有序数组。

代码中的MergeSort函数是对外接口,用于调用归并排序算法。首先申请了一个临时数组tmp,用于存放归并过程中的临时结果。然后调用_MergeSort函数进行实际的归并排序操作。

_MergeSort函数是核心函数,用于实现归并排序的递归过程。首先判断递归结束的条件,即如果beginend相等,则只有一个元素,不需要排序。然后找到中间位置mid,将原数组分成两个子数组并分别递归调用_MergeSort函数进行排序。

接下来是合并过程,使用四个指针begin1begin2end1end2分别指向两个子数组的起始和结束位置。然后使用指针i遍历临时数组tmp。比较两个子数组的元素大小,将较小的元素放入tmp数组中,并将对应指针向后移动。直到有一个子数组遍历完毕,将另一个子数组中的剩余元素依次放入tmp数组。

最后,使用memcpy函数将临时数组tmp中的元素拷贝回原数组a中,完成排序。

五、非递归实现归并排序

#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include<time.h>

void MergeSort(int* a, int n);
void _MergeSort(int* a, int begin, int end, int* tmp);
void MergeSortNonR(int* a, int n);
void PrintArray(int* a, int n);

void TestMergeSortNonR()
{
	int a[] = { 5, 13, 9, 16, 12, 4, 7, 1, 28, 25, 3, 9, 6, 2, 4, 7, 1, 8 };
	//int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	PrintArray(a, sizeof(a) / sizeof(int));

	MergeSortNonR(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void MergeSortNonR(int* a, int n)
{
	int* tmp = malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror;
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i = i+ 2* gap)
		{
			int begin1 = i, end1 = i + gap - 1, begin2 = i + gap , end2 = begin2 + gap - 1;
			if (end1 >= n || begin2 >= n)
				break;

			if (end2 >= n)
				end2 = n - 1;
			int j = i;
			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;
	}
	
	
}
void TestOP1()
{
	srand(time(0));
	const int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}


	int begin1 = clock();
	MergeSortNonR(a1, N);
	int end1 = clock();



	printf("MergeSortNonR:%d\n", end1 - begin1);

	free(a1);
}
int main()
{
	TestMergeSortNonR();
	TestOP1();

	return 0;
}

这段代码是用来实现非递归的归并排序算法。归并排序是一种分治算法,通过将数组分成两个部分,分别对这两个部分进行排序,然后将排好序的两个部分合并起来。

在代码中,首先创建一个临时数组tmp,用来在合并过程中暂存排序后的结果。然后定义一个变量gap作为当前的步长,初始时为1。通过一个循环,每次将gap乘以2,直到gap大于等于n。在循环中,通过两个内嵌的循环,将数组分成若干个子数组,并进行两两合并。

内层循环中,先计算出两个待合并的子数组的起始和结束位置,然后对这两个子数组进行合并操作。合并过程中,比较两个子数组中的元素,将较小的元素放入临时数组tmp中,并移动对应子数组的指针。最后,将tmp中的结果拷贝回原始数组a中。

整体的时间复杂度为O(nlogn),空间复杂度为O(n)。由于该排序算法是稳定的,所以适用于各种类型的数据排序。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/489603.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

百度百科审核不通过全攻略,一看就会!

在撰写百度百科词条时&#xff0c;遇到审核不通过的情况可能会让人感到沮丧。然而&#xff0c;我们并不需要灰心&#xff0c;而是要通过一些方法来改善文章质量&#xff0c;使其符合百度百科的要求。腾轩科技传媒分享百度百科审核不通过全攻略&#xff0c;一看就会&#xff01;…

Docker Stack(堆栈) 部署多服务集群,多服务编排

1、Docker Stack简介 Docker Stack(堆栈) 是在 Swarm 上管理服务堆栈的工具。而在以前文章docker swarm集群搭建 介绍的 Docker Swarm 只能实现对单个服务的简单部署&#xff0c;于是就引出了Docker Stack。 上面我们介绍到 docker-compose&#xff1a;可以在一台机器上使用…

出差补助怎么发放更高效省心?这套攻略快看看

交补、餐补、话补等各类补助场景分散&#xff0c;无法实现一站式统筹管理。不仅如此&#xff0c;补贴核算也总是需要员工提供各类凭证&#xff0c;经过财务反复核实才能发放……出差发放补助原本是为了传递企业关怀&#xff0c;鼓励员工积极出差&#xff0c;由于发放和管理不当…

6 Spring-AOP

文章目录 1&#xff0c;AOP简介1.1 什么是AOP?1.2 AOP作用1.3 AOP核心概念 2&#xff0c;AOP入门案例2.1 需求分析2.2 思路分析2.3 环境准备2.4 AOP实现步骤步骤1:添加依赖步骤2:定义接口与实现类步骤3:定义通知类和通知步骤4:定义切入点步骤5:制作切面步骤6:将通知类配给容器…

新能源电车充电桩运营管理分析

摘要&#xff1a;近年来&#xff0c;我国大力推进新能源公共交通的发展&#xff0c;制定了一系列相关政策法规。作为公共充电设施的新能源充电桩也得到了发展和普及&#xff0c;其在新能源领域发挥着重要的保障作用。在当前&#xff0c;充电桩的管理还存在许多短板&#xff0c;…

MySql实战--全局锁和表锁 :给表加个字段怎么有这么多阻碍

今天我要跟你聊聊MySQL的锁。数据库锁设计的初衷是处理并发问题。作为多用户共享的资源&#xff0c;当出现并发访问的时候&#xff0c;数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。 根据加锁的范围&#xff0c;MySQL里面的锁大致可以分成…

开放式耳机性价比高的品牌有哪些呢?五大高性价比选购清单

不入耳开放式蓝牙耳机近两年开始火起来了&#xff0c;因为它佩戴的舒适性和安全性两方面受到了很多人的关注。开放式的设计&#xff0c;就算不放进耳朵里也能听歌&#xff0c;同时加上它独特的空气传导的传声途径&#xff0c;整体的音质还是很不错的。不压耳&#xff0c;不涨耳…

Redis 6.0.8版本下载

简介&#xff1a;Java领域优质创作者楠木 分享Java项目、简历模板、学习资料、面试题库 想必大家在下载redis之前已经逛了很多教程了&#xff0c;可能不尽如意&#xff0c;找不到自己的想要的版本。既然刷到了我的这条博客&#xff0c;说明你是幸运的&#xff01; Redis6.0.8的…

【Godot4自学手册】第二十七节自定义状态机完成看守地宫怪物

本节&#xff0c;我将使用自定义状态机实现看守地宫怪物&#xff0c;完成了基础类State&#xff0c;状态机类StateMachine的编码&#xff0c;实现了怪物的闲置巡逻类、追踪类和攻击类&#xff0c;以及对应动画等。这节代码有点多&#xff0c;不过还好&#xff0c;代码比较简单。…

C语言 汉诺塔问题

目录 1.前言 2.问题描述 3.问题分析 4.定义一个主函数 5.再定义一个hanoi函数 6.所有代码 7.结语 1.前言 汉诺塔问题&#xff0c;是心理学实验研究常用的任务之一。该问题的主要材料包括三根高度相同的柱子和一些大小及颜色不同的圆盘&#xff0c;三根柱子分别为起始柱A…

大数据入门(一)

大数据主要要解决&#xff1a;海量数据的采集&#xff0c;存储&#xff0c;分析计算问题。 大数据的特点&#xff1a;大量&#xff08;数据量大&#xff09;&#xff0c;高速&#xff08;数据量的累积越来越快&#xff09;&#xff0c;多样&#xff08;结构化数据和非结构化数…

基于nodejs+vue医院综合管理系统实现与设计python-flask-django-php

第一&#xff0c;研究分析当下主流的nodejs技术&#xff0c;结合医院日常管理方式&#xff0c;进行医院综合管理系统的数据库设计&#xff0c;设计医院综合管理系统功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架构、系统搭建采用的服务…

【办公类-50-01】20240326判断随机写的“日期”是否是双休日

背景需求&#xff1a; 领导让我做设计本学期的科研培训方案。 我在2-6月随机写每月的培训日期&#xff0c;重新制定了主题 因为科研培训不可能在双休日&#xff0c;因此我希望本次活动的随机写的日期&#xff0c;不能是双休日。 我想用Python判断一下这些预设的日期是否是双休…

SpringBoot—@ConditionalOnBean与@ConditionalOnClass

一、ConditionalOnBean概念 需求场景 比如下面一种场景&#xff0c;我在实例化People对象的时候&#xff0c;需要注入一个City对象。这个时候问题来了&#xff0c;如果city没有实例化&#xff0c;那么下面就会报空指针或者直接报错。 所以这里需求很简单&#xff0c;就是当前c…

JS加密解密之应用如何保存到桌面书签

前言 事情起因是这样的&#xff0c;有个客户解密了一个js&#xff0c;然后又看不懂里边的一些逻辑&#xff0c;想知道它是如何自动拉起谷歌浏览器和如何保存应用到书签的&#xff0c;以及如何下载应用的。继而诞生了这篇文章&#xff0c;讲解一下他的基本原理。 渐进式Web应用…

电源模块 YULIN俞霖科技DC/DC电源模块 直流升压 高压稳压

Features 最低工作电压&#xff1a;0.7V电压隔离&#xff1a;1000VDC /3000VDC 平均无故障时间&#xff1a; > 800,000 小时短路与电弧保护无最低负载要求&#xff1a;可空载工作输入电压&#xff1a;5、12、15、24VDCOutput 100,200、300、400、500 、600、800、 1000、1…

kubernetes-k9s一个基于Linux 终端的集群管理工具

效果预览 下载 github 版本 此文档使用的版本是 v0.32.4&#xff0c;下载地址&#xff1a; https://github.com/derailed/k9s/releases/download/v0.32.4/k9s_linux_amd64.rpm 安装 rpm -ivh k9s_linux_amd64.rpm使用 启动 终端直接执行命令 k9s k9s基本操作 1 选择目…

魔众文库后台显示多少条,这个在那里文件修改?

显示多少条是那个文件修改的&#xff0c;显示1000条服务器比较差&#xff0c;加载太慢了。想要修改小一点。 这个是全局的显示配置&#xff0c;在文件 module/Wenku/Admin/Controller/WenkuDocController.php 中。 ->pageSizes([10, 100, 1000])

Redis中RDB的dirty机制和AOF中的后台重写机制

RDB的dirty计数器和lastsave属性 服务器除了维护saveparams数组之外&#xff0c;还维持着一个dirty计数器,以及一个lastsave属性: 1.dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后&#xff0c;服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括…

[Android]模拟器登录Google Play失败

问题&#xff1a; 模拟器登录Google Play失败&#xff0c;提示couldnt sign in there was a problem communicating with google servers. try again later. 原因&#xff1a; 原因是模拟器没有连接到互联网&#xff0c;打开模拟器中Google浏览器进行搜索一样不行。 解决&am…