排序篇(三)----交换排序

news2025/1/14 18:38:51

排序篇(三)----交换排序

1.冒泡排序

基本思想:

​ 通过不断地比较相邻的元素,将较大的元素往后移动,从而实现排序的目的。

具体的步骤如下:

  1. 从待排序的数组中选择相邻的两个元素进行比较,如果前一个元素大于后一个元素,则交换它们的位置。
  2. 继续比较下一对相邻的元素,重复上述步骤,直到将整个数组排序完成。
  3. 重复执行上述步骤,直到没有需要交换的元素,即数组已经完全排序。

​ 冒泡排序的特点是每次只比较相邻的两个元素,每一轮排序都将最大的元素移动到最后,因此称为冒泡。整个排序过程类似于水泡从底部冒到顶部的过程,因此得以闻名.

在这里插入图片描述

//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int exchange = 1;
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);
				exchange = 0;
			}
		}
		if (exchange)
			return;
	}
}

​ 这里面有一个小小的优化,exchange如果在这一趟排序中没有被修改过,代表着这组数据是有序的,因此可以直接return返回.

代码解析:

  1. 在给定的冒泡排序函数中,参数a是要排序的数组,n是数组的长度。
  2. 算法的核心是两层循环。外层循环控制排序的轮数,每一轮都将最大的元素移动到最后。内层循环用于比较相邻的元素并进行交换。
  3. 内层循环中,通过比较a[j-1]和a[j]的大小来判断是否需要交换它们的位置。如果a[j-1]大于a[j],则交换它们的位置,并将exchange标志设置为0,表示本轮循环有元素交换位置。如果没有发生交换,说明数组已经有序,可以提前结束排序。
  4. 外层循环每执行一轮,就会将最大的元素移动到最后,所以内层循环的范围会逐渐减小。每一轮排序都可以确定一个最大的元素的位置,所以外层循环只需要执行n次。

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

2.快速排序(递归)

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

void QuickSort(int* a, int left, int right)
{
// 假设按照升序对array数组中[left, right)区间中的元素进行排序
	if (right <= left)
		return;
// 按照基准值对array数组的 [left, right)区间中的元素进行划分
	//int keyi = PartSort1(a, left, right);
	//int keyi = PartSort2(a, left, right);
	int keyi = PartSort3(a, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, keyi-1) 和 [keyi+1, right)
// 递归排[left, keyi-1)
	QuickSort(a, left, keyi - 1);
// 递归排[keyi+1, right)
	QuickSort(a, keyi + 1, right);
}

​ 上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,在写递归框架时想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

2.1hoare版本快排

在这里插入图片描述

int PartSort1(int* a, int left, int right)
{
	//三数取中(优化)
	//int keyi = NumBers(a, left, right);
	//Swap(&a[keyi], &a[left]);
	int key = left;

	while (left < right)
	{
		while (left < right && a[left] <= a[right])
		{
			right--;
		}
		while (left < right && a[left] <= a[right])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	return left;
}

代码解析:

  1. 首先,定义一个变量key,用于保存基准值的下标,初始值为left。
  2. 进入一个循环,循环条件是left < right,即左右指针没有相遇。
  3. 在循环中,首先从右边开始,找到第一个小于等于基准值的元素的下标,将right指针左移,直到找到符合条件的元素或者left和right相遇。
  4. 然后从左边开始,找到第一个大于基准值的元素的下标,将left指针右移,直到找到符合条件的元素或者left和right相遇。
  5. 如果left < right,说明找到了需要交换的元素,将a[left]和a[right]交换位置。
  6. 重复步骤3到步骤5,直到left和right相遇。
  7. 最后,将基准值a[key]和a[left]交换位置,将基准值放在正确的位置上。
  8. 返回分割点的下标left。

​ 实现了一次快速排序的分割操作,将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。然后再通过递归调用这个函数,这就是hoare版的快排.

2.2挖坑法

在这里插入图片描述

int PartSort2(int* a, int left, int right)
{
	//三数取中优化
	//int keyi = NumBers(a, left, right);
	//Swap(&a[keyi], &a[left]);
	int key = a[left];
	int hole = left;//为第一个坑

	while (left < right)
	{
		while (left < right && key <= a[right])
		{
			--right;
		}
		a[hole] = a[right];
		hole = right;

		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

代码解析:

  1. 定义一个变量key,用于保存基准值,初始值为a[left]。
  2. 定义一个变量hole,用于保存空洞的位置,初始值为left。
  3. 进入一个循环,循环条件是left < right,即左右指针没有相遇。
  4. 在循环中,首先从右边开始,找到第一个小于基准值的元素的下标,将right指针左移,直到找到符合条件的元素或者left和right相遇。
  5. 将a[right]的值赋给a[hole],将空洞的位置移动到right。
  6. 然后从左边开始,找到第一个大于基准值的元素的下标,将left指针右移,直到找到符合条件的元素或者left和right相遇。
  7. 将a[left]的值赋给a[hole],将空洞的位置移动到left。
  8. 重复步骤4到步骤7,直到left和right相遇。
  9. 最后,将基准值key放入空洞的位置a[hole],将基准值放在正确的位置上。
  10. 返回空洞的位置hole。

​ 同样实现了将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。

2.3双指针法

在这里插入图片描述

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	//三数取中优化
	//int midi = NumBers(a, left, right);
	//Swap(&a[left], &a[midi]);

	int prev = left;
	int cur = prev + 1;

	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}

		++cur;
	}

	Swap(&a[prev], &a[keyi]);
	return prev;
}

