[数据结构1.0]快速排序

最近学习了快速排序,鼠鼠俺来做笔记了!

本篇博客用排升序为例介绍快速排序!

1.快速排序

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

如果对上面的介绍蒙圈的话,没关系,我们继续看下面的内容,会仔细介绍的!

1.1.快速排序的”单趟“

快速排序的”单趟“简单来说就是在需排序乱序数组中任取一个元素作为基准值,经过”单趟“过后,大于或者等于基准值的元素都排在基准值的后面,小于或者等于基准值的元素都排在基准值的前面,也就是说基准值所在的位置就是它应该出现的位置,这个基准值就排好了!

对于”单趟“的实现方法有但不限于下面三种:

1.1.1.hoare版本

这个动图就是hoare版本的”单趟“实现方法!这里取第一个元素6为基准值;R从最”右边“开始找比基准值小的元素,L从最”左边“开始找比基准值大的元素, 然后交换下标为R和L的元素;R继续找比基准值小的元素,L继续找比基准值大的元素,再交换下标为R和L的元素…………直到R和L相遇,将基准值和相遇位置的元素即可!

其实这个本质就是将比基准值大的元素”甩“到”后面“,将比基准值小的元素”甩“到”前面“。

也许会有疑问,怎么保证相遇位置的元素一定不大于基准值呢?

因为只要是R先动,L后动的话必然能保证相遇的元素一定不大于基准值!

相遇无非两种情况:

1.R遇L:R在去找小于基准值的元素的过程中,下标为L的元素必然是不大于基准值的元素。当R去找小于基准值的元素没有找到却遇到L时, 那么相遇位置的值就是不大于基准值的元素。

2.L遇R:由于R先动,那么L在找大于基准值的元素的过程中,下标为R的元素必然是不大于基准值的元素。当L去找大于基准值的元素没有找到却遇到R时,那么相遇位置的值就是不大于基准值的元素。

 hoare版本的“单趟”代码如下,需排序乱序数组下标为begin—end:

//hoare版本int keyi = begin;int left = begin, right = end;while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[right]);keyi = right;

由于hoare版本写起来很容易出错误,所以我们一般写下面两种版本!

1.1.2.挖坑法版本

也是取第一个元素6为基准值。初始坑位设置为基准值下标。让R从最“右边”开始找比基准值小的元素,找到后将下标为R的元素填入坑位,那么新的坑位就变成了R;让L从最“左边”开始找比基准值大的元素,找到后将下标为L的元素填入坑位,那么新的坑位就变成了L;R再找比基准值小的元素……直到R和L相遇,将基准值填入坑位即可。

本质就是R找小填入“左边”坑位,L找大填入“右边”坑位,最后一个坑位必定是R和L相遇位置,填入基准值就好。

挖坑法版本“单趟”代码如下,需排序乱序数组下标为begin—end:

//挖坑法版本int  key=a[begin];int left = begin, right = end;int hole = begin;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;int keyi = hole;
1.1.3.前后指针版本

取第一个元素6为基准值。prev初始指向基准值,cur初始指向基准值的下一个元素。cur遍历数组:如果cur遇到大于基准值的元素,++cur;否则++prev、cur指向的元素和prev指向的元素交换、++cur。

前后指针版本“单趟”代码如下, 需排序乱序数组下标为begin—end:

//前后指针版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;

这里指针写成了下标的形式,思想没变,本质上还是一样的!

1.2.快速排序的递归写法 

经过“单趟”过后,被选中的基准值就排在了它该出现的位置,就是说当数组排好变得有序后,基准值就在“单趟”过后出现的位置!

既然基准值已经拍好了,如果基准值的“左边”的元素集合能有序并且基准值的“右边”的元素集合能有序,那么需排序的乱序数组就排好了,变得有序了。举个例子:

如图, 所以说快速排序一种二叉树结构的交换排序方法。进行“单趟”排好基准值,递归“左边”元素集合…………“左边”元素集合排好后,递归“右边”元素集合…………“右边”元素集合排好后就搞定了!

递归结束条件:当元素集合只有1个值或为空。

看看快速排序的递归写法代码:

void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//hoare版本/*int keyi = begin;int left = begin, right = end;while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[right]);keyi = right;*///挖坑法版本int  key=a[begin];int left = begin, right = end;int hole = begin;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;int keyi = hole;//前后指针版本/*int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;*/QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

我们试试这个快速排序:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>void PrintArrar(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//hoare版本/*int keyi = begin;int left = begin, right = end;while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[right]);keyi = right;*///挖坑法版本/*int  key=a[begin];int left = begin, right = end;int hole = begin;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;int keyi = hole;*///前后指针版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int a[] = { 9,8,5,47,6,3,2,10 };PrintArrar(a, sizeof(a) / sizeof(a[0]));QuickSort(a, 0, sizeof(a) / sizeof(a[0]) - 1);PrintArrar(a, sizeof(a) / sizeof(a[0]));return 0;
}

