目录

快速排序

一. 快速排序递归的实现方法

1. 左右指针法

步骤思路

为什么要让end先走?

2. 挖坑法

步骤思路

3. 前后指针法

步骤思路

二. 快速排序的时间和空间复杂度

1. 时间复杂度

2. 空间复杂度

三. 快速排序的优化方法

1. 三数取中优化

2. 小区间优化

四. 使用栈来实现非递归快排

步骤思路

归并排序

​编辑

一. 归并排序的递归实现

步骤思路

二. 时间复杂度与空间复杂度

1. 时间复杂度

2. 空间复杂度

三. 非递归实现归并排序

步骤思路

排序算法的稳定性


快速排序

一. 快速排序递归的实现方法

1. 左右指针法
步骤思路

(假设排升序)将数组a最左边的下标用begin记录下来,最右边用end记录下来,定义一个key为begin或end

(假设key定义为begin)end向左查找找到<a[key]的数停下begin再向右查找找到>a[key]的值停下,此时将begin指向的值与end指向的值交换,以此类推直到end的值<=begin,将此时的a[key]与begin与end相遇坐标的值交换,我们发现此时的a[key],左边的值都比其小,右边的值都比其大,那就说明key所指向的值在数组中已经排好位置了

如以下代码,即完成了单趟

		int key = left;int begin = left, end = right;while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);

我们在end和begin寻找比a[key]大或小的值的时候不要忘记也要判断循环成立的条件

既然key已经在数组排好位置,我们接下来递归就不需要加上key了,只需要递归key的左右区间即可,直到递归的区间左边与右边相等即只有一个数

完整代码如下

void QuickSort1(int* a, int left,int right)
{if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int begin = left, end = right;while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);QuickSort1(a, left, begin - 1);QuickSort1(a, begin + 1, right);
}
为什么要让end先走?

左边做key右边先走,可以保证相遇位置比key小
相遇场景分析

begin遇end:end先走,停下来,end停下条件是遇到比key小的值,end停下来的位置一定比key小,begin没有找到大的遇到end停下了
end遇begin:end先走,找小,没有找到比key更小的,直接跟begin相遇了。begin停留的位置是上一轮交换的位置(即,上一轮交换,把比key小的值,换到begin的位置了)
同样道理让右边做key,左边先走,可以保证相遇位置比key要大  

2. 挖坑法

步骤思路

(假设排升序,给数组a)将最左边的值定义key存储起来,最左边的下标用bigen记录,最右边的下标用end记录,定义pivot记录为最左边的下标,即将最左边视为坑位

然后end向左寻找比key小的值放到pivot所指向的位置即坑位中,并将这个地方(end所找到的)视作新的坑(更新pivot的值)。

begin向右寻找比key大的值,放到坑位中,并将这个地方视作新的坑(更新pivot的值)

重复以上步骤直到end<=begin

然后将key填进pivot中,再通过递归,即可完成排序

由于与左右指针法类似就不写单趟,直接上完整代码

void QuickSort2(int* a, int left, int right)
{if (left >= right)return;int key = a[left];int begin = left, end = right;int pivot = left;while (begin < end){while (a[end] >= key && begin < end){end--;}a[pivot]=a[end];pivot = end;while (a[begin] <= key && begin < end){begin++;}a[pivot] = a[begin];pivot = begin;}a[pivot] = key;QuickSort2(a, left, pivot - 1);QuickSort2(a, pivot + 1, right);
}
3. 前后指针法

步骤思路

(假设排升序)定义key为数组最左边的下标,并定义,prev=key与after=key+1

after在找到比key指向的值小的值时,prev++,并将after指向的值与现在的prev(即prev++后的值)交换

以此往复,直到after>数组的值

然后将prev所指向的值与key所指向的值交换

代码如下

我们要注意,当prev++后的值==after就会发生与自身交换

完成一次后,效果依然是a[key]左区间的值比其小,右区间的值比其大

	int key = left;int prev = left, after = left + 1;while (after<=right){while (a[after] < a[key]&&++prev!=after){Swap(&a[prev], &a[after]);}after++;}Swap(&a[prev], &a[key]);

递归是和上面两种方法同样的道理

完整代码如下

