【六大排序详解】终篇 :冒泡排序 与 快速排序

终篇 :冒泡排序 与 快速排序

  • 1 冒泡排序
    • 1.1 冒泡排序原理
    • 1.2 排序步骤
    • 1.3 代码实现
  • 2 快速排序
    • 2.1 快速排序原理
      • 2.1.1 Hoare版本
        • 代码实现
      • 2.1.2 hole版本
        • 代码实现
      • 2.1.3 前后指针法
        • 代码实现
      • 2.1.4 注意
        • 取中位数
        • 局部优化
      • 2.1.5 非递归版本
        • 非递归原理
        • 代码实现
    • 2.2 特性总结
  • 谢谢阅读Thanks♪(・ω・)ノ
  • 下一篇文章见!!!

1 冒泡排序

1.1 冒泡排序原理

冒泡排序如同泡泡上升一样,逐个逐个向上冒,一个接一个的冒上去。两两比较,较大者(较小者)向后挪动。全部遍历一遍即可完成排序。
在这里插入图片描述

1.2 排序步骤

  1. 首先从头开始,两两相互比较。每次排好一个最大(最小)
  2. 然后在从头开始,两两比较 至已排序部分之前。
  3. 依次往复,全部遍历一遍。
  4. 完成排序。
    在这里插入图片描述

1.3 代码实现

以排升序为例


void BubbleSort(int* a, int n){for (int i = 0; i < n; i++) //从头开始遍历{//每次遍历 会排完一个 需排序部分减少 1 for (int j = 0; j < n - 1 - i;j++) {   //结束条件 a[n-2] > a[n-1]if (a[j] > a[j + 1]) //如果大,向上冒{      int tmp = a[j];a[j] = a[j + 1];a[j + 1] = tmp;}}}
}

排序结果,非常顺利。
在这里插入图片描述
冒泡排序的特性总结:

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

2 快速排序

2.1 快速排序原理

快速排序是一种高效快速的算法,是Hoare于1962年提出的一种二叉树结构的交换排序方法,
其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
根据其思想 ,便有递归版本 和 非递归版本。
递归版本中有 Hoare版本, Hole版本,前后指针版本
非递归版本使用 栈 来实现

2.1.1 Hoare版本

Hoare版本是一种非常巧妙的版本,其思路大致为(以排升序为例)

  1. 确定一个key值,然后右找较大值,左找较小值
  2. 交换,直到左右相遇,
  3. 相遇时, 相遇位置的值一定小于key值(取决于先找大还是先找小,先找大,则为较小值,否则反之),交换key 与 相遇位置的值。
  4. 此时满足左边都比key小,右边都比key大
  5. 然后再分别进行左部分和右部分的排序。
  6. 全部递归完毕,排序完成。
    在这里插入图片描述
