八大排序(二)快速排序

news2024/11/22 18:46:07

一、快速排序的思想

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

二、快速排序的三种实现方法

2.1、Hoare

思想:取最左边key为基准值,用right指针找比key值小的元素,用left指针找比key位置大的元素,

将两位置值进行交换,最后,将key值放在二者相遇位置上,就可保证key左边都是比key小的值,

右边都是比key大的值,然后进行递归即可实现,从相遇点分割成两部分,在分别对左右两部分重

复上述排序。

代码实现 :           

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
//Hoare
int partSort1(int* a, int left, int right)
{
	int key = left;
	while (right > left)
	{
		//从右往左找小
		while (right > left && a[right] >= a[key])
		{
			right--;
		}
		//从左往右找大
		while (right > left && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	return left;
}

void QuickSort(int* arr, int begin,int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = partSort1(arr, begin, end);
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

2.2、挖坑法                                                                                             

思想:取最左边或最右边值做key,右边形成一个坑,定义两个指针left、right指向头和尾。右边找

小值放到左边坑中右边形成新坑,左边找大值放到右边左边形成新坑,将key放到相遇位置。这时

key左边值均小于key,右边值均大于key。       

代码实现:  

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

//挖坑法
int partSort2(int* a, int left, int right)
{

	int hole = left;
	int key = a[left];
	while (right > left)
	{
		//从右往左找小
		while (right > left && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;

		//从左往右找大
		while (right > left && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

void QuickSort(int* arr, int begin,int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = partSort2(arr, begin, end);
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

   2.3、双指针法 

思想:     

1.选择数组中的第一个元素arr[startIndex]作为轴(pivot)

2.左指针为left,从最左边开始寻找第一个比pivot大的数

3.右指针为right,从最右面的一个元素开始向左寻找第一个小于等于pivot的数值

4.经过2,3两个步骤后,将会出现以下两种情况

​ (1):left和right没有相遇,此时进行交换,swap(arr,left,right);

​ (2):left和right相遇,做swap(arr,startIndex,left),然后返回left

5.partition中返回pivot用于分割数组,下一次用于排序的数组被分割为(startIndex,pivot-1),(pivot+1,endIndex)两段,进行递归操作

代码实现:

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int partSort3(int* a, int left, int right)
{

	int prev = left;
	int cur = prev + 1;
	int key = left;
	while (cur <= right)
	{
		if (a[cur] < a[key] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[key]);
	return prev;
}

void QuickSort(int* arr, int begin,int end)
{
	if (begin >= end)
	{
		return;
	int keyi = partSort3(arr, begin, end);
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

三、快速排序的优化

3.1、三数取中

当要排序的数组有序或者相对有序,比如我们要把一个逆序的数组按顺序排列,这时我们如果还选

择left为key的话,效率就会非常的低。我们要排除这种低效的可能就要让Key的值相对靠中间一

点,对此我们可以在实现一个函数,选择处left ,right ,和mid三个数中数值中间的那个数。用这个

数作为key就会避免我们遇到的这类问题。


                        

代码实现:

//三数取中
int Getmid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	// left mid right
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])  // mid是最大值
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right]) // mid是最小
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

对此我们就可以改进上面的三种方法,都可以在三种方法的开头添加这段代码,使之让key为靠中间的数,避免数组为有序的排序时间效率低的问题。

	int midi = Getmid(a, left, right);
	Swap(&a[left], &a[midi]);

3.2、小区间优化 

我们递归的深度越高效率越高,但是我们刚开始递归时深度很低,所以效率低下,所以我们可以采用高深度的时候用快速排序,在低深度的时候用直接插入排序,会对运行效率有所提高。

void QuickSort(int* a, int begain, int end)
{
	if (begain >= end)
		return;
 
	//小区间优化法 当数据量比较大的时候可以通过调整参数(20),来减小递归次数,提高性能
	if ((end - begain) > 20)
	{
		int meeti = HoareSort(a, begain, end);
		QuickSort(a, begain, meeti - 1);
		QuickSort(a, meeti + 1, end);
	}
	else
	{
		//数量比较少的时候用直接插入来排序
		InsertSort(a + begain, end - begain + 1);
	}
 
}

四、非递归实现快速排序

递归需要在栈上为函数开辟空间,32位下,栈可使用的内存大小不超过2G,如果递归较深,依然可能会发生栈溢出,这个时候递归排序就不大适用,所以需要非递归出场。

利用栈来存储区间下标,代码如下:要注意先数组头,后入数组尾。出栈时栈顶的数据为数组尾,在出才为头位置下标。

代码如下:

//交换函数
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//三数取中
int GetMinIndex(int* arr, int left, int right)
{
	int mid = (left + right) >> 1;
	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])
		{
			return mid;
		}
		if (arr[left] < arr[right] && arr[right] < arr[mid])
		{
			return right;
		}
		return left;
	}
	else//arr[left] >= arr[mid]
	{
		if (arr[left] < arr[right])
		{
			return left;
		}
		if (arr[mid] < arr[right] && arr[right] < arr[left])
		{
			return right;
		}
		return mid;
	}
}

//快排非递归
void QuickSort(int* arr, int n)
{
	ST st;
	StackInit(&st);

	//把左右区间压栈,先压右边
	StackPush(&st, n - 1);
	//后压左边
	StackPush(&st, 0);

	//只要栈不为空,就继续分割排序
	while (!StackEmpty(&st))
	{
		//从栈里面取出左右区间
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);

		int index = GetMinIndex(arr, left, right);
		//因为我们下面的逻辑都是把第一个数作为key,
		//为了避免改动代码,这里我们直接交换就可以
		Swap(&arr[left], &arr[index]);

		//开始单趟排序
		int begin = left;
		int end = right;
		int pivot = begin;
		int key = arr[begin];

		while (begin < end)
		{
			//end开始找小
			while (begin < end && arr[end] >= key)
			{
				end--;
			}
			arr[pivot] = arr[end];
			pivot = end;
			//begin开始找大
			while (begin < end && arr[begin] <= key)
			{
				begin++;
			}
			arr[pivot] = arr[begin];
			pivot = begin;
		}
		pivot = begin;
		arr[pivot] = key;

		//区间分为[left,pivot-1]pivot[pivot+1,right]
		//利用循环继续分割区间
		//先入右子区间
		if (pivot + 1 < right)
		{
			//说明右子区间不止一个数
			//先入右边边界
			StackPush(&st, right);
			//再入左边边界
			StackPush(&st, pivot+1);
		}

		//再入左子区间
		if (left < pivot-1)
		{
			//说明左子区间不止一个数
			//先入右边边界
			StackPush(&st, pivot-1);
			//再入左边边界
			StackPush(&st, left);
		}	
	}
	StackDestory(&st);
}

五、时间复杂度 

快速排序的时间复杂度:

最坏情况下,时间复杂度是O(n^2); (逆序)

最优情况下,时间复杂度是O(nlogn);

平均时间复杂度是O(nlogn);

快速排序是时间复杂度:O(logn)

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

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

相关文章

RK3568平台开发系列讲解(工具命令篇)ADB的安装

🚀返回专栏总目录 文章目录 一、ADB介绍二、Windows 下安装 adb 工具沉淀、分享、成长,让自己和他人都能有所收获!😄 一、ADB介绍 adb 全称 Android Debug Bridge,直译过来就是 Android 调试桥,它是一个通用的命令行工具。adb 做为 Android 设备与 PC 端连接的一个桥梁…

软件设计模式系列之十四——代理模式

1 模式的定义 代理模式是一种结构型设计模式&#xff0c;它允许一个对象&#xff08;代理&#xff09;充当另一个对象的接口&#xff0c;以控制对该对象的访问。代理模式通常用于控制对真实对象的访问&#xff0c;以实现一些额外的功能&#xff0c;例如延迟加载、权限控制、日…

ORM模型与表的映射

ORM模型与表的映射 ORM模型 对象关系映射(ObjectRelationship:Mapping)&#xff0c;简称 ORM&#xff0c;是一种可以用 Python 面向对象的方式来操作关系型数据库的技术&#xff0c;具有可以映射到数据库表能力的 Python 类我们称之为 ORM 模型。一个 ORM 模型与数据库中一个…

基于SpringBoot的的师生健康信息管理系统

目录 前言 一、技术栈 二、系统功能介绍 管理员功能模块 学生功能模块 教师功能模块 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着移动应用技术的发展&#xff0c;越来越多的用户借助于移动手机、电脑完成生活中的事务&#xff0c;许多的传统行业也…

华为NFC设置教程(门禁卡/公交卡/校园卡等)

今天把华为NFC设置教程分享给大家 出门带门禁卡、校园卡、银行卡、身份证……东西又多&#xff0c;携带又麻烦&#xff0c;还容易搞丢&#xff0c;有没有一种方法可以把它们都装下&#xff1f;有&#xff01;只要一部手机&#xff0c;出门不带卡包&#xff0c;各种证件&#x…

SpringCloud Alibaba 入门到精通 - Sentinel

SpringCloud Alibaba 入门到精通 - Sentinel 一、基础结构搭建1.父工程创建2.子工程创建 二、Sentinel的整合SpringCloud1.微服务可能存在的问题2.SpringCloud集成Sentinel搭建Dashboard3 SpringCloud 整合Sentinel 三、服务降级1 服务降级-Sentinel2 Sentinel 整合 OpenFeign3…

算法之斐波那契数列

10.1 斐波那契数列 题目链接 牛客网 题目描述 求斐波那契数列的第 n 项&#xff0c;n < 39。 解题思路 如果使用递归求解&#xff0c;会重复计算一些子问题。例如&#xff0c;计算 f(4) 需要计算 f(3) 和 f(2)&#xff0c;计算 f(3) 需要计算 f(2) 和 f(1)&#xff0c;…

好物周刊#9:AI 学习必备资料

村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. PicX 一款基于 GitHub API 开发的图床工具&#xff0c;提供图片上传托管、生成图片链接和常用图片工具箱服务。只需要申请一个 Github Token&am…

[Python]Open CV 基础知识学习

Open CV 在图像处理与目标检测中应用比较广&#xff0c;因此来学习一下基础知识。 Open CV 的安装: 在anaconda search中找opencv&#xff0c; 然后anaconda会自动安装opencv和关联的库 Open CV 基本操作: 注意python 中导入opencv的包名是cv2 读取图片: imread有两个参数…

力扣 -- 215. 数组中的第K个最大元素

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int QuickSelectSort(vector<int>& nums,int begin,int end,int k){//随机选keyint keynums[beginrand()%(end-begin1)];//left在左端点前一个位置int leftbegin-1;//right在右端点后一个位置in…

LDA算法并提取这份数据集中各个文档的主题

任务描述&#xff1a;现有一份“网易新闻语料”数据集&#xff0c;请尝试用Python或Java实现LDA算法并提取这份数据集中各个文档的主题&#xff0c;并显示出来&#xff08;可参考下图的输出结果&#xff0c;可网上拷贝代码&#xff0c;但需对算法以及代码有一定的基本了解&…

《从零开始的Java世界》01基本程序设计

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

Python 逢七拍手小游戏1.0

"""逢七拍手游戏介绍&#xff1a;逢七拍手游戏的规则是&#xff1a;从1开始顺序数数&#xff0c;数到有7&#xff0c;或者是7的倍数时&#xff0c;就拍一手。例如&#xff1a;7、14、17......70......知识点&#xff1a;1、循环语句for2、嵌套条件语句if/elif/e…

【前段基础入门之】=>HTML结构进阶【列表;表格;表单】

前言 在上一章节中&#xff0c;我们学习了讲述了 html 中的一些常用排版标签&#xff0c;以及一些文本标签和超链接等相关知识。本章节将重点给大家带来 HTML 中【列表&#xff0c;表格&#xff0c;表单】的使用讲解。 目录 列表有序列表无序列表自定义列表 表格表格的基本结构…

在项目中使用Service Worker 与 PWA

小册 这是我整理的学习资料&#xff0c;非常系统和完善&#xff0c;欢迎一起学习 现代JavaScript高级小册 深入浅出Dart 现代TypeScript高级小册 linwu的算法笔记&#x1f4d2; 引言 最近next项目有使用pwa技术&#xff0c;使用起来也不复杂&#xff0c;目前浏览器的兼容…

【hadoop3.x】一 搭建集群调优

一、基础环境安装 https://blog.csdn.net/fen_dou_shao_nian/article/details/120945221 二、hadoop运行环境搭建 2.1 模板虚拟机环境准备 0&#xff09;安装模板虚拟机&#xff0c;IP 地址 192.168.10.100、主机名称 hadoop100、内存 4G、硬盘 50G 1&#xff09;hadoop100…

MongoDB基础详解

一、MongoDB概述 MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统&#xff0c;由 C 编写的。MongoDB 提供了 面向文档 的存储方式&#xff0c;操作起来比较简单和容易&#xff0c;支持“无模式”的数据建模&#xff0c;可以存储比较复杂的数据类型&#xff0c;是一…

MySQL的general日志

起因是一个客户的MySQL数据的data目录下会生成一个 名为ubuntu.log的日志且文件占用空间极大&#xff0c;平均每2周500G&#xff0c;所以才产生了处理该问题的想法。后来查询了相关资料其实就是一般日志开启了&#xff0c;但是又未指定其路径及名称所以就会一头雾水。 MySQL的g…

关于华为笔记本谷歌浏览器(chrome)默认启动页为360搜索的解决方法

一、简介 好久没打开谷歌浏览器&#xff0c;近期发现谷歌浏览器的启动是360搜素&#xff0c;现象比较常见&#xff0c;一开始以为是被各种病毒或插件拦截&#xff0c;经过很多尝试方法&#xff1a; 修改浏览器启动页设置【无效】修改快捷方式的“目标位置”属性【无效】修改&…

MySQL:码包安装mysql(5.6.51)

我们去下载mysql源码安装包和cmake包 安装mysql的时候需要使用到cmake&#xff0c;其次我们还要使用yum安装一些环境 yum -y install ncurses-devel gcc-c gcc openssl* perl* 下载mysql源码包 http://mirrors.sohu.com/mysql/MySQL-5.6/mysql-5.6.51.tar.gzhttp://mirrors.s…