排序算法第三辑——交换排序

目录

​编辑

一,交换排序算法的简介

二,冒泡排序

冒泡排序代码:排升序

三,快速排序

1.霍尔大佬写的快速排序

2.挖坑法

3.前后指针法

四,以上代码的缺陷与改正方法

三数取中

三路划分:

五,随机数取中

六,快速排序的非递归


 

一,交换排序算法的简介

交换排序,顾名思义便可以知道这是一种通过交换来实现排序的算法。在这这种算法中有一种最简单最好理解的算法叫做冒泡排序。但是这种算法的效率十分低下,在现实生活中不足为用。还有一种算法叫做快速排序,这种算法的效率很高被广泛应用。并且,这种算法有许多个版本。

二,冒泡排序

虽然冒泡排序效率十分低下,但是我仍然要介绍一下这种算法。这种算法的精华一句话便可以总结出来:相邻的两两比较,遇到不相等的便交换。

冒泡排序代码:排升序

void swap(int* p, int* q)
{int tmp = *p;*p = *q;*q = tmp;
}
void BubbleSort(int* a, int n)
{int flag =1;for (int i = 0;i < n;i++){for (int j = 0;j < n-i-1;j++){if (a[j] > a[j+1]){swap(&a[j], &a[j+1]);flag = 0;}}if (flag){break;}}
}

三,快速排序

快速排序,虽然与冒泡排序同样是交换排序算法的一种但这种排序算法却比冒泡排序的效率快的多。并且快速排序的写法也有三种:

1.霍尔大佬写的快速排序

2.挖坑法

3.指针法
 下面我就来对这三种快速排序的写法进行一一介绍。

1.霍尔大佬写的快速排序

代码:

int PartSort1(int* a, int left, int right)
{int keyi = left;//将数组的第一个数据作为关键值while (left < right){while (right > left && a[right] >= a[keyi])//右边找小于关键值的元素{right--;}while (right > left && a[left]<= a[keyi])//左边找大于关键值的元素{left++;}swap(&a[left], &a[right]);//找到后交换左右的值让小的在右边,大的在左边}swap(&a[keyi], &a[left]);//循环结束后,left与right是相交的,所以在这个相交的位置放上关键值再在关键值原来的位置上放上原相交位置上的值keyi = left;return keyi;//返回相遇位置
}
//递归调用
void QuickSort(int* a, int begin,int end)
{if (begin >= end)//当范围为0或范围不存在时便返回{return;}int keyi = PartSort1(a, begin, end);QuickSort(a ,begin, keyi - 1);//调整左区间QuickSort(a , keyi + 1, end);//调整右区间}

霍尔大佬写的快速排序可能就是第一代快速排序算法了。这个排序算法的主要思想就是将比a[keyi]小的值放在左边,将比a[keyi]大的值放在右边。最后将左右指针相遇的位置与a[keyi]交换。

但是这个算法有几个坑:
1.一般我们在选keyi时选择的都是最左边或者最右边的值。在这里左右两个指针走的顺序就有讲究了:

1.当我们选择了最左边的值作为keyi时,我们就要让right先走,

当我们选择了最右边的值作为keyi时,我们就要让left先走。

2.left与right指针的指向也要保证指向最左边或者最右边的值,否则就会发生错误。

2.挖坑法

因为霍尔大佬写的代码有很多需要谨慎处理的地方,所以就有人写了一个对霍尔大佬的代码进行优化的算法——挖坑法。代码如下:

代码:

int PartSort2(int* a, int left, int right)
{int key = a[left];//记录关键值int hole = left;//初始化坑位while (left < right){   //在右边找能够填坑的值并填坑while (left < right && a[right] >= key){right--;}swap(&a[right], &a[hole]);hole = right;//在右边找能够填坑的值并填坑while (left < right && a[left] <= key){left++;}swap(&a[hole], &a[left]);hole = left;}//找到最后一个坑,将关键值填入并返回关键值的下标swap(&a[hole], &a[left]);hole = left;return hole;
}
void QuickSort(int* a, int begin,int end)
{if (begin >= end){return;}int keyi = PartSort2(a, begin, end);QuickSort(a ,begin, keyi - 1);QuickSort(a , keyi + 1, end);}

这个算法便不再需要像霍尔大佬的算法一样顾虑那么多。但是这个算法的循环还是多了点。死循环的概率还是大了点。为了减少循环,又有人对这些代码进行优化。

3.前后指针法

指针法可谓是这三种快速排序的写法中最简单的一种写法了。这个算法的大概思想如下:

1.用一个keyi记录最左边或者最右边的值的下标

2.再定义两个指针cur与prev。这两个指针的指向如下:

3.把cur当作一个探路者,cur无条件的向前冲。当cur感知到自己指向的数据比a[keyi]小的时候便停下来交换cur与++prev指向的数据。当cur大于右边的边界条件时便结束循环。

代码如下: 

代码:

int PartSort3(int* a, int left, int right)
{ int keyi = left;int prev = left;int cur = left + 1;while (cur<=right){   //这里的prev一定要先加加if (a[cur] <a[keyi]&&++prev!=cur){swap(&a[cur], &a[prev]);}cur++;}swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}
//递归调用
void QuickSort(int* a, int begin,int end)
{if (begin >= end){return;}int keyi = PartSort3(a, begin, end);QuickSort(a , begin, keyi - 1);QuickSort(a , keyi + 1, end);}

这个指针法便减少了代码内的循环可以大大的减少死循环的风险!!!

四,以上代码的缺陷与改正方法

虽然上面的代码可以很快的将一些数据进行排序,但是对于一些比较极端的场景下上面的代码排序起来就会变得十分吃力。比如:

1.当排序的数据是有序的数据时,上面的代码排序起来的时间复杂度就会是O(n2)

2.当排序的数据的量十分多,并且重复的数据也十分多时,上面的快速排序就会排不出来有栈溢出的风险。

针对上面的两个问题,可以给出下面的解决方案:

1.三数取中

2.三路划分

三数取中

三数取中针对的场景是数据有序时的场景,比如:

int main()
{   //创建一个有序的数组int N = 10000000;int* a = (int*)malloc(sizeof(int) * N);for (int i = 0;i < N;i++){a[i] = i;}//计算排序时间int begin = clock();QuickSort(a, 0, N-1);int end = clock();printf("QuickSort:%d ", end - begin);return 0;
}

要排序这串数据在我的编译器上会崩掉,崩掉的原因是栈溢出。

现在来加一段三数取中代码:

//三数取中,就是在左,中,右三个数中取中间值
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[mid] < a[left]){if (a[right] > a[left]){return left;}else if (a[mid] > a[right]){return mid;}else{return right;}}else{if (a[mid] < a[right]){return mid;}else if(a[left]>a[right]){return left;}else{return right;}}
}
//加入三数取中操作的快速排序
int PartSort1(int* a, int left, int right)
{//取出中间值然后与最左边的值交换让keyi指向中间元素int mid = GetMid(a, left, right);swap(&a[left], &a[mid]);int keyi = left;while (left < right){while (right > left && a[right] >= a[keyi]){right--;}while (right > left && a[left]<= a[keyi]){left++;}swap(&a[left], &a[right]);}swap(&a[keyi], &a[left]);keyi = left;return keyi;
}

效果:

虽然排起来并不算快,但是总算是排出来了。

三路划分:

 虽然上面的代码把有序的情况给排出来了,但是还有一种极端情况三数取中这个解决办法是解决不了的。那便是存在大量的重复元素的时候。比如:

int main()
{   //创建一个数据全是2的数组int N = 10000000;int* a = (int*)malloc(sizeof(int) * N);for (int i = 0;i < N;i++){a[i] = 2;}//计算排序时间int begin = clock();QuickSort(a, 0, N - 1);int end = clock();printf("QuickSort:%d\n ", end - begin);return 0;
}

这个数组如果用三数取中的话是不可能取到中间数的,所以三数取中没有用。如果按照原代码那样写的话也会栈溢出。所以有大佬就想到了三路划分的解决方法。

三路划分:

这个方法的思想和这个方法的名字一样:

就是要将一组数据划分成三路:左路,中路,右路。当我们再次递归时只需要递归左路与右路。这样做便可以大大减少递归的深度,防止栈溢出。

实现思想:

1.定义三个指针:cur,left,right.同时用key记录a[left]的值。

2.当a[cur]小于key时便将a[cur]与a[left]交换,同时left++,cur++。

   当a[cur]大于key时便将a[cur]与a[right]交换,right--,cur不动。

   当a[cur]与key相等时,cur++,left与right不动。

3.当cur超过right时循环停止。

4.再次排序时排序的范围是:[begin,left-1],[right+1,end]

写成代码如下:

代码:

//三路划分
void QuickSort2(int* a, int begin, int end)
{if (begin >= end){return;}int left = begin;int right = end;int cur = begin + 1;//三数取中int midi = GetMid(a,left, right);swap(&a[left], &a[midi]);//三路划分,开始甩值到左右两边int key = a[left];while (cur <= right){if (a[cur] < key){swap(&a[cur], &a[left]);left++;cur++;}else if (a[cur] > key){swap(&a[cur], &a[right]);right--;}else{cur++;}}//只递归两边的区间QuickSort2(a, begin, left - 1);QuickSort2(a, right + 1, end);
}

 这个代码便可以跑过有大量重复数据的数组排序,也能跑过有序的大量数据。

但是当我们要跑这个oj排序数组 时出现这种情况:

 虽然测试用例都过了,但还是没有将这道题通过,因为时间限制。

五,随机数取中

 为了通过这道OJ题,这里又得来上一个随机数取中的操作。因为在这道题的用例中可能存在着一些用例非常的接近导致取中的操作效率变低。所以为了提高效率,我们便可以采用随机数取中。

代码:

int GetMid(int* a, int left, int right)
{//只是改了这里srand(time(0));int mid = left+rand()%(right-left);if (a[mid] < a[left]){if (a[right] > a[left]){return left;}else if (a[mid] > a[right]){return mid;}else{return right;}}else{if (a[mid] < a[right]){return mid;}else if(a[left]>a[right]){return left;}else{return right;}}
}

结果:

 过啦!!!

六,快速排序的非递归

 前面我们写的快速排序都是递归版本的。递归版本的代码虽然简单但是递归调用的栈空间却比较小,很容易就会栈溢出。所以为了避免栈溢出的风险,我们便要实现一个非递归版本的快速排序算法。这个非递归写法的快速排序要用到一个数据结构就叫作栈(这个栈是我们定义的,这是一个堆上的栈)。非递归版本代码如下:

代码:

void QuickSortNR(int* a, int begin, int end)
{   //定义栈并初始化栈Stack stack;StackInit(&stack);//在栈中插入begin和end这个区间,牢记栈后进先出的特点。StackPush(&stack, end);StackPush(&stack, begin);while (!StackEmpty(&stack)){int left = StackTop(&stack);StackPop(&stack);int right = StackTop(&stack);StackPop(&stack);//根据区间排序int keyi = PartSort1(a, left, right);//判断区间是否存在再插入区间(先右后左,因为栈的特点是后进先出)if (keyi + 1 < right){StackPush(&stack, right);StackPush(&stack, keyi + 1);}if (keyi - 1 > left){StackPush(&stack, keyi - 1);StackPush(&stack,left);}}//栈销毁StackDestroy(&stack);}

快速排序的非递归版本需要使用到数据结构中的栈,所以为了实现非递归版本的快速排序我们就得自己定义一个栈。这个栈的作用就是用来分割排序区间的

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

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

相关文章

【电子学会】2023年05月图形化四级 -- 计算圆的面积和周长

计算圆的面积和周长 编写程序计算圆的面积和周长。输入圆的半径&#xff0c;程序计算出圆的面积和周长&#xff0c;圆的面积等于3.14*半径*半径&#xff1b;圆的周长等于2*3.14*半径。 1. 准备工作 &#xff08;1&#xff09;保留舞台中的小猫角色和白色背景&#xff1b; 2…

Python 列表 sort()函数使用详解

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 sort函数使用详解 1、升序降序2、sort()和sorted()的区别3、切片排序4、指定排序…

TypeScript笔记

文章目录 什么是TS前期准备安装TSTS配置文件 基础类型原始类型 object类型 数组类型 元组类型 枚举 函数类型可选参数和默认参数剩余参数 any任意类型 高级类型交叉类型联合类型 接口类泛型类型别名参考 什么是TS 官网介绍&#xff1a;TypeScript是JavaScript类型的超集&#…

飞书ChatGPT机器人 – 打造智能问答助手实现无障碍交流

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

Flask结合gunicorn和nginx反向代理的生产环境部署及踩坑记录

个人博客&#xff1a;https://xzajyjs.cn 前言 之前自己写的flask使用gunicorn上线生产环境没有什么问题&#xff0c;但是最近搭建了一个现成的flask项目&#xff0c;当使用python直接运行时不会有问题&#xff0c;而使用gunicorn时则会出现一些问题。 部署过程 运行测试 这…

听GPT 讲K8s源代码--pkg(一)

在 Kubernetes 代码仓库中&#xff0c;pkg/api和pkg/apis目录都包含用于定义 Kubernetes API 对象的代码&#xff0c;但它们的作用略有不同。 pkg/api目录包含 Kubernetes 的旧版本 API 对象定义&#xff0c;这些定义在 Kubernetes 1.7 版本之前使用。这些对象定义已经过时&…

k8s 持久化存储

我们继续来查看 k8s 的卷&#xff0c;上一次我们分享了将磁盘挂载到容器中&#xff0c;empyDir 和 gitRepo 都是会随着 pod 的启动而创建&#xff0c;随着 pod 的删除而销毁 那么我们或许会有这样的需求&#xff0c;期望在 pod 上面读取节点的文件或者使用节点的文件系统来访问…

Spring Boot 中的 Redis 的数据操作配置和使用

Spring Boot 中的 Redis 的数据操作配置和使用 Redis 是一种高性能的 NoSQL 数据库&#xff0c;它支持多种数据结构&#xff0c;包括字符串、哈希、列表、集合和有序集合。Redis 还提供了丰富的命令&#xff0c;可以对数据进行快速的 CRUD 操作。Spring Boot 是一个基于 Sprin…

基于单片机智能加湿器 水位防干烧加湿器的设计与实现

功能介绍 以51/STM32单片机作为主控系统&#xff1b;LCD1602液晶显示当前温湿度&#xff0c;当前模式&#xff0c;湿度下限;按键设置湿度下限&#xff0c;当湿度低于下限时开启加湿器;水位传感器检查加湿器是否有水&#xff0c;如果没有水到话加湿器不进行工作&#xff0c;蜂鸣…

怎么用PDF24 Tools工具在线进行PDF文件合并

PDF文件是经常会被用到&#xff0c;它在我们的日常生活和工作中扮演着重要的角色。PDF文件合并是将多个PDF文件合并为单个文件&#xff0c;这个过程通常是为了方便管理多个PDF文件&#xff0c;或者将多个PDF文件合并为一个整体以便于共享或打印。既然如此&#xff0c;如何快速合…

kotlin forEach循环return/break

kotlin forEach循环return/break fun main(args: Array<String>) {var a mutableListOf("0", "1", "2", "3", "4")var b mutableListOf<String>()a.forEachIndexed { index, s ->if (index > 2) {retu…

【Mac使用笔记】之 Homebrew

Homebrew更新&#xff1a; brew update && brew upgrade 当出现错误&#xff1a; fatal: couldnt find remote ref refs/heads/master 执行&#xff1a; brew tap --repair Ruby安装&#xff1a; 1、查看当前Homebrew版本&#xff1a; brew --version2、查看当前…

libbpf-bootstrap 开发指南:概念与如何安装

目录 概念 如何安装& 使用 git 地址 使用git clone 下载代码 安装依赖环境 安装libbpf 编译example 概念 libbpf-bootstrap 是一个项目&#xff0c;旨在帮助开发者快速启动和开发使用 eBPF (Extended Berkeley Packet Filter) 和 libbpf 的程序。eBPF 是一种可以在…

TTX1994-可调谐激光器控制系统

花了两周时间&#xff0c;利用下班时间&#xff0c;设计了一个ITLA可调谐激光器控制系统&#xff0c;从硬件到软件。下面这个图片整套硬件系统&#xff0c;软件硬件都自己设计&#xff0c;可以定制&#xff0c;做到单片机问题也不大。相当于一套光源了 这是软件使用的界面&…

Kafka 概述、Filebeat+Kafka+ELK

Kafka 概述、FilebeatKafkaELK 一、为什么需要消息队列&#xff08;MQ&#xff09;1、使用消息队列的好处2、消息队列的两种模式 二、Kafka 定义1、Kafka 简介2、Kafka 的特性3、Kafka 系统架构 三、部署 kafka 集群1.下载安装包2.安装 Kafka3.Kafka 命令行操作 四、Kafka 架构…

基于linux串口实现语音刷抖音

目录 1.开发逻辑图及模块 2.编程实现语音和开发板通信 3.手机接入Linux热拔插相关,打开手机开发者模式允许USB调试 4.用shell指令来操作手机屏幕&#xff0c;模拟手动滑屏幕 5.最终主程序代码 1.开发逻辑图及模块 逻辑图&#xff1a; 模块 &#xff08;1&#xff09;语音…

运维小知识(二)——Linux大容量磁盘分区及挂载

centos系统安装&#xff1a;链接 目录 1.&#x1f353;&#x1f353;命令格式化磁盘 2.&#x1f353;&#x1f353;大容量硬盘分区 3.&#x1f353;&#x1f353;自动挂载 整理不易&#xff0c;欢迎一键三连&#xff01;&#xff01;&#xff01; 新系统装完之后&#xff0…

C语言图书管理系统

一&#xff0c;开发环境 操作系统&#xff1a;windows10, windows11, linux, mac等。开发工具&#xff1a;Qt, vscode, visual studio等开发语言&#xff1a;c 二&#xff0c;功能需求 1. 图书信息管理&#xff1a; 这个功能的主要任务是保存和管理图书的所有信息。这应该包…

数据库多表查询作业

数据库多表查询作业 创建数据库 插入数据 mysql> insert into student values(901,张老大,男,1985,计算机系,北京市海淀区),-> (902,张老二,男,1986,中文系,北京市昌平市),-> (903,张三,女,1990,中文系,湖南省永州市), -…

opencv实战--角度测量和二维码条形码识别

文章目录 前言一、鼠标点击的角度测量二、二维码条形码识别 前言 一、鼠标点击的角度测量 首先导入一个带有角度的照片 然后下面的代码注册了一个鼠标按下的回调函数&#xff0c; 还有一个点的数列&#xff0c;鼠标事件为按下的时候就记录点&#xff0c;并画出点&#xff0c;…