c语言-快速排序

目录

一、实现快速排序三种方法

 1、hoare法

2、挖坑法

3、双指针法

4、快速排序的优化

5、测试对比

结语:


前言:

        快速排序作为多种排序方法中效率最高的一种,其底层原理被广泛运用,他的核心思想与二叉树结构中的递归逻辑相似,首先标记一个元素作为基准点,然后利用该基准点把数组分成左右两个区间,并且使小于该基准点的元素放在左区间,大于该基准点的元素放在右区间,如此反复递归,直到数组为一个有序数组。

        实现快速排序的原理有三种方法:hoare方法,挖坑方法,双指针方法。

一、实现快速排序三种方法

 1、hoare法

        比如将一个数组的起始位置记成left,最后一个元素位置记成right,那么标记left的位置的元素,把该元素看成基准点(key)。


        .这时候right要不断的向左移动,若right所在位置的元素是小于key位置的元素,那么right停止移动。left要不断的向右移动,若left所在位置的元素是大于key位置的元素,那么left停止移动。总结就是:left找大,right找小。(注意:若将left设置为key,则先移动right然后才能移动left)


         当left和right都停下来后,把他们的元素进行交换,交换过后继续移动。


        如此反复操作,最后left会走到right的位置,这时候left和right是处于同一位置的,把该位置的元素和key位置的元素进行交换,更新key的位置。


        可以观察到,此时数组有了一个特点:以key为中心点,左边区间的元素都是小于基准点key元素的,右边区间的元素都是大于key元素的。


        但是此时数组并不是一个有序的数组,所以要通过多重递归,因此将左边区间又看成一个小数组,右边区间也看成一个小数组。此时左边区间的left就是下标为0的位置,左边区间的right是key-1的位置。右边区间的left是key+1的位置,right是整个大数组的末尾处,既大数组的right。通过递归不断让每个小数组变得有序,最后整个数组也就有序了。

        递归逻辑图如下:

        hoare版本快速排序代码实现:

#include<stdio.h>void swap(int* p1, int* p2)//交换函数
{int temp = *p1;*p1 = *p2;*p2 = temp;
}
int PatrSort1(int* a, int left, int right)//hoare法
{int key = left;//定义基准点keywhile (left < right)//当left<right说明还没相遇,继续数组内元素的交换{while (left < right && a[right] >= a[key])//right找小{right--;}while (left < right && a[left] <= a[key])//left找大{left++;}swap(&a[right], &a[left]);//交换left和right位置的元素}swap(&a[key], &a[left]);//跳出循环说明他们相遇了,将他们位置的元素与key位置的元素交换key = left;//更新key的位置return left;//返回key元素当前的下标
}void Qsort(int* a, int begin, int end)//快速排序(递归法),这里的begin=left,end=right
{if (begin >= end)//{return;}int key = PatrSort1(a, begin, end);//每次递归都会找到一个属于该数组的keyQsort(a, begin, key - 1);//递归左右区间Qsort(a, key + 1, end);
}void PrintArr(int* a, int n)//打印数组
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Test_Qsort()//快排(递归)测试
{int arr[] = { 5,10,6,1,2,4,3,9 };int sz = sizeof(arr) / sizeof(int);PrintArr(arr, sz);Qsort(arr, 0, sz - 1);printf("hoare法:");PrintArr(arr, sz);
}int main()
{Test_Qsort();return 0;
}

        运行结果:

2、挖坑法

        思路:和hoare法一样先定义一个基准点,只不过把这个基准点叫做“坑”,“坑”里的元素是要被抹除的(也就是“坑”里不能有元素),因此先用一个变量key来保存“坑”里的元素。right向左移动找到小于该基准点(“坑”)的元素就把这个元素填到“坑”里,这时候“坑”里有了元素,可以表示“坑”被填满了,但是right位置的就变成了新的“坑”,因为right位置的元素被用来“填坑”了。


        下一次就是left找大的元素给到right位置的”坑“,然后left的位置就成了新坑,如此反复,直到left和righ相遇,待到他们相遇的位置必然是一个”坑“,这时候把key存储的元素放到这个”坑“里,此时会发现数组也以5(key)为中心点分成了两个区间,而且左区间的元素都是小于key的,右区间的元素都是大于key的。

        挖坑法代码实现快速排序:

#include<stdio.h>void swap(int* p1, int* p2)//交换函数
{int temp = *p1;*p1 = *p2;*p2 = temp;
}
int PatrSort2(int* a, int left, int right)//挖坑法
{int key = a[left];//把基准点元素的值保存起来int hole = left;//设置hole更加形象表示坑while (left < right)//相遇前进行相互“填坑”{if (left < right && a[right] >= key)//right找小{right--;}a[left] = a[right];//填坑hole = right;//right处变成新坑if (left < right && a[left] <= key)//left找大{left++;}a[right] = a[left];//填坑hole = left;//left处变成新坑}a[hole] = key;//最后left处是一个坑,把key存的元素给到此处,此处作为基准点return hole;//返回基准点的下标
}void Qsort(int* a, int begin, int end)//快速排序(递归法)
{if (begin >= end)//{return;}int key = PatrSort2(a, begin, end);//每次递归都会找到一个属于该数组的keyQsort(a, begin, key - 1);//递归左右区间Qsort(a, key + 1, end);
}void PrintArr(int* a, int n)//打印数组
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Test_Qsort()//快排(递归)
{int arr[] = { 5,10,6,1,2,4,3,9 };int sz = sizeof(arr) / sizeof(int);PrintArr(arr, sz);Qsort(arr, 0, sz - 1);printf("挖坑法:");PrintArr(arr, sz);
}int main()
{Test_Qsort();return 0;
}

        运行结果:

        从结果可以看到,不管是挖坑法还是hoare法都可以实现排序,因为他们本身的逻辑是一样的。 

3、双指针法

        顾名思义就是依靠两个类似指针的变量去变量数组并且完成排序,首先定义一个变量prev和一个变量cur,分别在数组的第一个元素位置和第二个元素位置,cur在prev的后边(既cur在第二个元素位置),并且把prev的位置设置成key基准点。当cur遇到比key小的元素则prev往后走一步,然后prev的元素与cur的元素进行交换,交换过后cur继续走。当cur遇到比key大的元素则prev不动,cur继续走。


        最后当cur走到数组外面表示遍历结束,这时候prev所在位置的元素与key元素交换,并且作为新的基准点。

        此时数组也被分成了两个区间,并且左边区间的元素小于key,右边区间的元素大于key。 

        双指针法实现快速排序代码如下:

#include<stdio.h>void swap(int* p1, int* p2)//交换函数
{int temp = *p1;*p1 = *p2;*p2 = temp;
}
int PatrSort3(int* a, int left, int right)//双指针法
{int key = left;//定义基准点int prev = left;//定义两个变量int cur = left + 1;while (cur <= right)//当cur还在数组内时{if (a[cur] <= a[key] && prev != cur)//若cur元素小于keyi元素{prev++;//prev向后走一步swap(&a[cur], &a[prev]);//交换cur和prev位置的元素}cur++;//cur一直往后走}swap(&a[prev], &a[key]);//最后交换prev和key的元素key = prev;//更新key的位置return key;//返回key的下标
}void Qsort(int* a, int begin, int end)//快速排序(递归法)
{if (begin >= end)//{return;}int key = PatrSort3(a, begin, end);//每次递归都会找到一个属于该数组的keyQsort(a, begin, key - 1);//递归左右区间Qsort(a, key + 1, end);
}void PrintArr(int* a, int n)//打印数组
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Test_Qsort()//快排(递归)
{int arr[] = { 5,10,6,1,2,4,3,9 };int sz = sizeof(arr) / sizeof(int);PrintArr(arr, sz);Qsort(arr, 0, sz - 1);printf("双指针法:");PrintArr(arr, sz);
}int main()
{Test_Qsort();return 0;
}

        运行结果:

        从结果来看,三个方法都可以实现快速排序。 三种方法的效率其实也不相上下,但是目前快速排序有一个问题:如果快速排序每次选key时都能选到中位数则快速排序的效率就很好,时间复杂度为:O(N*logN),但是快速排序在面对有序数组的排序下效率会很慢,因为第一位数不是中位数,其时间复杂度为O(N^2)。

