常见排序算法及其稳定性分析

news2024/11/19 19:45:14

前言:

排序算法可以说是每一个程序员在学习数据结构和算法时必须要掌握的知识点,同样也是面试过程中可能会遇到的问题,在早些年甚至还会考冒泡排序。由此可见呢,掌握一些常见的排序算法是一个程序员的基本素养。虽然现在的语言标准库里都有直接的排序函数,但是作为一个学习者,我们应当抱着“知其然,还要知其所以然”的态度去学习。

1.常见的排序算法有哪些?

常见的排序算法及其性能:

算法名称平均时间复杂度稳定性
直接插入排序N^2稳定
希尔排序N^1.25--1.6N^1.25不稳定
选择排序N^2不稳定
堆排序NlogN不稳定
冒泡排序N^2稳定
快速排序NlogN不稳定
归并排序NlogN稳定

这里呢我只讲一些效率比较高的排序算法,比如快排、并排、堆排序、希尔排序。

2.常见排序算法的实现

2.1希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成grap个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序(插入排序)。然后,重复上述分组和排序的工作。当分组间距为1时,所有记录在统一组内排好序。

 ee378545516e471289b7abd8e82c1a73.png

代码实现:


// 希尔排序
void ShellSort(int* a, int n) {
	int gap = n - 1;//grap为分组之间的间隔
	while (gap > 1) {
		gap = gap / 3 + 1;//每次分组的间距都越来越小,直到间距为一
		for (int i = 0; i < gap; i++) {//i表示每组的开头位置
			for (int j = i; j < n - gap; j += gap) {//对每一组插入排序
				int end = j;
				int temp = a[j + gap];
				while (end >= 0) {
					if (temp < a[end]) {
						a[end + gap] = a[end];
						end -= gap;
					}
					else {
						break;
					}
				}
				a[end + gap] = temp;
			}
		}
	}
}

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时(相当于对整个数组进行插入排序),数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好多书中给出的 希尔排序的时间复杂度都不固定,目前也只能给出一个大概的复杂度。

2.2堆排序

关于堆排序在之前的博客中已经详细讲解过,有兴趣的可以去看看。

解决top-k问题--堆排序-CSDN博客文章浏览阅读140次,点赞8次,收藏5次。堆排序将堆的根节点与最后一个节点交换,然后将堆的大小减1,并进行堆的重构。max1>max2,a[i]>max2,所以此时的max2的值已经不是这个数组次大的了。被淘汰说明有前k个数大于自己,加入top-k说明此时的a[i]至少大于目前的maxk。,如果大于此时的maxk,说明就目前来说,a[i]有资格进入这前k大的数,通常的做法是用一个变量max1依次去比较数组中的每一个数,并更新。,它的每个节点的值都大于等于(或小于等于)它的子节点的值。先把此时的maxk的值去掉,算上a[i]之后调整这k个数。https://blog.csdn.net/qq_62987647/article/details/134765613

 2.3快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

 2.3.1递归版

对于单趟排序,我们确定一个基准值key,并利用双指针从首尾两端移动。找到一个位置放key,使得,key左边的元素都小于key(以升序排序为例),右边的元素都大于key

2031d1e64085469ba4845d2139ed7f10.png

快排的算法思想本质是用空间换取时间,每一次递归排序的区间都在不断地缩小,每一趟排序都确定了下一趟排序的左右两个区间,类似于二叉树的节点的左右儿子节点。 

78e8e47004294c32bb72bd941bd1755d.png

代码实现

int PartSort1(int* a, int left, int right) {//单趟排序
	int end = right;
	int begin = left;
	int mid = GetMid(a, left, right);//基准值的下标
	Swap(&a[left], &a[mid]);//跟首元素交换
	int key = left;
	while (begin < end) {
		while (end > begin && a[end] >= a[key])end--;
		while (end > begin && a[begin] <= a[key])begin++;
		Swap(&a[end], &a[begin]);
	}
	Swap(&a[key], &a[begin]);
	return begin;//单趟排序后返回最后key所处的位置
}
void QuickSort(int* a, int left, int right){//递归
	   if (left >= right)return;
	    int mid = PartSort3(a, left, right);
		QuickSort(a, left, mid - 1);
		QuickSort(a, mid + 1, right);
}

 2.3.2非递归版

