【初阶数据结构篇】冒泡排序和快速排序(中篇)

news2024/11/24 19:57:22

文章目录

  • 冒泡排序和快速排序
    • 前言
    • 代码位置
    • 冒泡排序
    • 快速排序
      • 递归法实现
        • hoare版本
        • 挖坑法
        • lomuto前后指针
        • 递归法复杂度分析
      • 非递归法实现

冒泡排序和快速排序

前言

本篇以排升序为例

代码位置

gitee

冒泡排序

动图理解
在这里插入图片描述

  • 作为第一个接触的排序算法,冒泡排序想必大家已经很熟悉了
    • 总共n个数据,要排n-1趟
    • 第i(i从0开始取)趟要比较n-1-i次
    • 等差数列求和,最坏时间复杂度为O(n2)
  • 定义exchange变量,当数组已经有序时不进入交换,直接跳出循环
    • 最好时间复杂度为O(n)
  • 空间复杂度O(1)
void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		int exchange = 0;
		for (int j = 0; j < n - i - 1; j++)
		{
			//升序
			if (arr[j] < arr[j + 1])
			{
				exchange = 1;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}
  • 与直接插入排序法相比,比较次数一致,但冒泡排序的交换需要执行三次,而直接插入排序因为使用了tmp临时变量存储要插入的数据,只用执行一次,所以直接插入排序法效率明显更高
  • 与直接选择排序法相比,直接选择排序法无论数组是否有序都要执行到结束条件,不存在最好最坏时间复杂度。而冒泡排序因为使用了exchange变量进行优化,可以在最好时间复杂度上达到线性的结果。所以冒泡排序更胜一筹
  • 虽然但是,实际中还是不会使用冒泡排序,但它的教学意义是我们不能忽视的😂

快速排序

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

递归法实现

快速排序实现主框架:

void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//[left,right]--->找基准值mid
	int keyi = _QuickSort(arr, left, right);
	//左子序列:[left,keyi-1]
	QuickSort(arr, left, keyi - 1);
	//右子序列:[keyi+1,right]
	QuickSort(arr, keyi + 1, right);
}
  • 快速排序最重要的就是找基准值:

    • 基准值左边元素都小于它,右边都大于,显然的基准值所在的位置就是所有数据排序好后它应该在的位置上

    • 每次将这个数据(即基准值)放在正确的位置上,然后对其左右序列递归,最后所有数据都被放在了正确的位置上,排序就完成了

将区间中的元素进⾏划分的 _QuickSort ⽅法主要有以下⼏种实现⽅式:

hoare版本

算法思路

  • 假设将序列第一个数作为基准值

  • 定义左右指针

    • left:从左找比基准值大的 ,right:从右找比基准值小的
    • 找到后交换,left++,right–,进入下次循环
  • 跳出循环后交换基准值到正确位置

可以大致写出代码:

int _QuickSort1(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left <right)
	{
		while (left < right && arr[right] > arr[keyi])
		{
			right--;
		}
		while (left < right && arr[left] < arr[keyi])
		{
			left++;
		}
		//right left
		if (left < right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}
	
	Swap(&arr[keyi], &arr[right]);

	return right;
}

于是这里就抛出几个问题:

  1. 外层循环结束条件是否应该取=?
  2. 内层循环当right或left处数据和基准值相等时是否应该跳出循环?
  3. 最后跳出外层循环我们将基准值交换到正确位置时应该与right还是left处数据交换?

问题1:
在这里插入图片描述

  • 二者相遇时在9的位置,如果不取等,第一次交换完后就跳出循环,此时9和6交换,显然不行

外层循环需要取等,同时在内层循环时相应left和right判断处也要取等,不然left和right相等就死循环了

问题3:

  • 既然跳出循环时是left>right,right处在left扫描过的区域,都是不大于基准值的数据,而left处在right扫描过的区域,都是不小于基准值的数据
  • 显然我们将right处数据和基准值交换,基准值就来到了正确的位置

跳出外层循环应该与right处数据交换,right处数据就是基准值的位置

经过上面两层分析:

改进如下:

int _QuickSort1(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left <= right)//left和right相遇的位置的值比基准值要大
	{
		while (left <= right && arr[right] > arr[keyi])
		{·
			right--;
		}
		//right找到比基准值小/  等于?
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;
		}
		//right left
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}
	//right keyi交换
	Swap(&arr[keyi], &arr[right]);

	return right;
}

问题2:

  • 假设数组全是相同的数据

在这里插入图片描述

  • 取等于,第一次循环right就和left都在下标为1的位置,此时返回去的基准值就是下标1,左序列只有一个数据,右边序列还有n-2个数据
  • 同样的下次循环的左序列也只有一个数据
  • 像这样一次排一个数据时间复杂度很高

所以不应该取等于,尽量让左右子序列的数据个数平均一些

所以上述改进版本就是最终的hoare排序法


挖坑法

基本思路

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

动图解析

在这里插入图片描述

  • 动图演示的很清楚
  • 这里同样有两个问题
    • left和right是否取等?
    • 当right或left处数据与基准值key相等时是否继续循环