4、快速排序的优化

        针对以上的问题,如果每次选key的时候都能选到数组偏中位数的值,那么就解决了该问题,因此当我们有了一个数组的left和right,那么可以假设一个中间值:mid=(left+right)/2,通过对比他们三者之间的关系从而得到三者的中间值。

        三数取中代码如下:

int GetMid(int* a, int left, int right)//针对有序数组排序的三数取中
{int mid = (left + right) / 2;//先定义一个mid中间值//对比他们三者之间的关系,选出三者之间的中间值if (a[left] < a[mid]){if (a[right] < a[left])return left;else if (a[mid] > a[right])return right;elsereturn mid;}else{if (a[mid] > a[right])return mid;else if (a[right] > a[left])return left;elsereturn right;}
}

5、测试对比

        通过测试对比优化前的快速排序的效率和优化后快速排序的效率,测试代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>int GetMid(int* a, int left, int right)//针对有序数组排序的三数取中
{int mid = (left + right) / 2;//先定义一个mid中间值//对比他们三者之间的关系,选出三者之间的中间值if (a[left] < mid){if (a[right] < a[left])return left;else if (a[mid] > a[right])return right;elsereturn mid;}else{if (a[mid] > a[right])return mid;else if (a[right] > a[left])return left;elsereturn right;}
}void swap(int* p1, int* p2)//交换函数
{int temp = *p1;*p1 = *p2;*p2 = temp;
}
int PatrSort3(int* a, int left, int right)//双指针法
{int key = left;//定义基准点int temp = GetMid(a, left, right);swap(&a[temp], &a[key]);//使keyi处的元素是三数取中的结果int prev = left;//定义两个变量int cur = left + 1;while (cur <= right)//当cur还在数组内时{if (a[cur] <= a[key] && prev != cur)//若cur元素小于keyi元素{prev++;//prev向后走一步swap(&a[cur], &a[prev]);//交换cur和prev位置的元素}cur++;//cur一直往后走}swap(&a[prev], &a[key]);//最后交换prev和key的元素key = prev;//更新key的位置return key;//返回key的下标
}void Qsort(int* a, int begin, int end)//快速排序(递归法)
{if (begin >= end)//{return;}int key = PatrSort3(a, begin, end);//每次递归都会找到一个属于该数组的keyQsort(a, begin, key - 1);//递归左右区间Qsort(a, key + 1, end);
}void Contrast_test()//对比测试
{//手动构建数组int n = 100000;srand(time(0));int* n1 = (int*)malloc(sizeof(int) * n);int* n2 = (int*)malloc(sizeof(int) * n);int* n3 = (int*)malloc(sizeof(int) * n);int* n4 = (int*)malloc(sizeof(int) * n);int* n5 = (int*)malloc(sizeof(int) * n);int* n6 = (int*)malloc(sizeof(int) * n);for (int i = 0; i < n; i++){n1[i] = rand() % 10000;n2[i] = n1[i];n3[i] = n1[i];n4[i] = n1[i];n5[i] = n1[i];n6[i] = n1[i];}//先让数组排成有序//clock函数返回的是系统启动到调用该函数的时间,单位是毫秒,并存到变量中int start1 = clock();Qsort(n1, 0, n - 1);int end1 = clock();//在进行对有序数组的排序int start2 = clock();Qsort(n1, 0, n - 1);int end2 = clock();printf("key为中间值,对有序数组的排序:\n");printf("Test_PatrSort3:%d\n", end1 - start1);printf("Test_PatrSort3:%d\n", end2 - start2);free(n1);free(n2);free(n3);free(n4);free(n5);free(n6);
}int main()
{Contrast_test();return 0;
}

         运行结果(优化前):

         运行结果(优化后):

        通过结果可以发现,优化之后的快速排序在面对有序的数组效率依然很高。 

