😘个人主页:@Cx330❀

👀个人简介:一个正在努力奋斗逆天改命的二本觉悟生

📖个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》

前言:在上篇博客的学习中,我们掌握了直接选择排序和堆排序,发现堆排序算法的优秀,那么今天我们就进入冒泡排序和快速排序的学习中


目录

一、冒泡排序

代码实现:

测试结果:

复杂度分析:

二.快速排序(递归实现)

代码实现(框架):

找基准值划分的三种实现方式: 

1.hoare版本

2.挖坑法

3.lomuto前后指针

4.基准值的重要性 

时间复杂度:

测试结果:

三.非递归实现快速排序

代码实现:

测试结果:

四.冒泡排序和快速排序性能对比

代码实现:


一、冒泡排序

冒泡排序大家肯定都不陌生了,我们直接来看代码吧

代码实现:

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

图解:

test.c:

#include"Sort.h"
void printArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test01()
{int a[] = { 5,3,9,6,2,4,7,1,8 };//int a[] = { 6,1,2,7,9,3 };int n = sizeof(a) / sizeof(a[0]);printf("排序之前:");printArr(a, n);//InsertSort(a, n);//ShellSort(a, n);//SelectSort(a, n);//HeapSort(a, n);BubbleSort(a, n);//QuickSort(a, 0, n - 1);//QuickSortNorR(a, 0, n - 1);//MergeSort(a, n);//CountSort(a, n);//MergeSortNonR(a, n);printf("排序之后:");printArr(a, n);
}
int main()
{test01();return 0;
}

测试结果:

测试完成,打印没有问题,升序排列正确,退出码为0

复杂度分析:

时间复杂度:O(N^2)

冒泡排序比较简单,博主这里的冒泡排序是优化过的,当某一次已经有序了就会直接结束


二.快速排序(递归实现)

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

代码实现(框架):

void QuickSort(int* arr, int left, int right)
{if (left >= right){return ;}//左【left, keyi - 1】   右【keyi+1,right】int keyi = _QuickSort(arr, left, right);QuickSort(arr, left, keyi - 1);QuickSort(arr,keyi+1,right);
}

图解:

采用递归的思想,有些类似二叉树的前序遍历,分组进行划分。

找基准值划分的三种实现方式: 

将区间元素进行划分的_QuickSort方法主要有以下几种实现方法:

1.hoare版本

思路:

  1. 创建left和right指针
  2. (升序)从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换。进入下次循环-------我们默认left初始位置的值为基准值,特别注意一下确定基准之后left要先++一次。
  3. 循环结束后,交换keyi和right位置的值,然后返回right下标

代码实现:

//找基准值--hoare版本
int _QuickSort1(int* arr, int left, int right)
{int keyi = left;left++;while (left <= right){//right从右往左找比基准值小的,如果大于基准值就--while (left <= right && arr[right] > arr[keyi]){--right;}//left从左往右找比基准值大的,如果小于基准值就++while (left <= right && arr[left] < arr[keyi]){++left;}//left和right交换if(left<=right)Swap(&arr[left++], &arr[right--]);}//right位置就是基准值的位置Swap(&arr[right], &arr[keyi]);return right;
}

注意事项:

1.为什么跳出循环后right位置的值一定不大于key?

答:当left>right时,即right走到left的左侧,而left扫过的数据均不大于key,所有right此时所指向的数据一定不大于key

三种情况都在图中有所讲解,大家可以仔细的看一下

2.内层循环不能等于,数据重复的情况下会造成时间复杂度变为O(n^2)

2.挖坑法

思路:

  • 创建右指针,(这个思路left不用先++)首先从右向左找出比基准小的,找到后立即放入坑中,当前位置变为新的坑。
  • 然后从左到右找出比基准大的数据,找到后立即放到右边坑中,当前位置变为新的坑
  • 结束循环后将最开始存储的分界值放入当前的坑中,返回当前坑的下标

代码实现:

//找基准值--挖坑法
int _QuickSort(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;
}

图解:

3.lomuto前后指针

思路:

  1. 创建左右指针prev和cur,cur从左往右找比基准值要小的数据
  2. cur指向的数据比基准值要小的话,++prev,(且++prev!=cur),交换cur和prev位置的值。再++cur
  3. cur指向的数据不比基准值小的话,直接++cur
  4. 循环结束后,交换keyi位置和prev位置的值,返回prev下标

代码实现:

//找基准值--lumoto双指针法
int _QuickSort2(int* arr, int left, int right)
{int prev = left, cur = prev + 1;int keyi = left;while (cur < right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[keyi]);return prev;
}

图解:

4.基准值的重要性 

我们一般默认最左边就是我们要找的基准值,但其实我们是有特定方法求出比较好的基准值的,这个在后续的提升篇中会有讲解 

时间复杂度:

O(n*logn)

递归时间复杂度=单次递归时间复杂度(n)*递归次数(树的最大高度,logn)=n*logn

test.c:

#include"Sort.h"void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test1()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int size = sizeof(a) / sizeof(a[0]);printf("排序前:");PrintArr(a, size);//快速排序QuickSort(a, 0, size - 1);printf("排序后:");PrintArr(a, size);
}int main()
{test1();return 0;
}