没问题的:

1.3.快速排序的非递归写法

鼠鼠这里介绍一种快速排序的非递归写法。

需要用到鼠鼠前面博客 介绍的栈,利用到栈写的快速排序没有用到递归但思想却很像递归!

 非递归写法思想如上图。我们看快速排序非递归代码如下,其中变量s是栈。鼠鼠这里“单趟”选择挖坑法版本,当然老爷们可以用其他版本:

void QuickSortNonr(int* a, int begin, int end)
{Stack s;StackInit(&s);StackPush(&s, end);StackPush(&s, begin);while (!StackEmpty(&s)){int start = StackTop(&s);StackPop(&s);int finish = StackTop(&s);StackPop(&s);int  key = a[start];int left = start, right = finish;int hole = start;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;if (start < left - 1){StackPush(&s, left - 1);StackPush(&s, start);}if (left + 1 < finish){StackPush(&s, finish);StackPush(&s, left + 1);}}StackDestroy(&s);
}

我们要用到栈,所以记得将我们自己写的栈的头文件和源文件拷贝一份到快速排序的工程目录下 ,再包栈的头文件就可以用了。我们试试:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include"Stack.h" //注意包含栈的头文件void PrintArrar(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void QuickSortNonr(int* a, int begin, int end)
{Stack s;StackInit(&s);StackPush(&s, end);StackPush(&s, begin);while (!StackEmpty(&s)){int start = StackTop(&s);StackPop(&s);int finish = StackTop(&s);StackPop(&s);int  key = a[start];int left = start, right = finish;int hole = start;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;if (start < left - 1){StackPush(&s, left - 1);StackPush(&s, start);}if (left + 1 < finish){StackPush(&s, finish);StackPush(&s, left + 1);}}StackDestroy(&s);
}int main()
{int a[] = { 9,8,5,47,6,3,2,10 };PrintArrar(a, sizeof(a) / sizeof(a[0]));QuickSortNonr(a, 0, sizeof(a) / sizeof(a[0]) - 1);PrintArrar(a, sizeof(a) / sizeof(a[0]));return 0;
}

 没问题吧!

2.快速排序递归写法优化 

2.1.三数取中法选基准值

对于递归写法来说,对于需排序数组本身就是升序或者降序的情况适应的不是很好,因为:

1.固定了选择元素集合第一个元素为基准值,每次“单趟”过后都会导致某一边元素集合为空的情况。这样的话如果本身就是升序或者降序的需排序数组个数有n个的话,就要递归n层,很容易栈溢出!

2.每次“单趟”时间复杂度是O(N),递归n层的话快速排序时间复杂度是O(N^2),时间效率不划算!

所以我们有三数取中选基准值,我们取元素集合第一个元素、最后一个元素和中间那个元素,这三个元素比较得出第二大的元素,将这个元素与元素集合第一个元素交换再进行“单趟”。

这样的话就能很好适应需排序数组本身就是升序或者降序的情况,因为这样经过“单趟”之后,基准值一定会出现在“中间”。这样子去递归的话,每一层递归都会被“二分”,递归层数大大减少,递归log(N)层就行!

加入了三数取中选基准值的递归写法的快速排序时间复杂度是O(N*logN)。

而且加入了三数取中选基准值的递归写法的快速排序对于需排序数组本身不是升序或者降序的情况一样有帮助,可以让每层递归尽量“二分”,从而减少递归层数!

三数取中法代码:

int GetMidi(int* a, int begin, int end)
{int midi = begin + (end - begin) / 2;if (a[begin] > a[midi]){if (a[begin] > a[end]){if (a[midi] > a[end]){return midi;}elsereturn end;}else{return begin;}}else{if (a[midi] > a[end]){if (a[end] > a[begin]){return end;}else{return begin;}}else{return midi;}}
}

当没有加入三数取中选基准值的递归写法的快速排序排10000个升序元素组成的数组时,在Debug环境下就会崩溃,栈溢出了:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>int GetMidi(int* a, int begin, int end)
{int midi = begin + (end - begin) / 2;if (a[begin] > a[midi]){if (a[begin] > a[end]){if (a[midi] > a[end]){return midi;}elsereturn end;}else{return begin;}}else{if (a[midi] > a[end]){if (a[end] > a[begin]){return end;}else{return begin;}}else{return midi;}}
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//三数取中选基准值/*int midi = GetMidi(a, begin, end);Swap(&a[begin], &a[midi]);*///前后指针版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);srand((unsigned int)time(0));a[0] = rand();for (int i = 1; i < n ; i++){a[i] = a[i-1] + 1;}int begin = clock();QuickSort(a, 0, n - 1);int end = clock();printf("%d\n", end - begin);return 0;
}