利用,我们可以将单趟排序确定的两个子区间存起来,模拟函数栈帧的开辟。这样一来,我们就可以不用递归(递归较为耗损空间)就可以完成快速排序。

如果有对函数栈帧不了解的朋友可以去我之前写的博客里面看看。

深入理解函数调用--函数栈帧-CSDN博客文章浏览阅读87次,点赞6次,收藏3次。寄存器:寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。函数栈帧(Function Stack Frame)是在程序执行过程中,用来保存函数调用信息和局部变量的数据结构。它包含了函数的参数、返回地址和局部变量等信息。通俗的来说,就是在调用函数的时候系统给你开辟在栈区的一部分空间,专门用来存放局部变量等。https://blog.csdn.net/qq_62987647/article/details/132663764代码实现

void QuickSortNonR(int* a, int left, int right) {
	Stack ST;
	StackInit(&ST);
	StackPush(&ST, right);//根据栈的特性,先将右区间压栈
	StackPush(&ST, left);
	while (!StackEmpty(&ST)) {
		int l = StackTop(&ST);
		StackPop(&ST);
		int r = StackTop(&ST);
		StackPop(&ST);//取出一个区间[l,r]
		if (l > r)Swap(&l, &r);
		int keyi = PartSort1(a, l, r);//单趟排序
		if (keyi -1> l) {
			StackPush(&ST, keyi - 1);
			StackPush(&ST, l);
		}
		if (keyi + 1 < r) {
			StackPush(&ST, r);
			StackPush(&ST, keyi + 1);
		}
	}
	StackDestroy(&ST);
}

 2.4归并排序

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

实现思路: 

因为要先满足每个子序列都有序的条件,我们可以把区间长度划分为1,这样每个区间都一定是有序的,再对每个区间先上合并。值得注意的是,有序地子区间合并之后也是有序的。同样是根据二叉树的思想,每个区间一分为两个子区间,直到区间长度为1,需要logN的时间复杂度,合并两个区间又是N的复杂度,所以总的时间复杂度为NlogN

2a23d6c617044d108917794c823d362f.png

 代码实现

void MergeSort(int* a, int begin, int end, int* temp) {//temp用于暂时存放合并后的数组
	if (begin >= end)return;
	int mid = (begin + end) / 2;
	MergeSort(a, begin, mid, temp);
	MergeSort(a, mid + 1, end, temp);//先递归区间
	
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;//两个区间的端点指针
	int i = begin;//合并数组的下标的指针
	while (begin1 <= end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2]) {//比较两个区间的首元素,小的放入temp中,两个指针往后移
			temp[i++] = a[begin1++];
		}
		else {
			temp[i++] = a[begin2++];
		}
	}//有可能还有某个区间还有元素没有放进去
	while (begin1 <= end1) {
		temp[i++] = a[begin1++];
	}
	while (begin2 <= end2) {
		temp[i++] = a[begin2++];
	}
	//将合并好的数组复制到原数组中
	memcpy(a + begin, temp + begin, sizeof(int) * (end - begin + 1));
}

3.排序算法的稳定性

对于某种排序算法,如果会将两个相同大小的元素的相对位置改变,那么我们就称这个算法是不稳定的,否者就是稳定的。 

1c9ad664ec9a4a08b2c92fc8bb88f6ae.png

什么时候需要考虑稳定性?

针对多个字段进行排序,就可能需要考虑排序算法的稳定性

 举例:

对以下数据进行排序:

序号订单金额订单时间
1509:04
2309:00
3509:03
4109:01

要求:

是按照订单金额进行升序排序,如果订单金额相同,则按照下单时间升序排序

先按照订单时间升序排序得到序号为:2、4、3、1

再从上一个序号组中按照订单金额升序排序