代码解析:

  1. 定义两个指针prev和cur,分别指向left和left+1。
  2. 定义一个变量keyi,用于保存基准值的下标,初始值为left。
  3. 进入一个循环,循环条件是cur <= right,即cur指针没有越界。
  4. 在循环中,如果a[cur]小于基准值a[keyi],则将prev指针右移一位,并交换a[prev]和a[cur]的值,保证prev指针之前的元素都小于基准值。
  5. 将cur指针右移一位。
  6. 重复步骤4到步骤6,直到cur指针越界。
  7. 最后,将基准值a[keyi]和a[prev]交换位置,将基准值放在正确的位置上。
  8. 返回分割点的下标prev。

​ 同样实现了将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。

以上三种方法均可实现快速排序,没有谁优谁劣,挑取自己便于理解的就行

2.4三数取中优化

​ 三数取中是为了选择一个更好的基准值,以提高快速排序的效率。在快速排序中,选择一个合适的基准值是非常重要的,它决定了每次分割的平衡性。

​ 快速排序的基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的小,然后再对这两部分分别进行快速排序,递归地进行下去,直到整个序列有序。

​ 如果每次选择的基准值都是最左边或最右边的元素,那么在某些情况下,快速排序的效率可能会降低。例如,当待排序序列已经有序时,如果每次选择的基准值都是最左边或最右边的元素,那么每次分割得到的两个子序列的长度差可能会非常大,导致递归深度增加,快速排序的效率降低。

​ 而通过三数取中的优化,可以选择一个更好的基准值,使得每次分割得到的两个子序列的长度差更小,从而提高快速排序的效率。

​ 具体来说,三数取中的优化是选择待排序序列的左端、右端和中间位置的三个元素,然后取它们的中值作为基准值。这样选择的基准值相对于最左边或最右边的元素,更接近整个序列的中间位置,可以更好地平衡分割后的两个子序列的长度,从而提高快速排序的效率。

​ 通过三数取中的优化,可以减少递归深度,提高分割的平衡性,使得快速排序的效率更稳定,适用于各种不同的输入情况。

//三数取中
int NumBers(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	// left mid right
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])  // mid是最大值
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right]) // mid是最小
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

2.5小区间优化

​ 小区间优化是指在快速排序中,当待排序的子序列的长度小于一定阈值时,不再继续使用快速排序,而是转而使用插入排序。

void QuickSort(int* a, int left, int right)
{
	if (right <= left)
		return;
	if(right - left + 1 > 10)
	{
        int keyi = PartSort3(a, left, right);
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	else
	{
		InsertSort(a + left,right - left + 1);
	}
}

小区间优化的好处:

  1. 减少递归深度:使用插入排序来处理较小的子序列,可以减少递归的深度,从而减少了函数调用的开销。
  2. 提高局部性:插入排序是一种稳定的排序算法,它具有良好的局部性,可以充分利用已经有序的部分序列。对于较小的子序列,插入排序的效率更高。
  3. 减少分割次数:对于较小的子序列,使用插入排序可以减少分割的次数。快速排序的分割操作需要移动元素,而插入排序只需要进行元素的比较和交换,因此在较小的子序列中使用插入排序可以减少分割操作的次数。

​ 小区间优化可以在一定程度上提高快速排序的性能。它通过减少递归深度、提高局部性和减少分割次数来优化算法的效率,特别适用于处理较小的子序列。

3.快速排序(非递归)

​ 这里需要借助栈的来实现非递归.关于栈详情见:数据结构剖析–栈

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort3(a, begin, end);
		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}
		if (begin < keyi - 1)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}
	StackDestroy(&st);
}

