【数据结构】八大排序 (三)

目录

前言:

快速排序

快速排序非递归实现

快速排序特性总结

归并排序

归并排序的代码实现

归并排序的特性总结

计数排序

计数排序的代码实现

计数排序的特性总结


前言:

前文快速排序采用了递归实现,而递归会开辟函数栈帧,递归的深度越深,占用栈区的空间就越大,栈区的大小一般是8M,10M,当递归深度足够深时,栈区的空间就会被用完,导致栈溢出,此时需要将递归改为非递归更加稳妥,本篇继续详细解读快排的非递归实现,归并排序,计数排序;

快速排序

快速排序非递归实现

  • 采用递归实现快速排序时,而递归就是不断调用单趟排序函数的功能,若不采用递归,什么可以实现不断调用单趟排序函数的功能?
  • 循环;
  • 循环只要满足循环条件,就会不断调用单趟排序函数的功能,但是每次递归调用时单趟排序函数的参数是变化的,而循环条件确是一成不变的,递归会在栈上建立函数栈帧,而函数栈帧里面存放下次调用该函数的参数,若采用非递归,那我们就必须把每一次循环的参数记录下来,供单趟排序使用,如何解决?
  •  使用顺序栈或者链式栈记录每次函数参数,栈的实现采用动态内存开辟,存储空间是堆区开辟的空间,堆区大小可达2G;

快速排序非递归实现步骤:

  1. 创建一个栈,将整个序列的起始位置和结束位置入栈;
  2. 当栈不为空时,弹出栈顶元素,取出该区间的起始位置 left 和结束位置 right;
  3. 对该区间进行划分,获取划分点 keyi;
  4. 如果keyi左边还有元素,将左半部分的起始位置left和结束位置keyi-1入栈;
  5. 如果keyi右边还有元素,将右半部分的起始位置keyi+1和结束位置right入栈;
  6. 重复步骤2-5,直到栈为空;
  •  栈的特性为后入先出先将待排序序列的右边界 right入栈后将待排序序列的左边界left入栈;出栈时(获取栈顶元素)就可以先取到左边界值left ,后取到右边界值right;

  •  先获取栈顶元素,然后出栈,先取到0给left,后取到7给right,进行单趟排序(hoare版本)

  •  区间被划分为【left,keyi-1】 U keyi U 【keyi+1, right】(left=0, keyi-1=3,keyi+1=5,right=7),为了先处理划分后的左子序列,先将右子区间的边界值right keyi+1分别入栈(先入right,后入keyi+1) ,然后将左子区间的边界值keyi-1,left分别入栈(先入keyi-1,  后入left);

  •  先取栈顶元素,然后出栈,先取到的元素给left ,后取到的元素给right (left=0, right=3), 进行单趟排序(hoare版本);

  •  keyi左边没有元素,keyi右边还有元素,将右半部分的起始位置keyi+1和结束位置right入栈(right先入栈,keyi+1后入栈)

  •   先取栈顶元素,然后出栈,先取到的元素给left ,后取到的元素给right (left=1, right=3), 进行单趟排序(hoare版本);

  •  keyi左边没有元素,keyi右边还有元素,将右半部分的起始位置keyi+1和结束位置right入栈(right先入栈,keyi+1后入栈)

 

  • 左子区间全部被排完,此时才可以取出5和7排右子区间,右子区间按相同流程处理即可;

顺序栈与链式栈的实现:顺序栈与链式栈_顺序栈和链栈-CSDN博客

//快排非递归实现
void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;
	InitStack(&st);
	StackPush(&st, end);
	StackPush(&st, begin);
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);

		int right = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort(a, left, right);
		//[left keyi-1] keyi [keyi+1 right]
		if (right > keyi + 1)
		{
			StackPush(&st, right);
			StackPush(&st, keyi+1);
		}
		if (keyi - 1 > left)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}
	DestroyStack(&st);
}

快速排序特性总结

1. 时间复杂度

因为每次排序都将待排序序列分成两部分,每部分的长度大约为原序列的一半,因此需要进行logn次排序,每次排序的时间复杂度为O(n),所以快速排序的时间复杂度为O(n*logn)

2. 空间复杂度

因为每次排序需要使用递归调用,每次调用需要使用一定的栈空间,所以快速排序的空间复杂度为O(logn)

3. 算法稳定性

