快速排序

快速排序的基本思想(图文详解) 快速排序算法通过多次比较和交换来实现排序,其排序流程如下:(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。(4)重复上述过程,可以看出,这是一个递归定义。 阅读详情

快速排序

算法思想

快速排序的基本思想:选择一个数字作为基准数字,比这个数字大的放在右边,小于等于这个数字的放在左边;然后就可以分成两个子序列,对于每个子序列进行同样的操作,直到整个序列是有序的。

算法步骤:

  1. 从序列中挑出一个数字作为基准(pivot);
  2. 将给定的序列按照选定的基准分成两个子序列,小于等于基准的放在左侧,大于基准的放在右侧;
  3. 对于每个子序列采用递归的操作,重复1~2步骤,直到子序列的长度为1.

这里给出一个动画来展示上述的算法步骤:
在这里插入图片描述
为了可以使的算法步骤更明了,举一个例子,看看具体是如何操作的。给定一个数组 arr=[6,1,2,7,9,3,4,5,10,8],快排是如何把他分成两个子数组的,经典的快排算法选择的基准一般是最左侧或者最右侧,这里选择最左侧为基准,即pivot=6,那么大于6的放在右边,小于等于6的放在左边,一次排序的结果就是 arr=[5,1,2,4,3,6,9,7,10,8],通过下面的动画可以更清晰的看出是怎么进行交换的
在这里插入图片描述
对于快速排序来说,它的时间复杂度为 O(n×logn)O(n \times logn)O(n×logn),是因为我们需要划分lognlognlogn次,在每一次划分里面需要循环nnn次,所以时间复杂度为 O(n×logn)O(n \times logn)O(n×logn),空间复杂度也是 O(logn)O(logn)O(logn),因为,每次划分子数组都需要记录分界点的位置,需要partition的次数,就是产生的变量数,累计 lognlognlogn 次划分,所以,空间复杂度也是 O(logn)O(logn)O(logn)。快速排序是一种不稳定的排序算法,这是因为在基准数字和边界地方交换的过程中,破坏了稳定性,所以是不稳定的算法。

代码实现

#include <iostream>
#include <vector>

using namespace std;

int partition(vector<int>& vec, int left, int right) {
	int pivot = vec[left]; // 选择基准数字

    // 当左侧的下标大于等于右侧时,才跳出循环
	while (left < right) {
        // 大于基准的放在右侧
		while (left < right && vec[right] > pivot) { 
			right--;
		}
		vec[left] = vec[right]; // 交换数字
        // 小于等于基准的放在左侧
		while (left < right && vec[left] <= pivot) {
			left++;
		}
		vec[right] = vec[left]; // 交换数字
	}
	vec[left] = pivot; // 把基准数字更新到分界点的位置

	return left;    // 返回分界点
}

void quickSort(vector<int>& vec, int left, int right) {
    // 如果左侧的下标小于右侧的下标,执行递归操作
	if (left < right) {
		int par = partition(vec, left, right); // 获取分界点
		quickSort(vec, left, par-1);           // 左侧子序列
		quickSort(vec, par + 1, right);        // 右侧子序列
	}
}

void quickSort(vector<int>& vec) {
    // 当vec的长度小于2时,直接返回
	if (vec.size() < 2) {
		return;
	}
    // 调用递归
	quickSort(vec, 0, vec.size() - 1);
}

int main() {
	vector<int> arr = { 6,1,2,7,9,3,4,5,10,8 };
	quickSort(arr, 0, arr.size()-1);
	for (int i = 0; i < arr.size(); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

	return 0;
}

对于子序列的划分,也就是partition的过程,即大于基准数字的放在右侧,小于等于基准数字的放在左侧,可以也可以采用下面的代码,也是比较简单,可以看一下

// 交换两个数字
void mySwap(int& x, int& y) {
	int temp = x;
	x = y;
	y = temp;
}
// 数组划分子序列
int partition1(vector<int>& vec, int left, int right) {

	int index = left;   // 定义左边子序列的下标,开始是定义left-1,表示没有元素在左侧子序列中
	int pivot = vec[left];  // 定义基准数字

    // 遍历vec,从left开始,一直到right
	for (int i = left+1; i <= right; i++) { 
        // 数字小于等于基准,那么左侧的子序列的就扩充一个位置,并且交换数字
		if (vec[i] <= pivot) {
			mySwap(vec[i], vec[index+1]);
			index++;
		}
	}
    // 把基准数字和最左侧的数字交换
	mySwap(vec[left], vec[index]);

	return index;
}

优化经典快速排序

经典的排序算法是把大于基准的数字放在右侧,小于等于基准的数字放在左侧。举个例子,如果 arr = [6,1,4,3,7,6,6,8,9,10],如果pivot=6,左侧arrL=[6,1,4,3,6],右侧arrR=[7,8,9,10]。这样的话,只是排好了一个数字6,但是还有两个数字6,这样的话,还需要两次排序才可以得到 arr =[3,1,4,6,6,6,7,8,9,10],那么如果我们可以把数组划分为三块,左侧的放小于基准数字,中间的放等于基准数字,右侧放大于基准数字,这样就可以加快partition的过程,与经典快排不同的是我们需要返回的是下图的两个下标,即中间子序列的左侧下标和右侧下标
在这里插入图片描述

代码实现

#include <iostream>
#include <vector>

using namespace std;

/*
* 交换两个数
*/
void mySwap(vector<int>& vec, int index1, int index2) {
	int temp = vec[index1];
	vec[index1] = vec[index2];
	vec[index2] = temp;
}
/*
* 升级的子数组划分函数
*/
vector<int> partitionImprove(vector<int>& vec, int left, int right) {
	vector<int> res;             // 存储中间子数组的左右下标

	int pivot = vec[left];       // 定义基准数字
	int less = left;             // 左侧子数组开始的下标
	int more = right+1;          // 右侧子数组开始的下标
	int index = left+1;          // 遍历数组开始的下标
	
 	while (index < more) {
		// 如果小于基准数字,左侧子数组扩展一个位置,并且原地交换,index前进一位
		if (vec[index] < pivot) {
			mySwap(vec, less+1, index);
			less++;
			index++;
		}
		else if (vec[index] > pivot) {   // 如果大于基准数字,右侧子数组扩展一个位置,原地交换
			mySwap(vec, more-1, index);
			more--;

		}
		else {           //  中间子数组的范围
			index++;
		}
		
	}
	mySwap(vec, less, left);  // 把临界位置的数字和基准数字交换
	res = { less, more-1 };   // 中间子数组的左右下标

	return res;
}

void quickSort(vector<int>& vec, int left, int right) {
	if (left < right) {
		vector<int> p = partitionImprove(vec, left, right); // 接收函数返回的下标
		// p的长度固定为2,p[0]为最左侧已经排好的,p[1]为最右侧
		quickSort(vec, left, p[0] - 1);  // 其中的p[0]-1表示的是左侧子数组最右边未排序的数字
		quickSort(vec, p[1]+1, right);   // 其中的p[0]+1表示的是右侧子数组最左边未排序的数字
	}
}

void quickSort(vector<int>& vec) {
	// 长度小于2,表示没有或者只有一个数字,那么直接返回,不需要排序
	if (vec.size() < 2) {
		return;
	}
	// 调用递归
	quickSort(vec, 0, vec.size() - 1);
}

int main() {
	vector<int> arr = { 12,42,78,54,23,123,34,34,5,12};

	quickSort(arr);

	for (int i = 0; i < arr.size(); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

	return 0;
}

随机快速排序

经典快速排序的最坏时间复杂度为 O(n2)O(n^2)O(n2),这是弊端,分析一下这个时间复杂度是怎么来的。假如给定一个数arr=[1,2,3,4,5,6],现在选择最左侧为基准数字 pivot=1,那么左侧的子数组就没有,右侧的子数组就为arrR=[2,3,4,5,6],这样的话,下次递归就是[2,3,4,5,6],依然选择pivot=2,那么依然是左侧为空,右侧为[3,4,5,6],这样继续递归的话,每次这排序一个,每个产生一个子数组,就要分割n次,而不是lognlognlogn了,就差生了O(n2)O(n^2)O(n2)的时间复杂度。之所以产生这样的情况,是因为选择的基准数字不合适,不能把原数组均匀的分成左右两个子数组。这里采用的是产生一个随机数,产生数的范围是[left, right],即原数组的最左和最右之间,然后和基准数字交换,这样就打乱了原始的数组,改善了左右子数组的划分情况。这样的话,分割的情况就成了一个概率事件,时间复杂度为O(n2)O(n^2)O(n2)的情况就不受数据影响了。代码也是很简单,只是多了一个一句话,打乱原始数组,关键代码就是这一句 mySwap(vec, left + (rand() % (right - left + 1)), left);。整体代码如下:

#include <iostream>
#include <vector>
#include <cstdlib>

using namespace std;
/*
*  交换两个数字
*/
void mySwap(vector<int>& vec, int index1, int index2) {
	int temp = vec[index1];
	vec[index1] = vec[index2];
	vec[index2] = temp;
}

/*
* 划分子数组函数,返回中间子数组左右下标
*/
vector<int> partition(vector<int>& vec, int left, int right) {
	vector<int> res;   // 存储中间子数组的左右下标

	int pivot = vec[left];    // 定义基准数字
	int less = left;          // 左侧子数组开始的下标
	int more = right + 1;     // 右侧子数组开始的下标
	int index = left + 1;     // 遍历数组开始的下标
    
	while (index < more) {
        // 如果小于基准数字,左侧子数组扩展一个位置,并且原地交换,index前进一位
		if (vec[index] < pivot) {
			mySwap(vec, index, less + 1);
			less++;
			index++;
		}
		else if (vec[index] > pivot) {    // 如果大于基准数字,右侧子数组扩展一个位置,原地交换
			mySwap(vec, index, more - 1);
			more--;
		}
		else {
			index++;                      //  中间子数组的范围
		}
	}
	mySwap(vec, less, left);              // 把临界位置的数字和基准数字交换
	res = { less, more - 1 };             // 中间子数组的左右下标

	return res;
}

void randomQuickSort(vector<int>& vec, int left, int right) {
	if (left < right) {
        // 随机产生一个数,范围为[left,right],产生的数和最左侧的交换,打乱原始的数组
		mySwap(vec, left + (rand() % (right - left + 1)), left);  // 这一句是关键
        // pivotPos的长度固定为2,pivotPos[0]为最左侧已经排好的,pivotPos[1]为最右侧
		vector<int> pivotPos = partition(vec, left, right);
		randomQuickSort(vec, left, pivotPos[0]-1); // 其中的pivotPos[0]-1表示的是左侧子数组最右边未排序的数字
		randomQuickSort(vec, pivotPos[0] + 1, right); // 其中的pivotPos[0]+1表示的是右侧子数组最左边未排序的数字
	}
}

void quickSort(vector<int>& vec) {
	if (vec.size() < 2) {
		return;
	}
	// 调用递归
	randomQuickSort(vec, 0, vec.size() - 1);
}


int main() {

	vector<int> arr = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };

	quickSort(arr);
	for (int i = 0; i < arr.size(); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

	return 0;
}

总结

  • 稳定性:不稳定
  • 时间复杂度:O(n×logn)O(n \times logn)O(n×logn)
  • 空间复杂度:O(logn)O(logn)O(logn)

欢迎大家关注我的个人公众号,同样的也是和该博客账号一样,专注分享技术问题,我们一起学习进步
在这里插入图片描述

时间复杂度为O(N*logN)的常用排序算法总结与Java实现 时间复杂度为O(N*logN)的常用排序算法主要有四个——快速排序、归并排序、堆排序、希尔排序1.快速排序·基本思想    随机的在待排序数组arr中选取一个元素作为标记记为arr[index](有时也直接选择起始位置),然后在arr中从后至前以下标j寻找比arr[index]小的数,然后从前至后以下标i寻找比arr[index]大的数,如果i&lt;j则交换二者的值;重复以上操作,直到i&gt;... 阅读详情

相关推荐

快速排序及优化方案

文章目录快速排序及其优化方案1. 快速排序的流程2. 快速排序的实现时间复杂度计算3. 快速排序优化随机获取基准值进行优化3.2二路快速排序原理:思想:4. 总结 快速排序及其优化方案 1. 快速排序的流程 首先设定一个分界值,通过该分界值将数组分成左右两部分。 将大于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边,而等于分分界值的部分放在相对中间的部分。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于分界值,相对中间的部分的的数据等于分界值。 然后,左边和右边的数据可以独立排序

weixin_43899008的博客 6998

快速排序算法思路

快速排序的思想是分治和递归 观察一个有序的数列可以发现一个特点:有序数列中任意一个数,它左边的数一定小于等于它,它右边的数一定大于等于它,比如1, 2, 3, 3, 3, 4, 5,中间那个3,前面的三个数小于等于3,后面的数大于等于3。那么利用这个特点,只要我们能让一个数列中任意一个数都满足这样的条件:它前面的数小于等于它,后面的数大于等于它。那么整个数列就是一个有序的数列。

qq_46246906的博客 979

台达伺服B3驱动器参数的设定和CN1的接法

3. B2的电子齿轮比是直接设定P1-44(分子)和P1-45(分母)就就可以的,但是B3的有个隐藏的细节,就是P1-44的默认值77216不用去更改,这是官方客服说的,需要改的是P1-45这个参数,比如说,我使用的丝杆螺距是5mm的,那么就是以5000脉冲走完5mm为一圈,则就把P1-45设定为5000即可;我的是三菱FX3S系列的PLC,是NPN型的,输出点是24V-作为信号的,所以接41、37,具体可参考B3的。,跟着客服的讲解操作才解决的,以下就是解决方法,希望能够帮到需要的各位。

qq_38681017的博客 1万+

剑指 Offer 45. 把数组排成最小的数

我们今天通过一道稍微有点难度的题目来讲述一下排序的思想。 题目描述: 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 题目示例: 这道题其本质就是排序,在做这道题之前,我们先了解一下什么叫快速排序 快速排序的执行流程如下: 1.从序列中选择一个轴点元素pivot从最后一个元素向前遍历,一般情况下选择起始点作为privot 2.利用pivot将数组分割成2个子数组 将小于pivot的元素放在pivot的左侧 将大于pivot的元素放在pivot的右侧

zhiyikeji的博客 348

【WB32库开发】第9章 TIM1高级定时器——输出多路PWM

PWM(Pulse Width Modulation)意为脉冲宽度调制,简称脉宽调制 。在工控行业,PWM信号可以用来调节电机转速、调节变频器以及BLDC电机驱动等;在LED照明行业,可以通过PWM来控制LED的亮暗变化;还可以通过PWM信号来控制无源蜂鸣器发出简单的声音等。 WB32也可输出PWM,需配置WB32上的定时器。 在WB32中有4个定时器,其中TIM1为高级定时器,TIMx(2-4)为通用定时器,这四个定时器均挂载在APB1总线上,每个定时器都可以输出4路PWM。 本章需要使用固件库TIM例程

北海村夫的博客 4637

直击痛点:VS2015离线安装报错“安装包丢失或损坏”的根源分析与精准修复

本文深入剖析了Visual Studio 2015离线安装时出现“安装包丢失或损坏”报错的根本原因,指出问题核心在于安装程序固化的临时路径查找逻辑。文章提供了一步到位的精准修复方案,通过将ISO镜像中的完整packages文件夹复制到安装程序指定的临时路径,即可无需联网、无需操作证书直接解决问题,并对比分析了其他常见方法的局限性。

weixin_27734811的博客 356

三种快速排序以及快速排序的优化

1、快速排序的基本思想:    快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。 2、快速排序的三个步骤: (1)选择基准:在待排序列中,按照某种方式挑出一个元素,作为 "基准"(pivot) (2)分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在

思考,思考,再思考~ 5万+

从IR Drop到EM分析:数字IC电源规划中那些没人明说的细节(以MTCMOS配置为例)

本文深入探讨数字IC后端设计中电源规划的关键细节,特别关注MTCMOS配置、电源网络电阻最小化和level shifter供电等实战技巧。通过分析IR Drop和EM问题,提供优化floorplan和powerplan的具体策略,帮助工程师避免常见陷阱,提升芯片设计的稳定性和可靠性。

q9w8e7r6t5的博客 511

经典快速排序与随机快速排序

文章目录一、经典快速排序(一)、经典快速排序的实现理论(二)、经典快速排序的代码实现(Java)(三)、经典快速排序的时间复杂度二、随机快速排序(一)随机快速排序的实现理论(二)随机快速排序的代码实现(Java)(三)、随机快速排序的时间复杂度 一、经典快速排序 (一)、经典快速排序的实现理论 经典快速排序是用数组的最后一个数(这里记为 x )进行划分。也就是 &amp;lt;x、=x、&amp;gt;x。 有...

liyanan的博客 3034

Frida实战:高效dump Android内存中的so文件与修复技巧

本文详细介绍了使用Frida工具dump Android内存中已解密so文件的实战技巧。针对加固应用静态分析失效的痛点,文章提供了从环境搭建、使用frida_dump脚本自动化抓取内存数据,到利用SoFixer工具修复ELF文件结构的完整流程,帮助逆向分析人员高效获取可分析的so文件。

tt34567的博客 526

学习利用ce修改游戏生命参数

易语言代码 游戏的简陋界面 用ce查找存储这个1000即生命值的内存地址(得先附加到目标程序上,点击左上角那个电脑按钮) 看到有很多值为1000的,但哪个是生命值呢?真相只有一个 我们点击一下打我这个按钮,看下面的ce界面上地址为004A65C0处的值变了,所以这个就是我们要找的生命值 接下来我们对生命值进行修改,修改为99

zsd747289639的博客 5万+

双路快速排序

双路快速排序算法是随机化快速排序的改进版本,partition 过程使用两个索引值(i、j)用来遍历数组,将v的元素放在索引j所指向位置的右边,v代表标定值。

fangfan9的博客 2376

MT6816角度传感器SPI通信避坑指南:从数据手册到稳定读取的3个关键步骤

本文详细解析了MT6816角度传感器SPI通信中的常见问题与解决方案,包括寄存器地址计算、SPI时序配置和数据校验等关键步骤。通过实测波形和代码示例,帮助工程师避开数据手册中的隐藏陷阱,实现稳定可靠的角度数据读取,特别适用于高精度电机控制场景。

weixin_42520573的博客 516

什么是快速排序?

文章目录快速排序快速排序简介:基本思想原理快速排序法示意图排序演示Java代码 快速排序 快速排序简介: 快速排序是对冒泡排序的一种改进。 基本思想 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都小于或者等于另外一部分的所有数据,然后再按照此方法对这两部分数据分别进行快速排序,整个排序的过程可以递归进行,以此达到整个数据变成有序序列。 原理 设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选 用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它左边,所

热爱编程的菜鸟 5594

解锁 Bootloader 不再难!HyperOS澎湃系统小米与红米手机通用

必要时,可以在工具右上角安装驱动模块,手动安装驱动。解决方法:确保手机升级到最新稳定版,登录同一个小米账号,并在。新机型解锁时需要等待一段时间,通常为 7 天。如果账号安全评分较低,解锁等待时间会更长。如果解锁失败提示权限不足,说明账号存在安全问题。出现这个错误时,通常是因为网络不稳定。如果解锁次数已满,需要等到下个月。解锁后,手机会重启,首次开机会稍微慢一些,等耐心等待即可。按钮,开始解锁过程,注意:解锁会清除手机内所有数据。7次,开启开发者选项。,右键解压到指定文件夹。,使用小米账号登录。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 7214

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

快速排序——详解

快速排序(英语:Quicksort),又称分区交换排序(英语:partition-exchange sort),简称「快排」,是一种被广泛运用的排序算法。三路快速排序(英语:3-way Radix Quicksort)是快速排序和基数排序的混合。它的算法思想基于荷兰国旗问题的解法。内省排序(英语:Introsort 或 Introspective sort)4是快速排序和堆排序的结合,由 David Musser 于 1997 年发明。内省排序其实是对快速排序的一种优化,保证了最差时间复杂度为。

w_w_wwwww的博客 1373

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

图解快速排序及双路三路快速排序

前言 之前我们介绍了交换类排序中的冒泡排序,这次我们介绍另一种交换类排序叫做快速排序。快速排序的优点是原地排序,不占用额外空间,时间复杂度是O(nlogn)。 当然,对于快速排序来说,它也是有缺点的,它对于含有大量重复元素的数组排序效率是非常低的,时间复杂度会降为O(n^2)。此时需要使用改进的快速排序—双路快速排序,在双路快速排序的基础上,我们又进一步优化得到了三路快速排序。 快速排序 快速排...

子居的博客 1189

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

[算法]快速排序与随机快速排序

与归并排序一样,快速排序也运用了分治策略,而随机快速排序是对快速排序的一种优化。

hula_hula_的博客 1634

手写快速排序

快速排序简介快速排序是一种很不错的排序算法,算法复杂度为n*logn。快排使用了分而治之的思想,每次排序是都找到一个基准(我们学习时经常使用第一个作为基准),然后把小于基准的元素放到基准元素的左边,大于基准的元素放到基准元素的右边,这样一次排序下来,基准元素左边都是小于(等于)基准的数,基准右边的元素都是大于(等于)基准的元素了。快速排序关键点就是找到这样一个基准并将其放到恰到的位置。算法思路定义一

God Liao On The Way 1万+

分治之快速排序以及快速排序为何最快

算法思想 数组排序任务可以如下完成: 1)设k=a[0],将k挪到适当位置,使得比k小的元素都在k的左边,比k大的元素都在k的右边,和k相等的,不关心在k左右出现均可(O(n)时间完成) 2)把k左边的部分快速排序 3)把k右边的部分快速排序 (假设运气比较好,每次基准元素被移动到中间,这样的话时间复杂度为O(nlogn)) 如何在O(n)的时间内将数组分成两半,一般在小于基准值,一般大于基准值? 程序代码 #include<iostream> using namespace std; int

Java硬件工程师的博客 3189

【快速排序】快速排序/第k个数

acwing算法学习笔记 -----快速排序

青竹雾色间.的博客 1998
上一篇: 选择排序
下一篇: 堆排序
黑暗主宰
博客等级 码龄9年 72粉丝 169原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值