代码解析:

  1. 将整个序列的起始和结束位置入栈。然后,进入循环,不断从栈中取出子序列的起始和结束位置。
  2. 在每次循环中,通过PartSort3函数将当前子序列分割成两部分,并得到基准值的下标keyi。如果基准值右边的子序列长度大于1,则将右边子序列的起始和结束位置入栈。如果基准值左边的子序列长度大于1,则将左边子序列的起始和结束位置入栈。
  3. 循环继续,直到栈为空,表示所有的子序列都已经排序完成。

通过使用栈来模拟递归的过程,非递归实现避免了递归调用的开销,提高了快速排序的效率。

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

  2. 时间复杂度:O(N*logN)
    在这里插入图片描述

  3. 空间复杂度:O(logN)

  4. 稳定性:不稳定

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

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

相关文章

Java编程技巧:swagger2、knif4j集成SpringBoot或者SpringCloud项目

目录 1、springbootswagger2knif4j2、springbootswagger3knif4j3、springcloudswagger2knif4j 1、springbootswagger2knif4j 2、springbootswagger3knif4j 3、springcloudswagger2knif4j 注意点&#xff1a; Api注解&#xff1a;Controller类上的Api注解需要添加tags属性&a…

在云服务器上打开ftp服务-踩坑及心得

我们产生这个需求的原因是因为打算搭建一个博客&#xff0c;选择了使用wordpress框架。然后&#xff0c;在安装插件的过程中&#xff0c;需要使用FTP服务进行操作。于是&#xff0c;我们决定搞清楚这个过程&#xff0c;并在其中遇到的困难进行记录。 一、安装vsftpd # 安装 s…

python生成中金所期权行权价

参考沪深300股指期权的合约表&#xff0c;写一个工具函数&#xff1a; 使用方法 def get_format_option_gap(value: float, deviation: int 0): # 根据中证1000指数获取点位"""根据标准的行权价&#xff0c;生成不同档位的期权列表&#xff0c;适合中金所:…

铁道货车通用技术条件

声明 本文是学习GB-T 5600-2018 铁道货车通用技术条件. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 90 mm90 mm。 B.2 制造要求 B.2.1 车体钢结构组成后&#xff1a; a) 敞车钢质侧、端板的平面度公差应小于或等于15 mm/m; 压型侧、端板的平面度…

S0003-Mac下iTerm2+zsh+ohmyzsh打造优雅美观终端

背景 优雅耐看的终端工具&#xff0c;必是每个程序员的追求。 本人也不例外&#xff0c;从业几年先后使用过&#xff1a; windows电脑&#xff1a;cmd、git bash、wsl zsh、terminal zshMac电脑: 自带terminal、iTerm2、terminal zsh 其中windows terminal zsh、mac ter…

数据结构——二叉树的基本概念及顺序存储(堆)

目录 一.前言 二.树概念及结构 2.1 树的概念 2.2 树的相关概念 2.3 树的表现 2.4 树在实际中的应用&#xff08;表示文件系统的目录树结构&#xff09; 三.二叉树的概念及结构 3.1 概念 3.2 特殊的二叉树 3.3 二叉树的性质 3.4 二叉树的存储结构 3.4.1 顺序存储 3…

Vitamin K

各位 比对 机制 --Mechanism of Action of Vitamin K Carboxylase (VKC).IV. Intermediates and Transition State-davis2007.pdf --Mechanism of Action of Vitamin K Carboxylase (VKC).IV. Intermediates and Transition State-davis2007.pdf -- 维生素K的作用机制-dow…

房子再小,也要有自己的装修设计!福州中宅装饰,福州装修

小空间 也要有好设计 小户型的房子现在越来越受欢迎了 但是面积不大的小户型 怎么装修才能更适合居住呢&#xff1f; 那么多的东西又应该如何收纳呢&#xff1f; 白色系装修 采用白色系为装修的主色调 白色在装修上能让视觉上显得更宽敞 让小面积的房子 变得更多简洁大…

91、Redis - 事务 与 订阅-发布 相关的命令 及 演示

★ 事务相关的命令 Redis事务保证事务内的多条命令会按顺序作为整体执行&#xff0c;其他客户端发出的请求绝不可能被插入到事务处理的中间&#xff0c; 这样可以保证事务内所有命令作为一个隔离操作被执行。 Redis事务同样具有原子性&#xff0c;事务内所有命令要么全部被执…

最新AI智能创作系统源码V2.6.2/AI绘画系统/支持GPT联网提问/支持Prompt应用

一、AI创作系统 SparkAi创作系统是基于国外很火的ChatGPT进行开发的AI智能问答系统和AI绘画系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图…

【C语言】IO流(文件操作)- scanf / printf没那么简单!

本篇文章目录 1. 为什么使用文件&#xff1f;2. 什么是文件&#xff1f;3. IO流的概念4. 操作文件的步骤文件指针4.1 打开文件和关闭文件4.2 读写文件&#xff08;顺序读取&#xff09;4.2.1 字符输入输出4.2.2 字符串&#xff08;文本行&#xff09;输入输出4.2.3 格式化输入输…

SSM - Springboot - MyBatis-Plus 全栈体系(十六)

第三章 MyBatis 三、MyBatis 多表映射 2. 对一映射 2.1 需求说明 根据 ID 查询订单&#xff0c;以及订单关联的用户的信息&#xff01; 2.2 OrderMapper 接口 public interface OrderMapper {Order selectOrderWithCustomer(Integer orderId); }2.3 OrderMapper.xml 配置…

【【萌新的RiscV学习之在写代码之前对于关键路径的分析-11】】

萌新的RiscV学习之在写代码之前对于关键路径的分析-11 首先我们最简单的control 模块 全分段 因为只有分段 &#xff0c; 分开使用之后 &#xff0c; 各个阶段的具体功能才会合理使用 就像是为了后续 “气泡” 赋值 为 0 还有单独比较前递这种 EX &#xff1a; ALUOP ALUSrc …

Arcgis快速计算NDVI

Arcgis快速计算NDVI 一、问题描述 如何使用Arcgis像ENVI一样波段计算NDVI的值&#xff0c;事实上&#xff0c;Arcgis更快速一些。 二、操作步骤 首先准备好影像 打开窗口-影像分析 点击左上角 点击确定 &#xff08;发现自己使用的遥感影像不对劲&#xff0c;是计算好了…

智慧公厕有多智能?智慧厕所黑科技揭秘

随着科技的不断进步&#xff0c;智能化已经渗透到了我们生活的方方面面&#xff0c;智慧公厕也成为了时代的产物之一。那么&#xff0c;智慧公厕究竟有多智能呢&#xff1f;本文将以智慧公厕专业厂家广州中期科技有限公司&#xff0c;大量精品案例项目&#xff0c;以实景实图实…

idea技巧--debug使用技巧

写在前面&#xff1a; idea是java开发主流的ide&#xff0c;debug也是常用的功能&#xff0c;但这个功能有着很多强大好用的技巧。现在来总结一下。 文章目录 条件断点条件的时候求值并记录。命中后移除 基础debug简单的介绍 debug中求值强制返回断点回退 条件断点 从打断点…

HTML5+CSS3+JS小实例:鼠标滚轮水平滚动

实例:鼠标滚轮水平滚动 技术栈:HTML+CSS+JS 效果: 源码: 【html】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" content="…

【深蓝学院】手写VIO第4章--基于滑动窗口算法的 VIO 系统:可观性和 一致性--笔记

0. 内容 由于我们是要做一个实时的定位系统&#xff0c;而不是SfM那种离线的三维重建的工作&#xff0c;所以需要在滑动窗口中不断地添加新数据以及删除旧数据&#xff0c;而前者可以把所有数据放在一个BA中求解。 1. 从高斯分布到信息矩阵 SLAM问题的建模&#xff0c;MAP->…

【卷积神经网络:Inception模型】

【卷积神经网络&#xff1a;Inception模型】 1 Inception模型的组成2 实现代码3 查看经典的Inceptioon模型 1 Inception模型的组成 Inception模型的组成主要由不同卷积核大小的卷积层拼接而成&#xff0c;其中一种带维度缩减的Inception模型可以减少参数量&#xff0c;引入更多…

javaee SpringMVC中json的使用

jsp <%--Created by IntelliJ IDEA.User: 呆萌老师:QQ:2398779723Date: 2019/12/6Time: 15:55To change this template use File | Settings | File Templates. --%> <% page contentType"text/html;charsetUTF-8" language"java" %> <%St…