快速排序的算法不稳定,这是因为在排序过程中,可能会出现相同元素的相对位置发生变化的情况;当待排序序列中存在多个相同的元素时,快速排序可能会将它们分到不同的子序列中,从而导致它们的相对位置发生变化;

归并排序

归并排序的基本思想:

将待排序的序列分成若干个子序列,每个子序列都是有序的,然后再将这些有序的子序列合并成一个大的有序序列;

具体实现过程通常采用递归的方法,将序列递归地分成两半,对每一半分别进行归并排序,最后将两个有序的子序列合并成一个有序的序列;在合并的过程中,需要开辟一个数组来存储合并后的序列,然后再将临时数组中的元素拷贝回原数组中;

归并排序的基本思想可以总结为以下三个步骤:

  1. 分割:将待排序的序列分成若干个子序列,每个子序列都是有序的;
  2. 合并:将有序的子序列归并到开辟后的数组形成一个大的有序序列;
  3. 复制:将临时数组中的元素复制回原数组中;

归并排序的实现步骤:

  1. 分割:将待排序数组从中间位置分成两个子数组,直到每个子数组只有一个元素为止;
  2. 归并:将两个有序子数组合并成一个大的有序数组;
    • 开辟一个新数组,新数组的大小与原数组大小相同,定义三个指针,分别指向两个子数组和新数组;
    • 比较两个子数组的第一个元素,将较小的元素放入新数组中,并将指向该元素的指针向后移动一位;
    • 重复上一步,直到其中一个子数组的元素全部放入新数组中;
    • 将另一个子数组中剩余的元素依次放入新数组中;

归并排序的代码实现

//归并排序(递归)
//将待排序序列不断二分,直到每个子序列只有一个元素为止,只有一个元素,序列一定有序;
//将相邻的两个子序列合并成一个有序的序列,直到所有子序列都被合并成一个完整的序列;
void SubMergeSort(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;
	//划分区间为[begin,mid]U[mid+1,end]
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	//递归终止的条件为区间只包含一个元素或者区间不存在;
	//后序遍历
	SubMergeSort(a, tmp, begin1, end1);
	SubMergeSort(a, tmp, begin2, end2);
//首先归并到tmp数组,然后拷贝到原数组;
	int index = begin;//tmp数组下标
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//begin1>end1与begin2>end2至少有一个发生
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//拷贝到原数组
	memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		perror("malloc failed");
		return;
	}
	SubMergeSort(a, tmp, 0, n - 1);
}

归并排序的特性总结

1. 时间复杂度

归并排序的时间复杂度可以通过递归树来分析;在递归树中,每个节点表示对一个区间进行排序的时间复杂度,而每个节点的子节点表示对该区间的两个子区间进行排序的时间复杂度,因此,递归树的深度为logn,每层的时间复杂度为O(n),因此归并排序的时间复杂度为O(nlogn)

2. 空间复杂度

归并排序的空间复杂度为O(n),因为在排序过程中需要创建一个长度为n的临时数组来存储排序结果;

3. 算法稳定性

归并排序是一种稳定的排序算法,因为在合并两个有序子序列的过程中,如果两个元素相等,那么先出现的元素会先被放入结果数组中,保证了排序的稳定性;

计数排序

计数排序的基本思想:

计数排序不是一个基于比较的排序算法,是记录数据出现次数的一种排序算法;计数排序使用一个额外的count数组,其中第i个元素是待排序数组中值等于i的元素的个数,然后根据count数组来将待排序数组中的元素排到正确的位置;

计数排序的实现步骤:

  1. 遍历原数组,找出原数组中的最大值max,最小值min;
  2. 创建count数组,数组大小为max-min+1,并将其元素初始化为0;
  3. 将原数组里面的值减去原数组最小值min作为count数组的下标映射下来,而count数组里面存放的值就是原数组里面值出现的次数;
  4. 从前向后依次填充数组,填充数组时,只需要加上这个最小值,就能还原出原来的值;

计数排序的代码实现

//计数排序
void CountSort(int* a, int n)
{
	//寻找最大值,最小值
	int min = a[0];
	int max = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
			min = a[i];
		
		if (a[i]>max)
			max = a[i];
	}
	//确定新数组count的大小
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int)*range);
	if (count == NULL)
	{
		perror("malloc failed:");
		return;
	}
	//新数组全部初始化为0,方便计数
	memset(count, 0, sizeof(int)*range);
	//统计数据出现的次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
   //从前向后依次填充原数组
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
	free(count);
}