void QuickSort3(int* a,int left,int right)
{if (left >= right)return;int key = left;int prev = left, after = left + 1;while (after<=right){while (a[after] < a[key]&&++prev!=after){Swap(&a[prev], &a[after]);}after++;}Swap(&a[prev], &a[key]);QuickSort3(a, left, prev - 1);QuickSort3(a, prev + 1, right);
}

二. 快速排序的时间和空间复杂度

1. 时间复杂度

①最好情况

每次的划分都使得划分后的子序列长度大致相等,一般在数据已经部分有序或者随机分布的情况下发生。此时时间复杂度为O(Nlog₂N)

②最坏情况

待排序序列有序的情况下,每一次划分的两个区间都有一个为0,此时快速排序的时间复杂度退化为O(N²)

③平均情况

实际应用中快速排序的平均情况大概会接近于最好情况,因为待排序序列通常不是有序的,我们还可以通过三数取中来优化,减少最坏情况的可能性,所以快速排序的时间复杂度为O(Nlog₂N)

2. 空间复杂度

由于需要递归调用,相当于求递归树的深度,

①最坏情况

当数组接近有序时,递归深度很深,空间复杂度为O(N)

②最好情况

当数组无序时,递归树基本相当与完全二叉树,空间复杂度为O(log₂N)

③平均情况

实际应用中,平均情况大概会接近最好情况,同样可以用三数取中优化

所以快速排序空间复杂的为O(log₂N)

三. 快速排序的优化方法

1. 三数取中优化

为了让每次左右区间长度接近,我们可以使用三数取中,即最左边最右边与中间的值取不大也不小的一个值并返回

int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right])return mid;else if (a[left] < a[right])//上面if条件不成立可得a[right]<a[mid]return right;else//又可得 a[left] > a[right]return left;}else//a[left]>=a[mid]{if (a[mid] > a[right])return mid;else if (a[left]<  a[right])//上面if条件不成立可得a[right]>a[mid]return left;else//又可得 a[left] < a[right]return right;}}

将返回值接收并将其指向位置与最左边的值交换,代码如下

		if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;
2. 小区间优化

当快速排序要排的数据很长时,越递归到后面区间越小递归的层数越多,我们可以考虑,当要递归区间小于10的时候用别的排序来代替,这样就可以省去80%到90%的递归

代码如下

void QuickSort1(int* a, int left,int right)
{if ( (right-left+1)<10)//小区间优化{InsertSort(a+left, right - left + 1);//a+left 有可能是后半段区间//减少递归层数}else{if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int begin = left, end = right;while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);QuickSort1(a, left, begin - 1);QuickSort1(a, begin + 1, right);}
}

四. 使用栈来实现非递归快排

栈的实现可以看一下我以前的博客

栈的实现详解-CSDN博客

步骤思路

初始化栈后,将数组的最右边与最左边分别放入栈(即将一个区间放入栈中)

进入循环(当栈为空时循环结束),用begin和begin1接收栈顶端的值,再删除栈的值,再用end和end1接收栈顶端的值,再删除栈的值,使用左右指针法(挖坑法,前后指针法皆可)(用begin与end来寻找值,begin1与end1不变)进行一趟排序,

如果right1>=begin+1 就往栈里存 right1(当前排序区间的最右边) 和 begin+1 反之不存

如果left1<=begin-1 就往栈里存  begin-1 和 left1(当前排序区间的最左边)  反之不存

最后不要忘记销毁栈

代码如下