测试结果:

三种找基准的方法结合快速排序主体最后测试都没有问题,这里就放一个图了,升序排列正确,退出码为0


三.非递归实现快速排序

我们非递归实现快速排序需要借助数据结构栈,直接使用我们之前自己实现过的就行了,然后在.c文件里面引入一下栈的.h文件。具体操作和之前我们借助队列实现二叉树的层序遍历和判断二叉树是否为完全二叉树差不多,但是不用修改数据类型

思路:

1.初始化栈:首先将整个数组的起始索引(如 0)和结束索引(如 n-1)作为初始区间推入栈中,先推结束的再推起始的。

2.循环处理栈中区间:当栈不为空时,执行以下操作:

  • 弹出区间:从栈顶取出一个待排序区间的起始索引 begin 和结束索引 end。
  • 划分区间:调用划分函数(与递归版相同),选择基准元素,将区间 [begin, end] 划分为左子区间 [begin, keyi-1](元素均小于基准)和右子区间 [keyi+1, end](元素均大于基准),返回基准元素的最终位置 keyi。
  • 推入子区间:若左子区间存在(即 begin < keyi-1),将其边界 (begin, keyi-1) 推入栈;若右子区间存在(即 keyi+1 < end),将其边界 (keyi+1, end) 推入栈。

3.结束条件:当栈为空时,所有子区间均已排序完成,整个数组排序结束。

需要注意的时栈先进后出,所以我们一般都是先推右再推左,不管是左子区间右子区间还是左边范围右边范围都是这样的

代码实现:

//找基准值非递归版--栈
int QuickSortNorR(int* arr, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){//取栈顶两次int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//[begin,end]--找基准值int keyi = begin;int prev = begin, cur = prev + 1;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[keyi]);keyi = prev;if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi+1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDesTroy(&st);
}

图解:

test.c:

#include"Sort.h"void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test1()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int size = sizeof(a) / sizeof(a[0]);printf("排序前:");PrintArr(a, size);//非递归快速排序QuickSortNoare(a, 0, size - 1);printf("排序后:");PrintArr(a, size);
}
int main()
{test1();return 0;
}

测试结果:

测试完成,打印没有问题,非递归版本升序排序正确,退出码为0


四.冒泡排序和快速排序性能对比

我们还是通过测试来对比一下这两种排序的性能,大家也可以看看和之前实现过的排序的对比

代码实现:

#include"Sort.h"void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test1()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int size = sizeof(a) / sizeof(a[0]);printf("排序前:");PrintArr(a, size);//直接插入排序//InsertSort(a, size);//希尔排序//ShellSort(a, size);//直接选择排序//SelectSort(a, size);//堆排序//HeapSort(a, size);//冒泡排序//BubbleSort(a, size);//快速排序//QuickSort(a, 0, size - 1);//非递归快速排序QuickSortNoare(a, 0, size - 1);printf("排序后:");PrintArr(a, size);
}// 测试排序的性能对比
void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();SelectSort(a3, N);int end3 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();int begin5 = clock();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();//MergeSort(a6, N);int end6 = clock();int begin7 = clock();BubbleSort(a7, N);int end7 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);printf("QuickSort:%d\n", end5 - begin5);//printf("MergeSort:%d\n", end6 - begin6);printf("BubbleSort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);//free(a6);free(a7);
}int main()
{TestOP();//test1();return 0;
}

我们可以看出快速排序要比冒泡快很多,和其它排序相比的情况下,快速排序也是很快的


往期回顾:

【数据结构初阶】--排序(一):直接插入排序,希尔排序

【数据结构初阶】--排序(二):直接选择排序,堆排序

总结:本篇博客就到此结束了,主要实现了一下两种交换排序,一个冒泡排序,一个快速排序。我们通过对比可知快速排序优于冒泡排序。其中快速排序找基准值我们提供了三种方法,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

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

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

相关文章

名词概念:什么是尾部误差?

“尾部误差”就是指误差分布在两端的那一小撮、但数值特别大的误差——也就是离中心&#xff08;均值/中位数&#xff09;很远的“极端样本”的误差。对应统计学里的“分布尾部”&#xff08;tails&#xff09;。通俗点&#xff1a;大多数样本误差都很小&#xff0c;但总会有少…

记对外国某服务器的内网渗透

本专栏是笔者的网络安全学习笔记&#xff0c;一面分享&#xff0c;同时作为笔记 文章目录前文链接前言上线CS上线rdp后渗透信息收集SMB Pth攻击权限维持魔幻上线提权关Windows Defenderend前文链接 WAMP/DVWA/sqli-labs 搭建burpsuite工具抓包及Intruder暴力破解的使用目录扫描…

速卖通平台关键字搜索商品列表列表接口实现指南:从接口分析到代码落地

在跨境电商开发中&#xff0c;速卖通平台的商品数据获取是许多开发者关注的焦点。本文将详细介绍如何实现速卖通关键字搜索商品列表接口&#xff0c;涵盖接口请求参数分析、签名机制、分页处理及完整代码实现&#xff0c;帮助开发者快速对接速卖通开放平台。一、接口基本信息速…

UE UDP通信

1.确保工程为C工程&#xff0c;在项目工程的xx.Build.cs中加入Networking和Sockets模块。PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Networking", "Socke…

JavaScript 逻辑运算符与实战案例:从原理到落地

JavaScript 中的逻辑运算符不仅是条件判断的核心&#xff0c;还能通过“短路特性”简化代码&#xff1b;结合 DOM 操作的实战案例&#xff0c;更能体现其灵活性。本文整理了逻辑运算符的个人理解、优先级规则&#xff0c;以及 4 个高频实战需求的实现方案&#xff0c;附个人思路…

Android RxJava 过滤与条件操作详解

RxJava 是一个基于观察者模式的响应式编程库&#xff0c;在 Android 开发中被广泛使用。其中&#xff0c;过滤和条件操作是 RxJava 中非常重要的一部分&#xff0c;它们允许我们对数据流进行精细控制。本文将详细介绍 RxJava 中常用的过滤与条件操作符及其使用场景。一、过滤操…

云手机都具有哪些特点?

云手机拥有着便捷的远程操作功能&#xff0c;让用户无论身处何地&#xff0c;只要能连接网络&#xff0c;就能通过手机、电脑等终端设备远程操控云手机&#xff0c;无需受限于物理位置&#xff0c;大大提升了工作的灵活性与便捷性。云手机主要是依赖于云计算技术&#xff0c;能…

Sparse-ICP—(4) 加权稀疏迭代最近点算法(matlab版)

目录 一、算法原理 1、原理概述 2、参考文献 二、代码实现 三、结果展示 一、算法原理 1、原理概述 见:Sparse-ICP—(1)稀疏迭代最近点算法 2、参考文献 二、代码实现 SparseWeightedDistance.m function [move_points,T] =

统信UOS安装NFS共享文件夹

在 UOS ARM 架构系统上安装和配置 NFS 服务&#xff0c;实现与局域网中其他服务器共享文件夹的步骤如下&#xff1a;1. 安装 NFS 服务首先更新系统并安装 NFS 服务器组件&#xff1a;bash# 更新软件包列表 sudo apt update# 安装NFS服务器 sudo apt install nfs-kernel-server …

【完整源码+数据集+部署教程】孔洞检测系统源码和数据集:改进yolo11-RetBlock