问题1:

  • 以上面动图为例,如果取等最后当left和right相遇时left还要++一次,导致hole所在位置偏移,发生错误,所以不取等

问题2:

  • 同hoare版本,如果全是相等数据时每次只会排序一个数据,时间复杂度太高,所以不取等
//挖坑法
int _QuickSort2(int* arr, int left, int right)
{
	int hole = left;
	int key = arr[hole];

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


lomuto前后指针

基本思想

创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。

动图解析:

在这里插入图片描述

  • 这种方法比较好理解代码也简单,就不赘述了(就是想不到一点🤣)
//lomuto前后指针法
int _QuickSort(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int keyi = left;
 
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);
	return prev;
}
  • 这里当cur和keyi数据相同时是否交换?
  • 假设仍然全是重复数据,代入后会发现二者都是一样的,如果不加等号最后prev下标在0;反之prev下标在end。可见其对重复数据无法通过此来进行优化

递归法复杂度分析
  • 时间复杂度:每一层的总时间复杂度都是O(n),因为需要对每一个元素遍历一次。而且在最好的情况下,同样也是有logn层,所以快速排序最好的时间复杂度为O(nlogn)
  • 空间复杂度:二叉树递归最大深度为logn,即O(nlogn)
  • 以上是最好情况,最坏情况则是上面说的一次排序一个数据,时间复杂度O(n2),空间复杂度O(n)。不过现实中基本不会出现这种情况。

注意:在以上找基准值方法中,我们默认都是把基准值定为left所在位置,这种方法当数组接近升序时会导致分割的序列也出现“一边倒”的情况,在高阶数据结构中会讲到如何优化,敬请期待😘


非递归法实现

借助栈这样一种数据结构

有关栈的相关知识,不了解的小伙伴可以看看这篇:
栈的实现方法

  • 栈是先进后出,所以插入先插入right后插入left
  • 找基准值方法使用双指针法最简单
  • 根据基准值划分左右区间
    • 左区间:[begin,keyi-1]
    • 右区间:[keyi+1,end]
  • 循环直到栈为空

在这里插入图片描述

//非递归版本快排
//--借助数据结构--栈
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		//取栈顶元素---取两次
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);
		//[begin,end]---找基准值

		int prev = begin;
		int cur = begin + 1;
		int keyi = begin;

		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
			{
				Swap(&arr[cur], &arr[prev]);
			}
			cur++;
		}
		Swap(&arr[keyi], &arr[prev]);

		keyi = prev;
		//根据基准值划分左右区间
		//左区间:[begin,keyi-1]
		//右区间:[keyi+1,end]
		
		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}

	STDestroy(&st);
}

以上就是冒泡排序和快速排序方法的介绍啦,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️
请添加图片描述

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

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

相关文章

Monaco 使用 SelectionRange

Monaco 中有个展开选择的功能&#xff0c;默认如果我们选择 function&#xff0c;扩展选择就会选择到行尾&#xff0c;再扩展就会选中整个函数&#xff0c;效果如下&#xff1a; Monaco 可以自定义选择范围&#xff0c;通过 registerSelectionRangeProvider 注册 selectionRang…

yarn运行失败

目录 1.项目初始化失败2.powershell管理身份运行3.正常4.项目启动5.接下来安装yarn过程 使用vuepress构建静态文档网站1.标题This is an H1This is an H2 this is H1this is H2this is H6 2.字体3.分割线4.引用引用接着 5.列表无序列表有序列表 6.表格7.代码 1.项目初始化失败 …

【通俗理解】主动推理模型——从感知到决策的智慧桥梁

【通俗理解】主动推理模型——从感知到决策的智慧桥梁 主动推理模型的类比 你可以把主动推理模型比作一个“智慧导航仪”&#xff0c;它能够帮助我们的大脑在感知世界、更新信念和做出决策的过程中找到最佳路径。 主动推理模型的核心作用 组件/步骤描述感知世界大脑通过感官接…

在vue3的开发环境中为什么使用vite而不是用webpack

1、vite在开发阶段没有打包过程&#xff0c;直接启动一个服务器 2、请求一个模块到开发服务器 3、开发服务器编译模块&#xff0c;根据页面用所需要的依赖去加载文件 4、加载完成后&#xff0c;开发服务器把编译的结果返回给页面 这使得提高了我们在开发阶段运行的效率 vite是…

【漏洞复现】搜狗输入法简单绕过Windows锁屏机制

免责申明 本公众号的技术文章仅供参考&#xff0c;此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息…

群晖NAS结合内网穿透工具实现远程连接内网SFTP服务传输文件

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

ABAP+json格式数据转换时参数为空没传值

CALL METHOD /UI2/CL_JSON>SERIALIZE 我们在ABAP传输json格式数据到外围系统时&#xff0c;会用到这个类方法 /UI2/CL_JSON>SERIALIZE CALL METHOD /UI2/CL_JSON>SERIALIZEEXPORTINGDATA LO_DATACOMPRESS XPRETTY_NAME /UI2/CL_JSON>PRETTY_M…

LiveBOS UploadFile.do 任意文件上传漏洞复现(XVE-2023-21708)