计数排序的特性总结

1. 时间复杂度

计数排序的时间复杂度与待排序元素的范围相关,其时间复杂度为O(n+k),其中n为元素数量,k为元素的范围(即最大的元素与最小的元素的差加1);

2. 空间复杂度

计数排序需要额外开辟的空间大小k=max+min-1,所以空间复杂度为O(k)

3. 算法稳定性

计数排序是一个非基于比较的线性时间排序算法,是一种稳定排序

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

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

相关文章

赴日开发做什么?日本签证很难拿?

日本的IT行业历史比较悠久&#xff0c;业务以上层前端业务为主&#xff0c;如设计和构建软件。日本IT公司组织庞大&#xff0c;行业内部有着严格的分工和部署&#xff0c;工作会被细分化。分配给个人的工作量不会太大&#xff0c;难度也不会很高。 在日本IT公司就业&#xff0…

【古月居《ros入门21讲》学习笔记】06_ROS常用命令行工具

目录 说明&#xff1a; 1. 回顾小海龟案例 终端1&#xff1a;启动ROS master 终端2&#xff1a;启动小海龟仿真器 终端3&#xff1a;启动海龟控制节点&#xff1a; 2. 系统计算图&#xff1a;rqt_graph 3. rosnode rosnode list&#xff1a;显示节点列表 rosnode info&…

LESS的叶绿素荧光模拟实现——任意波段荧光模拟

目录 前言一、任意波段荧光模拟的实现二、需要注意的输入参数 前言 此专栏默认您对LESS (LargE-Scale remote sensing data and image Simulation framework) 模型和叶绿素荧光(Sun-Induced chlorophyll Fluorescence, SIF)有一定的了解。当然&#xff0c;您也可以在这里下载中…

NCo3.1(08) - Nco3 服务器端编程

本篇博文不再重复ABAP调用外部服务器的基础&#xff0c;只介绍 NCo3 开发的过程和要点。需要了解相关知识点的小伙伴们自行参考&#xff1a; SAP接口编程 之JCo3.0系列(06) - Jco服务器端编程 PyRFC 服务器端编程要点 创建项目 新建一个 Console 项目&#xff0c;选择 .Net …

(亲测有效)解决windows11无法使用1500000波特率的问题

大家好&#xff01;我是编码小哥&#xff0c;欢迎关注&#xff0c;持续分享更多实用的编程经验和开发技巧&#xff0c;共同进步。 1、问题描述 从图1可以看出串口是正常的&#xff0c;安装的驱动是CP210xVCPInstaller_x64.exe&#xff0c;但是从图2可以看出&#xff0c;串口拒…

C# WPF 基础教程——触发器、行为、形状、变换与透明、路径和几何图形

触发器 简单触发器 单条件触发器 多条件触发器 事件触发器 行为 形状 矩形和椭圆 Viewbox缩放控件&#xff0c;直线&#xff0c;折线&#xff0c;多边形 画刷 普通画刷 线性渐变画刷 环形渐变画刷 位图画刷 虚拟画刷&#xff08;复制元素外观&#xff09; 位图缓存画刷 变换…

对二分搜索的理解 Go语言版

二分搜索大家都很熟悉&#xff0c;首先我们先来看看基本框架 func binarySearch(nums []int, target int) int {left, right : 0, ...for ... {mid : left (right-left)/2if nums[mid] target {...} else if nums[mid] < target {left ...} else if nums[mid] > targ…

探索测试开发工程师的通往成功的秘密路径!

「作者说」随着近几年国内IT行业高速发展&#xff0c;对测试工程师的要求也越来越高&#xff0c;其作用也越来越重要&#xff0c;但很多测试工程师也迎来了个人发展的瓶颈&#xff0c;下一步该向哪个方向发展&#xff0c;该如何发展&#xff1f;本文将概述测试工程师的现状及发…

使用MAT分析内存泄漏(mac)

前言 今天主要简单分享下Eclipse的Memory Analyzer在mac下的使用。 一、Mat&#xff08;简称&#xff09;干什么的&#xff1f; 就是分析java内存泄漏的工具。 二、使用步骤 1.下载 mac版的现在也分芯片&#xff0c;别下错了。我这里是M2芯片的&#xff0c;下载的Arch64的。 …