结果:

当加入三数取中选基准值的递归写法的快速排序,排100w个升序元素组成的数组都没问题,Debug环境下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>int GetMidi(int* a, int begin, int end)
{int midi = begin + (end - begin) / 2;if (a[begin] > a[midi]){if (a[begin] > a[end]){if (a[midi] > a[end]){return midi;}elsereturn end;}else{return begin;}}else{if (a[midi] > a[end]){if (a[end] > a[begin]){return end;}else{return begin;}}else{return midi;}}
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//三数取中选基准值int midi = GetMidi(a, begin, end);Swap(&a[begin], &a[midi]);//前后指针版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int n = 1000000;int* a = (int*)malloc(sizeof(int) * n);srand((unsigned int)time(0));a[0] = rand();for (int i = 1; i < n ; i++){a[i] = a[i-1] + 1;}int begin = clock();QuickSort(a, 0, n - 1);int end = clock();printf("%d\n", end - begin);return 0;
}

 结果:

2.2.小区间优化

递归到小的子区间(数量少的元素集合)时,可以考虑使用插入排序。这样子可以减少大部分的递归,因为大部分的递归都是由小的子区间产生的。不过由于编译器优化的厉害,小区间优化效果不是很明显,鼠鼠就在这里顺便提一提算了!

感谢阅读!

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

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

相关文章

Java面试八股之一个char类型变量能不能存储一个中文字符

Java中一个char类型变量能不能存储一个中文字符&#xff1f;为什么&#xff1f; Java中一个char类型变量可以存储一个中文字符。原因如下&#xff1a; Unicode编码支持&#xff1a;Java语言采用Unicode字符集作为其内建字符编码方式。Unicode是一种广泛接受的字符编码标准&am…

两小时看完花书(深度学习入门篇)

1.深度学习花书前言 机器学习早期的时候十分依赖于已有的知识库和人为的逻辑规则&#xff0c;需要人们花大量的时间去制定合理的逻辑判定&#xff0c;可以说是有多少人工&#xff0c;就有多少智能。后来逐渐发展出一些简单的机器学习方法例如logistic regression、naive bayes等…

什么是CCRC?做什么用的?

CCRC&#xff08;中国网络安全审查认证和市场监管大数据中心&#xff09;原名为中国网络安全审查技术与认证中心&#xff0c;也被称为中国信息安全认证中心&#xff08;ISCCC&#xff09;。 该中心是经中央机构编制委员会办公室批准成立的&#xff0c;其主要职责是依据国家法律…

超级简单的地图操作工具开发可疑应急,地图画点,画线,画区域,获取地图经纬度等

使用echars的地图画点,画线,画区域,获取地图经纬度等 解压密码:10086007 地图也是用临时的bmap.js和china.js纯离线二选一 一共就这么多文件 画点,画线,画区域 点击地图获取经纬度-打印到控制台,这样就能渲染航迹,多变形,结合其他算法算圆等等操作 下载资源:https://download…

代码随想录训练营Day28:贪心算法06

1.738单调递增的数字 贪心策略&#xff1a;如果strNum[i]<strNum[i-1]那么strNum[i] 9,strNum[i-1]--;//比如87对应的最大的单调递增的就是79. 具体实现&#xff1a; 对于遇到小于的情况&#xff1a;如果strNum[i]<strNum[i-1]那么strNum[i] 9,strNum[i-1]--;遍历顺…

linux phpstudy 重启命令

[rootLinuxWeb phpstudy]# ./system/phpstudyctl restart 查看命令 1) phpstudy -start 启动小皮面板 2) phpstudy -stop 停止小皮面板 3) phpstudy -restart 重启小皮面板 4) phpstudy -status 查询面板状态 5) phpstudy -in…

OFDM802.11a的FPGA实现(十五)短训练序列:STS(含Matlab和verilog代码)

原文链接&#xff08;相关文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA实现 1.前言 在之前已经完成了data域数据的处理&#xff0c;在构建整个802.11a OFDM数据帧的时候&#xff0c;还剩下前导码和signal域的数据帧&#xff0c;这两部分的内容。 PLCP的前导部分…

Nodejs笔记2

模块化 模块化初体验 模块暴露数据 导入模块 fs 写绝对路径 require写相对路径不会受到影响 ./../不能省略 js 和json文件后缀可以省略 如果存在 命名相同的js和json文件&#xff0c;优先导入js文件 导入文件夹时的情况 require导入模块的基本流程 commonJS模块…

其它高阶数据结构①_并查集(概念+代码+两道OJ)