0x01 产品简介 LiveBOS(Live Business Object System)是顶点软件自主研发的以业务对象建模为核心的业务中间件及其集成开发工具,它通过业务模型建立直接完成软件开发的创新模式,支持各类基于WEB的专业应用软件与行业大型应用的开发。LiveBOS系统由三个相对独立的产品构成:…

DBA界中的ACE 是时候谢幕了

很多人把ACE当作神一样去膜拜! 还脑残地帮ACE去宣传,需要对ACE要有敬畏之心! ACE 全称是 America Certification Eloquent 首先由ORACLE公司 (美国公司) 在最近20年推出来的荣耀称号! 授予一些通过OCP,OCM认证的DBA在社区为其产品使用和推广做出贡献的荣耀称号 为什么我们…

实验21.实现 printf

已完成实验 已完成实验链接 简介 实验 21. 实现 printf 总结 简化系统调用和中断&#xff0c;用 eax 代表调用号参数&#xff0c;ebx,ecx,edx 来代表参数(syscall.c kernel.s) 添加 write 的系统调用接口(syscall.c, syscall-init.c, print.s) 注意&#xff1a;要更改 p…

Arbitrum Nitro交易速度压力测试实战:TPS性能评估全解析

Arbitrum Nitro 是一种基于以太坊的 Layer 2 扩展解决方案&#xff0c;旨在提高交易吞吐量并降低交易费用。为了全面评估其性能&#xff0c;我们需要进行了详细的压力测试。本文的目的是回顾一下我在实际测试过程中采用的方法&#xff0c;还有测试的思路。 我们的压力测试主要…

【Docker应用】快速搭建Plik服务结合内网穿透无公网IP远程访问传输文件

文章目录 前言1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 前言 本文介绍如何使用Linux docker方式快速安装Plik并且结合Cpolar内网穿透工具实现远程访问&#xff0c;实现随时随地在任意设…

页面路由怎么开发

首先删除它自带的页面 新建页面 Composition API 和 Options API 是 Vue.js 中两种不同的组件写法风格&#xff0c;它们用于定义 Vue 组件的结构和逻辑。我用的是Options API 配置路由 将它修改为需要的,按照上面的写法 如果component里面已经加了那么就不需要在上面加这是一种…

C++数据结构重要知识点(4)(map和set封装)

前面我们已经实现了红黑树&#xff0c;接下来我们需要将这个数据结构封装成map和set两个容器&#xff0c;其中很多地方的处理都有一定难度&#xff0c;下面会按照我的思路讲解map的改造 map成员变量如下&#xff0c;如果是第一次看到它&#xff0c;那么一定会很陌生&#xff0…

C高级(学习)2024.8.1

目录 shell命令 数组 数组的赋值 数组的调用 遍历数组 函数 函数的定义方式 函数调用 分文件编程 源文件 头文件 include引用时“”和<>的区别 编译工具 gcc编译工具 gdb调试 make工具 定义 Makefile格式 Makefile管理多个文件 Makefile变量 自定义…

发布NPM包详细流程

制作 首先需要制作一个npm包。 按照以下步骤依次执行。 mkdir my-npm-package cd my-npm-package npm init 相信这一步不需要过多的解释&#xff0c;就是创建了一个文件夹&#xff0c;然后初始化了一下文件夹。 然后在生成的package.json文件夹中更改一下自己的配置&…

Python-docx,修改word编辑时间总计、创建时间、上次修改时间、作者、上次修改者、备注

Python版本3.9&#xff0c;Python-docx版本1.1.2 修改下图中红框内的信息 创建时间、上次修改时间、作者、上次修改者、备注&#xff0c;这些都有接口&#xff0c;调用 import docx from docx import Document from docx.oxml.ns import qn from docx.shared import Inches, …

“2028年互联网上所有高质量文本数据将被使用完毕”

研究公司Epoch AI预测&#xff0c;到2028年互联网上所有高质量的文本数据都将被使用完毕&#xff0c;机器学习数据集可能会在2026年前耗尽所有“高质量语言数据”。研究人员指出&#xff0c;用人工智能(AI)生成的数据集训练未来几代机器学习模型可能会导致“模型崩溃”&#xf…

助力外卖霸王餐系统运营 微客云近期更新汇总

全面助力霸王餐合作运营&#xff0c;给大家汇报下最近微客云更新的内容&#xff0c;说实话近期非常的忙&#xff0c;各种功能上线&#xff0c;各种市场部们反馈的需求&#xff0c;微客云霸王餐招商体系&#xff08;分站&#xff09;自年底上线到现在&#xff0c;不知已更新了多…

2024年8月初AI大赛盛宴来袭!7场赛事等你挑战,最高奖金高达1.4万!

本期为大家带来7场精彩的AI大赛&#xff0c;主要以AI绘画大赛为主打&#xff0c;涵盖1场视频大赛和1场大模型大赛。 其中&#xff0c;以下3场大赛不容错过&#xff0c;分别是“36氪AI PARTNER2024具身智能大会”、“2024年大学生AI艺术季”和“混元万物 LiblibAlx腾讯混元模型…