结语:

       以上就是关于快速排序的介绍,最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!(~ ̄▽ ̄)~

 

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

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

相关文章

30秒搞定一个属于你的问答机器人,快速抓取网站内容

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 文章目录 简介运行效果GitHub地址 简介 爬取一个网站的内容&#xff0c;然后让这个内容变成你自己的私有知识库&#xff0c;并且还可以搭建一个基于私有知识库的问…

没有预装Edge浏览器的Windows系统安装Edge正式版的方法,离线安装和在线安装

一、在线安装 没有预装Edge浏览器的Windows系统安装Edge正式版的方法 二、离线安装 进入到下面这个目录 C:\Program Files (x86)

【Web】NISACTF 2022 个人复现

目录 ①easyssrf ②babyupload ③ level-up ④bingdundun~ 明天就新生赛了&#xff0c;练套题保持下手感吧 &#xff08;文章只选取了一部分&#xff09; ①easyssrf 输入/flag 输入file:///fl4g 访问/ha1x1ux1u.php ?filephp://filter/convert.base64-encode/resource/…

C++利剑string类(详解)

前言&#xff1a;大家都知道在C语言里面的有 char 类型&#xff0c;我接下来要讲的 string 类功能是使用 char 类型写的类&#xff0c;当然这个是C官方写的&#xff0c;接下来我们将会学会使用它&#xff0c;我们会发现原来 char 这种类型是还能这么好用&#xff0c;授人以…

【VerilogVCS仿真_2023.11.15】

HDL&#xff1a;硬件描述语言&#xff0c;并发&#xff0c;时序RTL&#xff1a;寄存器传输级语言 Verilog和VHDL的区别&#xff1a;VHDL侧重于系统级描述——系统级设计人员所采用&#xff0c;Verilog侧重于模块行为的抽象描述——电路级设计人员 前端&#xff1a;系统级、算法…

linux上编写进度条

目录 一、预备的两个小知识1、缓冲区2、回车与换行 二、倒计时程序三、编写入门的进度条四、编写一个正式的五、模拟实现和下载速度相关的进度条 一、预备的两个小知识 1、缓冲区 首先认识一下缓冲区&#xff1a;先写一个.c文件如下&#xff1a; 我们执行一下这个程序时&…

【产品应用】一体化伺服电机在摆轮分拣机中的应用

随着物流和制造业的快速发展&#xff0c;分拣机的应用越来越广泛。摆轮分拣机作为一种常见的分拣设备&#xff0c;具有高效、准确、灵活等特点&#xff0c;被广泛应用于各类物流分拣场景。而一体化伺服电机在摆轮分拣机中的应用&#xff0c;为分拣机的性能提升和优化提供了新的…

专业视频剪辑利器Final Cut Pro for Mac,让你的创意无限发挥

在如今的数字时代&#xff0c;视频内容已经成为人们生活中不可或缺的一部分。无论是在社交媒体上分享生活点滴&#xff0c;还是在工作中制作专业的营销视频&#xff0c;我们都希望能够以高质量、高效率地进行视频剪辑和制作。而Final Cut Pro for Mac作为一款专业级的视频剪辑软…

6.5 Windows驱动开发:内核枚举PspCidTable句柄表

在 Windows 操作系统内核中&#xff0c;PspCidTable 通常是与进程&#xff08;Process&#xff09;管理相关的数据结构之一。它与进程的标识和管理有关&#xff0c;每个进程都有一个唯一的标识符&#xff0c;称为进程 ID&#xff08;PID&#xff09;。与之相关的是客户端 ID&am…

【蓝桥杯软件赛 零基础备赛20周】第6周——栈