1.假如排序算法不稳定

则可能得到:4、2、1、3

对于序号1和3,订单金额相同,但是时间小的反而排在后面,不符合要求。

2.假如排序算法稳定

则一定得到:4、2、3、1

对于序号1和3,订单金额相同,下单时间大的排在后面,符合要求。

根据以上举例可以看出来,在对多个字段排序的时候,往往需要稳定的排序算法进行排序。

这也是为什么同样的时间复杂度,在有些时候能用不稳定的快排,有些时候用稳定归并排序。

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高标准农田建设的实施要点

高标准农田应用大数据、物联网、移动互联等现代信息技术&#xff0c;对基地种植区域的气象环境、土壤墒情、病虫害、农事活动等进行实时监测&#xff0c;实现农田种植智能化、经营网络化、管理高效化、服务便捷化&#xff0c;全面提高农田种植现代化水平。在高标准农田的建设方…

git秘钥过期 ERROR: Your SSH key has expired

文章目录 1、错误提示Your SSH key has expired2、登录Github确认3、重新设置秘钥 1、错误提示Your SSH key has expired 使用git命令时遇到Github 的 SSH Key秘钥过期&#xff0c;提示错误ERROR: Your SSH key has expired 2、登录Github确认 首先登录Github查看&#xff…

CSAPP阅读笔记-信息的表示和处理

信息的表示和处理 包括整数、浮点数的存储格式、计算中可能存在的问题等 信息存储 大多数计算机使用8位的块&#xff0c;或者字节(byte)&#xff0c;作为最小的可寻址的内存单位&#xff0c;而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组&#xff0c…

Fedora Linux 中安装 nginx

Fedora 35 中安装 nginx 的方法非常简单。 运行下面的命令&#xff1a; sudo dnf install nginx 在提示你需要确认的地方&#xff0c;输入 y 后回车即可。 开机自动启动 如果你希望在你的操作系统重启的时候自动启动 nginx&#xff0c;请输入下面的命令&#xff1a; syst…

2.2.3机器学习—— 判定梯度下降是否收敛 + α学习率的选择

2.2.3 判定梯度下降是否收敛 α学习率的选择 2.1、 判定梯度下降是否收敛 有两种方法&#xff0c;如下图&#xff1a; 方法一&#xff1a; 如图&#xff0c;随着迭代次数的增加&#xff0c;J(W,b)损失函数不断下降当 iterations 300 之后&#xff0c;下降的就不太明显了 / …

shader技巧

数学函数&#xff1a; abs()&#xff1a;绝对值函数。 acos()&#xff1a;反余弦函数。 asin()&#xff1a;反正弦函数。 atan()&#xff1a;反正切函数。 ceil()&#xff1a;向上取整函数。 cos()&#xff1a;余弦函数。 cross()&#xff1a;向量叉积函数。 distance()&#x…

MyBatis:自定义 typeHandler 处理枚举类型

MyBatis 枚举类型typeHandler 枚举类型 枚举类型&#xff0c;在 Java 中属于基本数据类型&#xff0c;而不是构造数据类型&#xff0c;用于声明一组命名的常数。枚举可以根据 Integer 、Long 、Short 或 Byte 中的任意一种数据类型来创建一种新型变量。这种变量可以设置为已经…

【Python程序开发系列】一文总结API的基本概念、功能分类、认证方式、使用方法和开发流程

这是Python程序开发系列原创文章&#xff0c;我的第195篇原创文章。 一、什么是API&#xff1f; API是软件开发中非常重要的概念&#xff0c;它简化了不同组件之间的交互和集成&#xff0c;提供了对其他软件或服务功能的访问和调用方式。 API是应用程序编程接口&#xff08;Ap…

SSL证书不受信任怎么办? SSL证书不受信任解决方案汇总

随着网络安全问题日益凸显&#xff0c;网站使用SSL证书以实现HTTPS加密及身份的可信认证&#xff0c;防止传输数据的泄露或篡改&#xff0c;已成为互联网人的共识。但SSL证书并不是部署了就能正常使用的&#xff0c;有时浏览器会提示“SSL证书不受信任”&#xff0c;这种时候该…