目录 1. 并查集的概念 2. 并查集的实现 3. 并查集的应用 3.1 力扣LCR 116. 省份数量 解析代码1 解析代码2 3.2 力扣990. 等式方程的可满足性 解析代码 本篇完。 写在前面&#xff1a; 此高阶数据结构系列&#xff0c;虽然放在⑤数据结构与算法专栏&#xff0c;但还是作…

【数据可视化01】matplotlib实例介绍4之六边形分箱图

目录 一、引言二、实例介绍 一、引言 hexbin是一个二维直方图&#xff0c;其中箱子是六边形&#xff0c;颜色表示每个箱子内的数据点数。 二、实例介绍 import matplotlib.pyplot as plt import numpy as np# Fixing random state for reproducibility np.random.seed(19680…

服务器利用率的神器脚本

在服务器管理的过程中&#xff0c;了解服务器的各项性能指标是至关重要的。无论是CPU的负载情况&#xff0c;内存使用情况&#xff0c;还是硬盘的存储空间以及TCP连接状态&#xff0c;这些都是我们判断服务器健康状态和性能的重要依据。然而&#xff0c;手动一项项去检查这些指…

【MySQL】Mysql——安装指南(Linux)

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以; Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 3. 上传MySQL安装包 4. 创建目录,并解压 mkdir mysqltar -xvf mysql-8.0.26-1.el7.x86_64.rpm-bundle.tar -C mysql5. 安装mysql的安装包 …

pip镜像源

1.1 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple 1.2 阿里云 https://mirrors.aliyun.com/pypi/simple/ 1.3 网易 https://mirrors.163.com/pypi/simple/ 1.4 豆瓣 https://pypi.douban.com/simple/ 1.5 百度云 https://mirror.baidu.com/pypi/simple/ 1.6 中科大 ht…

RAG 面向 LLM: 基于检索增强的大语言模型调研

摘要 作为 AI 领域最先进的技术之一,检索增强生成(RAG)技术可以提供可靠和最新的外部知识,为众多任务提供巨大的便利。特别是在 AI 生成内容(AIGC)时代,RAG 中检索强大的提供额外知识的能力使得检索增强生成能够辅助现有生成式 AI 生产高质量输出。最近,大语言模型(LLM)在语言…

Zoho CRM企业成长的智能引擎,智能化销售自动化

数字化时代&#xff0c;客户体验已成为企业竞争的核心要素。卓豪Zoho CRM&#xff0c;作为全球领先的SaaS云端客户关系管理平台&#xff0c;正引领着一场企业运营模式的变革&#xff0c;助力超过25万家企业跨越180多个国家&#xff0c;实现客户互动与业务增长的无缝对接。让我们…

广汽原车控制系统CAN协议控制汽车基本信息获取及数据应用

在现代汽车工业的迅速发展中&#xff0c;车辆控制系统的智能化和网络化已成为提升汽车性能的关键。广汽作为中国汽车行业的佼佼者&#xff0c;其在原车通信网络方面也取得了显著的成就。特别是广汽原车CAN&#xff08;Controller Area Network&#xff09;协议的应用&#xff0…

短视频最后的慢动作怎么做:成都鼎茂宏升文化传媒公司

短视频最后的慢动作怎么做&#xff1a;技巧与创意实践指南 在短视频创作的浩瀚宇宙中&#xff0c;慢动作特效如同一颗璀璨的星辰&#xff0c;为作品增添无限魅力与情感深度。它不仅能够放大细节之美&#xff0c;还能延长关键瞬间&#xff0c;引发观众强烈的情感共鸣。短视频最…

SpringBoot项目的项目部署全过程

一、前端 安装nginx 1.将提前准备好的nginx的安装包上传到Linux中/opt目录下(我用的是Xftp) 2.解压 2.1:在xshell中解压该文件: tar -zxvf nginx-1.20.1.tar.gz 2.2:进入解压后的目录 cd nginx-1.20.1/ 2.3:安装需要的依赖 yum -y install zlib zlib-devel openssl openssl-de…

FreeRTOS【4】线程挂起和恢复

1.开发背景 基于上一篇指引&#xff0c;成功创建并启动线程后&#xff0c;线程已经开始运行了&#xff0c;但是有时我们需要线程暂停运行&#xff0c;例如某个线程是控制 LED 闪灯的&#xff0c;如果现在需要让 LED 停止工作&#xff0c;单纯的关闭 LED 是没用的&#xff0c;因…

【智能优化算法】雁群优化算法(Wild Geese Algorithm,WGA)

雁群优化算法(Wild Geese Algorithm,WGA)是期刊“Array”的2021年智能优化算法 01.引言 雁群优化算法(Wild Geese Algorithm,WGA)用于大规模全局优化&#xff0c;并利用IEEE CEC 2008和CEC 2010高维D100、500、1000特别会议的大规模测试函数验证了该算法的效率和性能。WGA的灵…