void StackQuickSort(int* a, int left, int right)
{ST s;StackInit(&s);StackPush(&s, right);StackPush(&s, left);while (!StackEmpty(&s)){int begin = StackTop(&s);int left1 = begin;StackPop(&s);int end = StackTop(&s);int right1= end;StackPop(&s);int key = begin;//int mid = GetMid(a, begin, end);//Swap(&a[mid], &a[begin]);while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);if(right1>=begin+1){StackPush(&s,right1);StackPush(&s, begin + 1);}if(left1<=begin-1){StackPush(&s, begin - 1);StackPush(&s, left1);}}StackDestroy(&s);
}

归并排序

一. 归并排序的递归实现

步骤思路

malloc一个临时数组进入子函数(创建子函数递归会更方便),进行递归,子函数利用分治思想一直递归直到left>=right 开始执行下面操作

k赋初值为当前区间最左边begin1 , end1来记录左数组最左边和最右边,定义begin2 ,end2 来记录右数组的最左边和最右边,将两个数组从头比较,较小的赋值给临时数组,直到有一方赋完值,再将没赋完值的数组给临时数组赋值。最后给要排序数组left到right赋值为临时数组left到right

代码如下

//递归
void _MergeSort(int* a,int* tmp, int left, int right)
{if(left>=right){return;}int mid = (left + right) / 2;//如果[left,mid][mid+1,right]有序就可以归并了_MergeSort(a,tmp, left, mid);_MergeSort(a,tmp, mid + 1, right);int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int k=left;while (begin1 <= end1&&begin2<=end2){if(a[begin1]<a[begin2]){tmp[k++] = a[begin1++];}else{tmp[k++] = a[begin2++];}}while (begin1 <= end1){tmp[k++] = a[begin1++];}while (begin2 <= end2){ tmp[k++] = a[begin2++];}for (int i = left; i <= right; i++){a[i] = tmp[i];}}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}//_MergeSort(a, tmp, 0, n - 1);_MergeSort2(a,tmp,  n);free(tmp); tmp = NULL;
}

二. 时间复杂度与空间复杂度

1. 时间复杂度

归并排序的时间复杂度是稳定的,不受输入数组的初始顺序影响

 将数组分成两个子数组的时间复杂度为O(1),递归对子数组进行排序,假设每个子数组长度为n

则两个子数组排序的总时间复杂度为O(NlogN),将两个有序数组合并为一个有序数组时间复杂度为O(N),所以归并排序时间复杂度为O(NlogN)

2. 空间复杂度

调用栈所需要的额外空间为O(logN),因为我们需要一个额外数组来存储数据所以又额外消耗O(N)的空间,我们将较小的O(logN)忽略可以得到归并排序的空间复杂度为O(N)

三. 非递归实现归并排序

步骤思路

开辟动态空间后定义一个数gap=1来控制区间(gap相当于每组数据个数),(每一次gap*2,使每次区间扩大)gap<数组长度

设计一个for循环i+=gap*=2

每次分两组[i][i+gap-1]和[i+gap][i+2*gap-1]  (i每次+=正好跳过这些数据)

将两个区间的值比较放入新开辟的数组,再拷贝到原数组

代码如下

//非递归
void _MergeSort2(int* a,int* tmp,int n)
{int gap = 1;while(gap<n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int k = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[k++] = a[begin1++];}else{tmp[k++] = a[begin2++];}}while (begin1 <= end1){tmp[k++] = a[begin1++];}while (begin2 <= end2){tmp[k++] = a[begin2++];}//memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));for (int j = i; j < k; j++){a[j] = tmp[j];}}gap *= 2;}
}

但是我们发现,这样如果会发生越界的现象

一共三种可能

1. [begin1,end1][begin2,end2]  end2越界
2. [begin1,end1][begin2,end2]  begin2,end2越界
3. [begin1,end1][begin2,end2]  end1,begin2,end2越界

 第2,3种我们可以直接不递归了,因为后面区间的不存在前面区间的在上一次已经递归好了,

第一种呢我们需要把区间(即end)给修正一下

修正代码如下