Java--业务场景:SpringBoot 通过Redis进行IP封禁实现接口防刷

文章目录 前言具体实现步骤1. 定义自定义注解2. 编写拦截器类IpUrlLimitInterceptor3. 在WebConfig类中添加IpUrlLimitInterceptor4. 添加注解到接口上 测试效果参考文章 前言 在实际项目中&#xff0c;有些攻击者会使用自动化工具来频繁刷新接口&#xff0c;造成系统的瞬时吞…

vue配置qiankun及打包上线

项目结构 基座&#xff1a;vue3 子应用A&#xff1a;vue3 子应用B&#xff1a; react 子应用C&#xff1a;vue3vite 项目目录&#xff1a; 配置基座 首先下载qiankun yarn add qiankun # 或者 npm i qiankun -S 所有子应用也要安装&#xff0c;vue-vite项目安装 cnpm ins…

XUbuntu22.04之快速复制绝对路径(二百零五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Java项目:115SSM宿舍管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 宿舍管理系统基于SpringSpringMVCMybatis开发&#xff0c;系统主要功能如下&#xff1a; 学生管理班级管理宿舍管理卫生管理维修登记访客管理 二、技术框…

向量数据库:Milvus

特性 Milvus由Go(63.4%),Python(17.0%),C(16.6%),Shell(1.3%)等语言开发开发&#xff0c;支持python&#xff0c;go&#xff0c;java接口(C,Rust,c#等语言还在开发中)&#xff0c;支持单机、集群部署&#xff0c;支持CPU、GPU运算。Milvus 中的所有搜索和查询操作都在内存中执行…

Phi-2小语言模型QLoRA微调教程

前言 就在不久前&#xff0c;微软正式发布了一个 27 亿参数的语言模型——Phi-2。这是一种文本到文本的人工智能程序&#xff0c;具有出色的推理和语言理解能力。同时&#xff0c;微软研究院也在官方 X 平台上声称&#xff1a;“Phi-2 的性能优于其他现有的小型语言模型&#…

C# WPF 数据绑定

需求 后台变量发生改变&#xff0c;前端对应的相关属性值也发生改变 实现 接口 INotifyPropertyChanged 用于通知客户端&#xff08;通常绑定客户端&#xff09;属性值已更改。 示例 示例一 官方示例代码如下 using System; using System.Collections.Generic; using Sy…

IoT 物联网 MQTT 协议 5.0 版本新特性

MQTT 是一种基于发布/订阅模式的轻量级消息传输协议&#xff0c;专门为设备资源有限和低带宽、高延迟的不稳定网络环境的物联网场景应用而设计&#xff0c;可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于智能硬件、智慧城市、智慧农业、智慧医疗、新零…

Linux:linux计算机和windows计算机 之间 共享资源

在前面章节已经介绍过&#xff0c;NFS用于Linux系统之间的文件共享&#xff0c;windows 并不知道 NFS &#xff0c;而是使用 CIFS (Common Internet File System) 的协议机制 来 “共享” 文件。在1991年&#xff0c;Andrew Tridgell 通过逆向工程 实现了 CIFS 协议&#xff0c…

GAMES101-Assignment5

一、问题总览 在这次作业中&#xff0c;要实现两个部分&#xff1a;光线的生成和光线与三角的相交。本次代码框架的工作流程为&#xff1a; 从main 函数开始。我们定义场景的参数&#xff0c;添加物体&#xff08;球体或三角形&#xff09;到场景中&#xff0c;并设置其材质&…

【Cadence】sprobe的使用

实验目的&#xff1a;通过sprobe测试电路中某个节点的阻抗 这里通过sprobe测试输入阻抗&#xff0c;可以通过port来验证 设置如下&#xff1a; 说明&#xff1a;Z1代表sprobe往left看&#xff0c;Z2代表sprobe往right看 结果如下&#xff1a; 可以看到ZM1I0.Z2 顺便给出了I…