代码实现
//交换函数
void Swap(int* p1, int* p2) {int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//key 取中位数
int getmidi(int *a,int begin,int end) {int midi = (begin + end) / 2;if (a[midi] > a[begin]) {if (a[midi] < a[end]) return midi;else if (a[begin] > a[end]) return begin;else return end;}else {//a[midi]<a[begin]if (a[begin] < a[end])	return begin;else if (a[midi] > a[end]) return midi;else return end;
}//Hoare版本快速排序
int PartSort1(int* a, int left, int right) {//取key 为首元素int keyi = left;//开始交换,右找大,左找小while (left < right) {//右找大while (left < right && a[right] >= a[keyi]) {right--;}//左找小while (left < right && a[left] <= a[keyi]) {left++;}//交换Swap(&a[left], &a[right]);}//将key与相遇位置值交换,//满足左边都比key小,右边都比key大Swap(&a[keyi], &a[left]);keyi = left;
}
//快速排序主体
void QuickSort(int* a, int begin, int end) {//递归实现if (begin >= end) return;// 定义左边,右边与key相应位置。int left = begin, right = end ;int keyi = begin;//该步骤优化十分重要。int midi = getmidi(a, begin, end);Swap(&a[left], &a[midi]);//排序int key = PartSort1(a, left, right);QuickSort(a, begin, key-1);QuickSort(a, key+1, end);
}

我们来看看运行效果。
在这里插入图片描述

2.1.2 hole版本

Hole版本即为挖坑法,是对Hoare版本的优化,避免了许多容易出现的错误。其基本思路为(排升序为例)

  1. 确定一个key值,在该处形成坑位
  2. 右找较大值,进入坑位,然后在该较大值处形成新的坑位
  3. 左找较小值,进入坑位,然后在该较小值处形成新的坑位。
  4. 。。。反复进行至相遇时,把key值放入该坑位。
  5. 此时满足左边都比key小,右边都比key大
  6. 然后再分别进行左部分和右部分的排序。
  7. 全部递归完毕,排序完成。
    在这里插入图片描述
代码实现

主体与上面的Hoare相同,这里提供挖坑法的函数部分。

int PartSort2(int* a, int left, int right) {int key = a[left]; //key取左值int holei = left;//开始排序while(left < right){//右找大while (a[right] >= key && right > left) {right--;}//进坑 挖坑a[holei] = a[right];holei = right;//左找小while (a[left] <= key && right > left) {left++;}//进坑 挖坑		a[holei] = a[left];holei = left;}//结束时,key进坑。完成排序a[holei] = key;return left;
}

2.1.3 前后指针法

前后指针算法是不同于上面两种的独特算法,较为简单。其基本思路为(以排升序为例):

  1. 首先取key值,并定义两个指针,分别指向当前位置与下一位置
  2. 如果cur 指向的值比key小,prev++。然后交换prev和cur指针指向的内容
  3. cur++;
  4. 直到cur到末位置。
  5. 交换key和prev指针指向的内容交换。
  6. 此时满足左边都比key小,右边都比key大
  7. 然后再分别进行左部分和右部分的排序。
  8. 全部递归完毕,排序完成。
    在这里插入图片描述
代码实现

主体与上面的Hoare相同,这里提供挖坑法的函数部分。

void Swap(int* p1, int* p2) {int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
int PartSort3(int* a, int begin, int end) {int key = a[begin];int prev = begin, cur = prev + 1;while (cur <= end) {if (a[cur] < key) {prev++;if (prev != cur)Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[begin], &a[prev]);return prev;
}

2.1.4 注意

取中位数

接下来来看两组测试数据,一组为随机十万数据,一组为有序十万数据。
不取中位数版本 与 取中位数版本。
在这里插入图片描述
这是肉眼可见的性能提升,防止了再有序情况下的逐个遍历。因此取中位数是很重要的一步,当然一般情况下不会遇到最坏情况。

局部优化

根据二叉树的相关知识,最后一层包含50%数据,倒数第二层包含25%数据,倒数第三层包含12.5%数据。
第n层 递归 1 次 第 n-1 层 递归 2 次 第 n - 2 层 递归 4 次 … 第 1 层 递归 2^n 次
所以在进行绝大部分的排序后,如果继续进行递归会存在问题,此时递归次数非常多。所以我们进行局部优化,在数据小于10个(取决于具体数据)时改换为插入排序等稳定算法即可。

2.1.5 非递归版本

非递归算法通常要使用一些循环来达到全部遍历的目的。也使得 非递归版本 比 递归版本 更需要对“递归”的深入理解,这里快速排序的非递归版本使用栈来模拟递归过程

非递归原理

先看递归的实现过程,先对整体排,然后排左部分,排右部分。接着对左进行相同处理,对右进行相同处理。
这样的过程可以通过栈来实现(当然使用数组进行指定操作也可以)
在这里插入图片描述

栈里面依次存放了应该排序的部分,每次取出两个,来进行排序(注意取出顺序与存入顺序相反,若先入左 则先取的为右),排序完毕,存入左右部分的开始位置与结束位置,直到有序。
排序步骤

  1. 存入开始位置begin 结束位置end ,key值取左值。
  2. 依次出栈 记录右位置 right ,左位置 left(读取顺序很重要),排序 该部分
  3. 以key值分割左右两部分,压栈存入左部分的开始与结束位置,压栈存入右部分的开始与结束位置。(若left >= key不读取左部分 若 right<=key 不读取右部分)
  4. 依次出栈 记录右位置 right ,左位置 left(读取顺序很重要),排序 该部分
  5. 重复2 - 3步骤,直到栈为空。
  6. 完成排序
代码实现

需要使用栈的相应函数,栈的具体内容请看
栈相关知识


//非递归排序
void QuickSortNonR(int* a, int begin, int end) {//建立栈Stack s ;StackInit(&s);//初始化//压入开始与结束位置StackPush(&s, begin);StackPush(&s, end);//开始排序while (!StackEmpty(&s)) {//不为空就继续进行//出栈读入右位置int right = StackTop(&s);//读取后删除StackPop(&s);//出栈读入左位置int left = StackTop(&s);//读取后删除StackPop(&s);//对该部分进行排序 这里使用前后指针法(使用三种其一即可)int keyi = PartSort3(a, left, right);//读取左部分 若left>=key不进行读入if (left < keyi) {//入栈 StackPush(&s, left);StackPush(&s, keyi - 1);}//读取右部分 若right<=key不进行读入if (right > keyi) {//入栈StackPush(&s, keyi + 1);StackPush(&s, right);}		}
}

2.2 特性总结

快速排序的特性总结

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

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

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

  4. 稳定性:不稳定
    总的来说快速排序的内容十分丰富。我个人感觉使用前后指针来实现快速排序比较简单。同时非递归版本可以让我们更深刻的认识递归过程。而且不同版本的性能大差不差,基本相同。

谢谢阅读Thanks♪(・ω・)ノ

下一篇文章见!!!

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

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

相关文章

室内效果图没有质感?外国大神这6个实用技巧,带你轻松掌握!

为了创作出高级有质感的效果图&#xff0c;我们需要注意构图、颜色、布光等多种因素&#xff0c;以打造出逼真的渲染效果。不过不要担心&#xff01;今天小编带来了国外知名设计师Arch Viz Artist在油管上分享的6个实用小技巧。看完带你轻松提升室内效果图的表现力&#xff01;…

Vue2面试题:说一下$set的作用和原理?

作用&#xff1a; 对象&#xff1a; 响应式原理&#xff1a;通过触发setter实现更新 对象中后追加的属性、删除已有属性&#xff0c;Vue默认不做响应式处理 解决&#xff1a;this.$set() 数组&#xff1a; 响应式原理&#xff1a;调用重写的原生方法实现更新 数组中修改某下标的…

WPS/PPT插件-大珩助手免费功能更新-特殊字符

扩展特殊格式下特殊字符&#xff0c;增加200多个常用特殊字符&#xff0c;可直接点击插入。 PPT大珩助手 1.7.6 1、提供素材库功能&#xff0c;可实现一键保存素材&#xff0c;支持对选中的形状&#xff0c;支持一键替换素材&#xff0c;保留原素材的尺寸和位置&#xff0c;…

Redis使用场景(五)

Redis实战精讲-13小时彻底学会Redis 1.计数器 可以对 String 进行自增自减运算&#xff0c;从而实现计数器功能。 Redis 这种内存型数据库的读写性能非常高&#xff0c;很适合存储频繁读写的计数量。 2.缓存 将热点数据放到内存中&#xff0c;设置内存的最大使用量以及淘汰策略…

c++学习笔记-提高篇-STL-常用六大算法(遍历、查找、排序、拷贝和替换、算术生成、集合算法)

目录 概述 一、常用遍历算法 &#xff08;1&#xff09;for_each &#xff08;2&#xff09;transform 二、常用查找算法 &#xff08;1&#xff09;find &#xff08;2&#xff09;find_if &#xff08;3&#xff09;adjacent_find &#xff08;4&#xff09;binary…

RTT打印时间戳

官方的RTT VIEWER没有打印接收时间戳的功能&#xff0c;经过查找后发现可以有以下三种打印时间戳的方法。 第三方的RTT上位机ExtraPutty自己打印 第三方的RTT上位机 码云上有一个RTT_T2的仓库&#xff0c;基于python qt包写的画面&#xff0c;通过pylink来jlink通信。 优点…

Journal of King Saud University - Computer and Information Sciences投稿经验

期刊标签&#xff1a; 中科院二区 JCR Q1 影响因子&#xff1a;6.9 双盲审 个人认为还是很不错的期刊 开源期刊1350美元版面费 投稿经验 一共三个审稿人&#xff0c;一个建议小修后录取&#xff08;list文章的贡献&#xff0c;添加一篇文章的引用&#xff09;&#xff0c; 另…

conda创建、查看、删除虚拟环境

在现代的Python开发中&#xff0c;使用虚拟环境已经成为了一种标准的做法。它可以帮助我们隔离不同的项目&#xff0c;使得每个项目都有自己独立的Python环境和依赖&#xff0c;从而避免各种依赖冲突。Conda是一个流行的包管理器和环境管理器&#xff0c;它可以帮助我们轻松地创…

Java八股文面试全套真题【含答案】-SQL优化篇

以下是关于Java八股文面试全套真题-SQL优化篇 你SQL优化这块有哪些技巧和方法&#xff0c;谈一谈&#xff1f; 以下是一些常用的SQL优化技巧&#xff1a; 使用索引&#xff1a;索引是提高SQL查询性能的最常见和有效的方法之一。通过创建适当的索引&#xff0c;可以加快查询的…

Nginx 负载均衡集群 节点健康检查

前言 正常情况下&#xff0c;nginx 做反向代理负载均衡的话&#xff0c;如果后端节点服务器宕掉的话&#xff0c;nginx 默认是不能把这台服务器踢出 upstream 负载集群的&#xff0c;所以还会有请求转发到后端的这台服务器上面&#xff0c;这样势必造成网站访问故障 注&#x…

Linux GDB 调试

文章目录 一、Qemu二、Gdbvscode 调试 三、RootFs 一、Qemu qemu 虚拟机 Linux内核学习 Linux 内核调试 一&#xff1a;概述 Linux 内核调试 二&#xff1a;ubuntu20.04安装qemu Linux 内核调试 三&#xff1a;《QEMU ARM guest support》翻译 Linux 内核调试 四&#xff1a;…

基于SSM框架和Layui框架的管理系统

计算机毕业设计&#xff1a;打造安全、高效的信息管理系统在这个数字化时代&#xff0c;信息安全和高效管理是至关重要的。为了帮助学校或机构更好地管理和保护信息&#xff0c;我们为您设计了一套功能强大的信息管理系统。该系统利用先进的技术&#xff0c;结合MD5加密&#x…

使用Go语言的HTTP客户端进行并发请求

Go语言是一种高性能、简洁的编程语言&#xff0c;它非常适合用于构建并发密集型的网络应用。在Go中&#xff0c;标准库提供了强大的HTTP客户端和服务器功能&#xff0c;使得并发HTTP请求变得简单而高效。 首先&#xff0c;让我们了解为什么需要并发HTTP请求。在许多应用场景中…

【Nacos专题】Nacos如何建立与应用服务之间的通信渠道?

作为Spring Cloud Alibaba微服务架构实战派上下册和RocketMQ消息中间件实战派上下册的作者胡弦。 Nacos是一款面向云原生服务的注册中心和配置中心技术解决方案&#xff0c;既然要与服务打交道&#xff0c;那么通信渠道是必不可少的组件&#xff0c;那么Nacos是如何建立与应用…

【后端已完成,前端更新ing】uniapp+springboot实现个人备忘录系统【前后端分离】

目录 &#xff08;1&#xff09;项目可行性分析 &#xff08;一&#xff09;技术可行性&#xff1a; &#xff08;二&#xff09;经济可行性&#xff1a; &#xff08;三&#xff09;社会可行性&#xff1a; &#xff08;2&#xff09;需求描述 功能模块图 用例图&#…

Xcode15在iOS12系统上崩溃的原因

1.1.崩溃在_dyld_start&#xff0c;如图&#xff1a; 崩溃截图 解决办法&#xff1a;在other link flags添加-ld64 注意&#xff1a;该办法只能解决运行真机&#xff0c;archive出来的包依然报错闪退...... 1.2 SwiftUI导致iOS12及以下系统闪退问题 SwiftUI是iOS13开始使用&…

(NeRF学习)NeRF复现 win11

目录 一、获取源码二、环境三、准备数据集1.下载数据集方法一&#xff1a;官方命令方法二&#xff1a;官网下载数据集 2.修改配置 四、开始训练1.更改迭代次数2.开始训练方法一&#xff1a;方法二&#xff1a; 3.使用预训练模型 五、NeRF源码学习 一、获取源码 git clone http…

Blazor 问题记录

1&#xff09;使用Ant 样式。结果弹窗提示怎么都出不来。 只要在App.razor 加最后一句即可 <Router AppAssembly"typeof(App).Assembly"><Found Context"routeData"><RouteView RouteData"routeData" DefaultLayout"typeof…

C#之反编译之路(一)

本文将介绍微软反编译神器dnSpy的使用方法 c#反编译之路(一) dnSpy.exe区分64位和32位,所以32位的程序,就用32位的反编译工具打开,64位的程序,就用64位的反编译工具打开(个人觉得32位的程序偏多,如果不知道是32位还是64位,就先用32位的打开试试) 目前只接触到wpf和winform的桌…

什么是负载均衡?什么情况下又会用到负载均衡

什么是负载均衡 在大型的网络应用中&#xff0c;使用多台服务器提供同一个服务是常有的事。平均分配每台服务器上的压力、将压力分散的方法就叫做负载均衡。 [利用 DNS来实现服务器流量的负载均衡&#xff0c;原理是“给网站访问者随机分配不同ip”] 什么情况下会用到负载均…