海康运行管理中心 RCE漏洞复现

0x01 产品简介 海康威视是以视频为核心的智能物联网解决方案和大数据服务提供商。海康运行管理中心是一款功能强大、易于使用的安防管理平台&#xff0c;能满足用户对视频监控、报警管理、设备配置和数据统计等方面的需求&#xff0c;帮助用户建立高效、智能的安防系统。 0x02…

tcpdump使用心得

参考原文 https://danielmiessler.com/p/tcpdump/ 几个用例 tcpdump -i eth0 显示eth0网卡当前所有的抓包情况eth0是网卡名&#xff0c;可以通过ifconfig获得&#xff0c;也可以通过 tcpdump -D 显示当前可以监听的网卡 -i 参数表示接口&#xff0c;后跟要监听的网卡 tcpdu…

MySQL 中的锁(三)

8.7. 死锁和空间锁 一般来说&#xff0c;只要有并发和加锁这两种情况的共同加持下&#xff0c;都会有死锁的身影。 死锁的具体成因&#xff0c;借用我们在并发编程中的内容&#xff1a; 8.7.1. 死锁 8.7.1.1. 概念 是指两个或两个以上的进程在执行过程中&#xff0c;由于竞…

二阶龙格塔库积分法求解混沌产生方程(求助)

最近论文中常常接触到激光产生混沌的方程&#xff0c;激光器作为非线性元件&#xff0c;在信息处理中具有非常大的潜力&#xff0c;其中激光产生混沌应用在通信中很有用处。论文中对于模拟数据部分&#xff0c;采用了以下公式来产生混沌&#xff1a;以此公式产生混沌的方法应用…

滴滴打车崩了!全过程

滴滴发布致歉10元补偿券&#xff0c;文末可领取 。 事情发生于 2023年11月27日晚~28日中午&#xff0c;滴滴打车服务出现大面积故障&#xff0c;登上微博热搜。 许多用户在使用滴滴出行时遇到了无法叫车、订单异常等问题&#xff0c;导致大量用户滞留在外&#xff0c;出行受阻…

基于Django+Tensorflow卷积神经网络鸟类识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介系统概述系统功能核心技术系统架构系统优势 二、功能三、系统四. 总结  总结 一项目简介 介绍一个基于DjangoTensorflow卷积神经网络鸟类识别系统是一个非…

Linux常用命令——rm 命令

文章目录 Linux系统中的rm命令是一个非常强大且危险的工具&#xff0c;用于删除文件和目录。由于其具有不可逆的特性&#xff0c;了解其参数和正确使用非常重要。 1. 基本用法 rm命令的基本格式是rm [选项] 文件或目录。不带任何选项时&#xff0c;rm命令仅删除文件。 示例&a…

Cytoscape软件下载、安装、插件学习[基础教程]

写在前面 今天分享的内容是自己遇到问题后&#xff0c;咨询社群里面的同学&#xff0c;帮忙解决的总结。 关于Cytoscape&#xff0c;对于做组学或生物信息学的同学基本是陌生的&#xff0c;可能有的同学用这个软件作图是非常溜的&#xff0c;做出来的网络图也是十分的好看&am…

【上海大学数字逻辑实验报告】二、组合电路(一)

一、 实验目的 熟悉TTL异或门构成逻辑电路的基本方式&#xff1b;熟悉组合电路的分析方法&#xff0c;测试组合逻辑电路的功能&#xff1b;掌握构造半加器和全加器的逻辑测试&#xff1b;学习使用可编程逻辑器件的开发工具 Quartus II设计电路。 二、 实验原理 异或门是数字…

基于单片机智能电子密码锁设计

**单片机设计介绍&#xff0c;基于单片机智能电子密码锁设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能电子密码锁设计是一种利用单片机&#xff08;如Arduino、Raspberry Pi等&#xff09;和相关电子元件来…

纹理烘焙:原理及实现

纹理烘焙是计算机图形学中常见的技术&#xff0c;用于将着色器的细节传输到纹理中。 如果你的着色器计算量很大&#xff0c;但会产生静态结果&#xff0c;例如&#xff0c;这非常有用。 复杂的噪音。 NSDT在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器…
最新文章