背景意义 研究背景与意义 随着工业自动化和智能制造的快速发展&#xff0c;孔洞检测作为关键的质量控制环节&#xff0c;受到了广泛关注。孔洞的存在可能会影响产品的强度、密封性和整体性能&#xff0c;因此&#xff0c;准确、快速地检测孔洞对于保障产品质量至关重要。传统的…

k8s环境使用Operator部署Seaweedfs集群(一)

#作者&#xff1a;闫乾苓 文章目录4.1 前置条件4.2 部署seaweedfs-operator4.3 准备operator镜像SeaweedFS Operator是一个Kubernetes Operator&#xff0c;用于自动化部署和管理SeaweedFS集群 README.md:6-8 。部署分为两个阶段&#xff1a;首先部署Operator本身&#xff0c;然…

实践基地落地:成都影像产业园与重庆五一职院强实训

近日&#xff0c;成都国际影像产业园与重庆五一职业技术学院合作的实践基地正式落地&#xff0c;这一举措为双方强化实训合作、培养高素质技能人才注入了新的活力。实践基地的落地&#xff0c;是双方基于各自优势资源的深度融合。成都国际影像产业园作为影像行业的重要聚集地&a…

算法----滑动窗口

滑动窗口 什么是滑动窗口 滑动窗口是一种常用的技术&#xff0c;主要用于处理连续数据序列&#xff08;如数组、字符串或时间序列数据&#xff09;&#xff0c;通过动态调整一个固定大小的“窗口”来高效地解决问题。窗口在序列上“滑动”&#xff0c;每次移动一个位置&#xf…

Rust学习笔记(三)|所有权机制 Ownership

本篇文章包含的内容1 重新从堆和栈开始考虑2 所有权规则3 变量和数据&#xff08;值&#xff09;的交互方式3.1 移动 Move3.2 克隆 Clone3.3 复制 Copy4 函数与所有权4.1 参数传递时的所有权转移4.2 函数返回时的所有权转移5 引用和借用6 切片前面两篇仅仅介绍了一些Rust的语法…

Redis 知识点与应用场景

1. Redis 简介与核心特性Redis&#xff08;Remote Dictionary Server&#xff09;是一款开源的内存数据存储系统&#xff0c;支持多种数据结构&#xff0c;兼具高性能、持久化、分布式等特性&#xff0c;广泛用于缓存、数据库、消息中间件等场景。其核心特性包括&#xff1a;高…

日常反思总结

1.group by和order by的区别

易贝 (eBay (eBay) 关键字搜索 API 实战:从认证到商品列表获取全流程解析

在跨境电商开发领域&#xff0c;eBay 作为全球最大的在线交易平台之一&#xff0c;其开放 API 为开发者提供了丰富的商品数据获取能力。本文将聚焦 eBay 关键字搜索商品列表接口的实现&#xff0c;涵盖 OAuth2.0 认证、高级搜索参数配置、分页策略及完整代码实现&#xff0c;帮…

敏捷数据开发实践:基于 Amazon Q Developer + Remote MCP 构建本地与云端 Amazon Redshift 交互体系

敏捷数据开发实践&#xff1a;基于 Amazon Q Developer Remote MCP 构建本地与云端 Amazon Redshift 交互体系 新用户可获得高达 200 美元的服务抵扣金 亚马逊云科技新用户可以免费使用亚马逊云科技免费套餐&#xff08;Amazon Free Tier&#xff09;。注册即可获得 100 美元的…

【SpringBoot】11 概念理解 - 深入理解 Java 和 Spring 中的容器、组件、类、对象与 Bean

文章目录引言1. 基本概念解析1.1 类&#xff08;Class&#xff09;1.2 对象&#xff08;Object&#xff09;1.3 组件&#xff08;Component&#xff09;1.4 Bean 实例&#xff08;Bean Instance&#xff09;1.5 容器&#xff08;Container&#xff09;2. 运行时 vs. 非运行时的…

【学习嵌入式day-25-线程】

exec函数族exec函数族利用进程空间执行另一份代码#include "../head.h"int main(void) {char *parg[5] {"./hello","how","are","you",NULL,};printf("execl-up\n");//execl("./hello", "./hello…