//非递归
void _MergeSort2(int* a,int* tmp,int n)
{int gap = 1;while(gap<n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int k = i;if (begin2 >= n)//第二种情况,第二组不存在,不需要归并break;if (end2 >= n)//第一种情况,需要修正一下end2 = n - 1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[k++] = a[begin1++];}else{tmp[k++] = a[begin2++];}}while (begin1 <= end1){tmp[k++] = a[begin1++];}while (begin2 <= end2){tmp[k++] = a[begin2++];}//memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));for (int j = i; j < k; j++){a[j] = tmp[j];}}gap *= 2;}
}

排序算法的稳定性

假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变

原序列中 r[i]=r[j],且r[i]在r[j]之前而在排序后的序列中r[i]仍在r[j]前,则称这种排序算法是稳定的,否则是不稳定的

冒泡选择稳定
选择排序不稳定***只会考虑自身,假如找到最小值1下标为3,将其与下标为0(假设此处为6)处交换若下标为1处也是6,就改变了
直接插入排序稳定
希尔排序不稳定(分组)预排序时相同的值可能分到不同的组
堆排序不稳定建堆时可能就乱了
归并排序稳定当两个数相等,让第一个下来就是稳定的(可以控制)
快速排序不稳定end先找到 j 和begin交换了,在找到 i 和bigin交换,显然改变了

这篇文章就到这里了,感谢大家阅读

(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/46077.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/46077.shtml
英文地址,请注明出处:http://en.pswp.cn/web/46077.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验丰富、原创改进!|多策略改进蜣螂优化算法(MATLAB)

本文内容来源于本人公众号&#xff1a;KAU的云实验台&#xff0c;更新内容&#xff1a;智能优化算法及其改进应用。 本文核心内容&#xff1a; 新颖的多策略改进蜣螂优化算法 对比算法包括&#xff1a;高引用/新发布/经典/其他DBO变体&#xff08;共11种&#xff09; 实验设计…

用c语言写一个贪吃蛇游戏

贪吃蛇游戏通常涉及到终端图形编程和简单的游戏逻辑。以下是一个基本的实现示例&#xff0c;包括贪吃蛇的移动、食物生成、碰撞检测等功能。 1. 贪吃蛇游戏的基本结构 贪吃蛇游戏可以分为以下几个部分&#xff1a; 游戏地图和终端绘制&#xff1a;使用二维数组表示游戏地图&am…

SpringBoot结合ip2region实现博客评论显示IP属地

你好呀&#xff0c;我是小邹。 在现代的Web应用中&#xff0c;特别是博客和论坛类网站&#xff0c;为用户提供地理定位服务&#xff08;如显示用户所在地理位置&#xff09;可以极大地增强用户体验。本文将详细探讨如何使用Java和相关技术栈来实现在博客评论中显示用户的地址信…

Java实验3

实验内容 学生信息管理系统 学生成绩表Student(Sno 字符串&#xff0c;长度9, Sname 字符串&#xff0c;长度10, Class 字符串&#xff0c;长度10, Age 整型, Sex 字符串&#xff0c;长度2) 实现如下功能&#xff1a; A&#xff0e;输入若干个学生的信息到Student表&#x…

初学Python必须知道的14个强大单行代码

引言&#xff1a;Python的魅力与单行代码的重要性 Python以其简洁明了的语法、丰富的内置函数和强大的第三方库深受广大开发者喜爱。尤其对于编程小白来说&#xff0c;学习Python就像打开了一扇通向编程世界的大门。而单行代码&#xff0c;作为Python魅力的一部分&#xff0c;…

【NetTopologySuite类库】合并所有几何的包围盒AABB

流程示意图 示例代码 using GeoAPI.Geometries; using Microsoft.VisualStudio.TestTools.UnitTesting; using NetTopologySuite.Geometries; using NetTopologySuite.IO; using System.Collections.Generic; using System.Linq;namespace Test472 {[TestClass]public class T…

深度解析:电商订单API及其技术实现

随着电子商务的发展&#xff0c;实体企业开拓电商渠道的越来越多&#xff0c;原有的管理系统都需要增加电商业务管理功能&#xff0c;其中&#xff0c;对电商订单的管理是每一个电商商家都需要的功能&#xff0c;所以对于开发者来说&#xff0c;了解电商API是什么是非常重要的&…

第100+16步 ChatGPT学习:R实现Xgboost分类

基于R 4.2.2版本演示 一、写在前面 有不少大佬问做机器学习分类能不能用R语言&#xff0c;不想学Python咯。 答曰&#xff1a;可&#xff01;用GPT或者Kimi转一下就得了呗。 加上最近也没啥内容写了&#xff0c;就帮各位搬运一下吧。 二、R代码实现Xgboost分类 &#xff08…

LeetCode题练习与总结:比较版本号--165

一、题目描述 给你两个 版本号字符串 version1 和 version2 &#xff0c;请你比较它们。版本号由被点 . 分开的修订号组成。修订号的值 是它 转换为整数 并忽略前导零。 比较版本号时&#xff0c;请按 从左到右的顺序 依次比较它们的修订号。如果其中一个版本字符串的修订号较…

C++动态内存的管理

今天来分享C动态内存管理相关知识&#xff0c;闲言勿谈&#xff0c;直接上干货。 1. 动态内存的开辟和销毁(new和delete) (1)前置知识&#xff1a;我们知道c语言有malloc和calloc和realloc三个函数可以进行动态的开辟内存&#xff0c;那么它们有什么区别呢&#xff1f;首先是…

MPS 后端

本文来自&#xff1a; https://pytorch.org/docs/stable/notes/mps.html https://pytorch.ac.cn/docs/stable/notes/mps.html MPS 后端 mps 设备支持 在使用 Metal 编程框架的 MacOS 设备上&#xff0c;进行高性能 GPU 训练。 它引入了新的设备&#xff0c;将机器学习计算图和…

【C语言】条件运算符详解 - 《 A ? B : C 》

目录 C语言条件运算符详解1. 条件运算符的语法和使用示例 1&#xff1a;基本用法输出 2. 嵌套条件运算符示例 2&#xff1a;嵌套条件运算符输出 3. 条件运算符与 if-else 语句的比较示例 3&#xff1a;使用 if-else 语句示例 4&#xff1a;使用条件运算符 4. 条件运算符的实际应…

PLC_博图系列☞基本指令”TONR:时间累加器“

PLC_博图系列☞基本指令”TONR&#xff1a;时间累加器“ 文章目录 PLC_博图系列☞基本指令”TONR&#xff1a;时间累加器“背景介绍TONR&#xff1a; 时间累加器说明参数脉冲时序图示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 TONR 背景介绍 这是一篇关于P…

ElasticSearch学习之路

前言 为什么学ElasticSearch&#xff1f; 数据一般有如下三种类型&#xff1a; 结构化数据&#xff0c;如&#xff1a;MySQL的表&#xff0c;一般通过索引提高查询效率非结构化数据&#xff0c;如&#xff1a;图片、音频等不能用表结构表示的数据&#xff0c;一般保存到mong…

Linux C++ 054-设计模式之外观模式

Linux C 054-设计模式之外观模式 本节关键字&#xff1a;Linux、C、设计模式、外观模式 相关库函数&#xff1a; 概念 外观模式&#xff08;Facade&#xff09;&#xff0c;亦称“过程模式”。主张按照描述和判断资料来评价课程&#xff0c;关键的活动是在课程实施的全过程中…

昇思25天学习打卡营第24天|基于MindSpore的Diffusion扩散模型

Diffusion扩散模型 本文基于Hugging Face&#xff1a;The Annotated Diffusion Model一文翻译迁移而来&#xff0c;同时参考了由浅入深了解Diffusion Model一文。 关于扩散模型&#xff08;Diffusion Models&#xff09;有很多种理解&#xff0c;本文的介绍是基于denoising di…

基础动态规划题目基础动态规划题目

目录 题目1&#xff1a; P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles 代码示例&#xff1a; 题目2&#xff1a; Common Subsequence 代码示例 题目3 &#xff1a;最长上升子序列 最长不下降子序列 最长上升子序列oj答案 题目1&#xff1a; P1216 [USACO1.5]…

SQL面试题练习 —— 查询每个用户最大连续登录天数

目录 1 题目2 建表语句3 题解 1 题目 查询每个用户最大连续登录天数 样例数据如下 login_log&#xff1a; 2 建表语句 --建表语句 create table if not exists login_log (user_id int comment 用户id,login_time date comment 登录时间 ); --数据插入 INSERT overwrit…

Matlab进阶绘图第63期—带标记线的三维填充折线图

三维填充折线图是在三维折线图的基础上&#xff0c;对其与XOY平面之间的部分进行颜色填充&#xff0c;从而能够更好地刻画细节变化。 而带标记线的三维填充折线图是在其基础上&#xff0c;添加X相同的一条或多条标记线&#xff0c;以用于进一步讨论分析。 由于Matlab中未收录…

飞睿智能UWB Tag蓝牙防丢器标签,宠物安全新升级,5cm精准定位测距不迷路

宠物早已成为许多家庭不可或缺的一员&#xff0c;它们用无条件的爱温暖着我们的心房&#xff0c;陪伴我们度过每一个平凡而温馨的日子。然而&#xff0c;随着宠物活动范围的扩大和外界环境的复杂多变&#xff0c;宠物走失的风险也随之增加。每一次出门遛弯&#xff0c;都像是心…