文章目录 1. 基本数据结构概述1.1 数据结构和算法的关系1.2 线性数据结构概述1.3 二叉树简介 2. 栈2.1 手写栈2.2 CSTL栈2.3 Java 栈2.4 Python栈 3 习题 1. 基本数据结构概述 很多计算机教材提到&#xff1a;程序 数据结构 算法。 “以数据结构为弓&#xff0c;以算法为箭”…

uniapp uview u-input在app(运行在安卓基座上)上不能动态控制type类型(显隐密码)

开发密码显隐功能时&#xff0c;在浏览器h5上功能是没问题的 <view class"login-item-input"><u-input:type"showPassWord ? password : text"style"background: #ecf0f8"placeholder"请输入密码"border"surround&quo…

麻吉POS集成:如何无代码开发实现电商平台和CRM系统的高效连接

麻吉POS集成的前沿技术&#xff1a;无代码开发 在竞争激烈的电商市场中&#xff0c;商家们急需一种高效且易于操作的技术手段来实现系统间的快速连接与集成。麻吉POS以其前沿的无代码开发技术&#xff0c;让这一需求成为可能。无代码开发是一种允许用户通过图形用户界面进行编…

LiteOS内存管理:TLSF算法

问题背景 TLSF算法主要是面向实时操作系统提出的&#xff0c;对于RTOS而言&#xff0c;执行时间的确定性是最根本的&#xff0c;然而传统的动态内存分配器&#xff08;DMA&#xff0c;Dynamic Memory Allocator&#xff09;存在两个主要问题&#xff1a; 最坏情况执行时间不确…

【LeetCode】栈和队列OJ题---C语言版

栈和队列OJ题 1.括号匹配问题&#xff08;1&#xff09;题目描述&#xff1a;&#xff08;2&#xff09;思路表述&#xff1a;&#xff08;3&#xff09;代码实现&#xff1a; 2.用队列实现栈&#xff08;1&#xff09;题目描述&#xff1a;&#xff08;2&#xff09;思路表述&…

Python学习路线 - Python语言基础入门 - 准备工作

Python学习路线 - Python语言基础入门 - 准备工作 初识PythonPython的优点 什么是编程语言Python环境安装Windows系统Python安装Python验证 MacOS系统Linux系统 第一个Python程序常见问题 Python解释器Python解释器概念Python解释器存放位置Python解释器运行".py"文件…

vue3请求代理proxy中pathRewrite失效

问题引入 在vue3配置请求代理proxy的时候pathRewrite失效。 有这样一个例子&#xff0c;作用是为了把所有以/api开头的请求代理到后端的路径和端口上&#xff0c;在vue.config.js配置文件中 设置了代理跨域和默认端口。但是重新运行之后发现端口是改了&#xff0c;但是路径仍然…

【工作生活】汽车ECU开发内容简介

目录 1. 目标 2. 要分享什么 3.1 行业知识 3.1.1车载行业知识&#xff1a; 3.1.2项目&#xff1a; 3.1.3开发测试工具&#xff1a; 3.2 硬件平台 3.3 基础知识 3.4 工作生活 3. 我们是谁 1. 目标 随着新能源汽车的快速崛起&#xff0c;汽车电子行业开始快速发展&…

Redis数据结构之跳表

跳表是一种有序的数据结构&#xff0c;它通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。其核心思想就是通过建立多级索引来实现空间换时间。 在Redis中&#xff0c;使用跳表作为Zset的一种底层实现之一&#xff0c;这也是跳表在Redis中的…

SpringBoot 集成 ChatGPT,实战附源码

1 前言 在本文中&#xff0c;我们将探索在 Spring Boot 应用程序中调用 OpenAI ChatGPT API 的过程。我们的目标是开发一个 Spring Boot 应用程序&#xff0c;能够利用 OpenAI ChatGPT API 生成对给定提示的响应。 您可能熟悉 ChatGPT 中的术语“提示”。在 ChatGPT 或类似语…

如何本地搭建个人hMailServer邮件服务并实现远程发送邮件

文章目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpola…