快排三种递归及其优化,非递归和三路划分

news2024/11/20 20:33:25

个人主页:Lei宝啊 

愿所有美好如期而遇


目录

快排简介:

快排的三种递归实现:

Hoare:

挖坑:

双指针:

小区间优化:

三数取中优化:

快排非递归实现:

快排的三路划分实现:


快排简介:

快速排序,参见: qsort详解及其模拟实现


快排的三种递归实现:

Hoare:

此法乃Hoare大佬所创,我等一个不注意便就掉入陷阱,大坑于代码处自有注释,诸君慢品:

//Hoare版本  right传数组下标
void QuickSort_Binary(int* arr, int left, int right)
{
	if (left >= right)
		return;

	//选定一个数keyi,最好是不大也不小,上面加个三数取中
	int keyi = left;

	//快排开始的区间,都是闭区间
	int begin = left;
	int end = right;

	while (begin < end)
	{
		//一坑在=,若无此,循环不止
		//二坑在begin < end,若无此,有越界之忧,end或减减不止
		//三坑在要从右先行,以此保证begin与end相遇时
		//					二者所指处值小于keyi所指值
		while (begin < end && arr[end] >= arr[keyi])
		{
			end--;
		}

		while (begin < end && arr[begin] <= arr[keyi])
		{
			begin++;
		}

		Swap(&arr[end], &arr[begin]);
	}

	Swap(&arr[begin], &arr[keyi]);
	
	QuickSort_Binary(arr, left, begin - 1);
	QuickSort_Binary(arr, begin + 1, right);

}

挖坑:

此法则无关左右矣

//挖坑法  传数组下标
void QuickSort_Binary(int* arr, int left, int right)
{
	if (left >= right)
		return;

	int hole = left;
	int temp = arr[left];

	//定义这两变量主要是为了区分后面递归时的区间
	int begin = left;
	int end = right;

	while(begin < end)
	{
		while(begin < end && arr[end] >= temp)
		{
			end--;
		}
		//交换爽啊,赋值的话循环结束后,还要把temp的值赋给hole位置
		Swap(&arr[hole], &arr[end]);
		hole = end;

		while (begin < end && arr[begin] <= temp)
		{
			begin++;
		}
		Swap(&arr[hole], &arr[begin]);
		hole = begin;
	}

	QuickSort_Binary(arr, left, begin - 1);
	QuickSort_Binary(arr, begin + 1, right);
}

双指针:

//双指针法 传数组下标
void QuickSort_Binary(int* arr, int left, int right)
{
	if (left >= right)
		return;

	int temp = arr[left];

	int prev = left;
	int cur = left;

	while (cur <= right)
	{
		
		while (arr[cur] < temp  && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}

		cur++;
	}
	//想法大致都是keyi位置的值不动,从下一个位置开始,最后交换keyi位置和停止位置
	//停止位置的值一定比keyi位置的值要小
	Swap(&arr[left], &arr[prev]);

	QuickSort_Binary(arr, left, prev - 1);
	QuickSort_Binary(arr, prev + 1, right);

}

小区间优化:

我们可以发现的是,递归像一座金字塔,越是到下面,递归次数越多,而我们通过计算得知,一颗满二叉树节点数为2^n-1,最后一层节点数为2^(n-1),也就是说,最后三层节点数占到总数的近87.5%,

也就是说,剩余的节点小于15就不要递归了,可以使用插入排序,这个还是比较好的,插入排序参见:插入排序与希尔排序

以Hoare大佬的排序为例:

//Hoare版本  right传数组下标
void QuickSort_Binary(int* arr, int left, int right)
{
	if (left >= right)
		return;

    if(right-left+1 >= 15)
    {
        //选定一个数keyi,最好是不大也不小,上面加个三数取中
	    int keyi = left;

	    //快排开始的区间,都是闭区间
	    int begin = left;
	    int end = right;

	    while (begin < end)
	    {
	    	//一坑在=,若无此,循环不止
	    	//二坑在begin < end,若无此,有越界之忧,end或减减不止
	    	//三坑在要从右先行,以此保证begin与end相遇时
	    	//					二者所指处值小于keyi所指值
	    	while (begin < end && arr[end] >= arr[keyi])
		    {
		    	end--;
		    }

		    while (begin < end && arr[begin] <= arr[keyi])
		    {
		    	begin++;
		    }

		    Swap(&arr[end], &arr[begin]);
	    }

	    Swap(&arr[begin], &arr[keyi]);
	    
	    QuickSort_Binary(arr, left, begin - 1);
	    QuickSort_Binary(arr, begin + 1, right);

	}
    else
    {
        InsertSort(arr,right-left+1);
    }
}

三数取中优化:

再一个,如果说一个序列已然有序,我们再使用快排就很难受,此时时间复杂度直达O(N^2),所以如果我们加上三数取中,就不会出现最坏情况,但是力扣老贼针对快排,快排的三数取中我们仍要修改,改为随机数取中。

int GetMidNum(int* a, int left, int right)
{
	int mid = left + (rand() % (right - left));

	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[left] > a[right])
		{
			return left;
		}
		else if (a[mid] > a[right])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}

这样我们返回这个中间值坐标后,这样做:

int mid = GetMidNum(arr, left, right);
Swap(&arr[left], &arr[mid]);

 

快排非递归实现:

快排掌握递归并不够,虽然说他的空间复杂度不高,尽管我们有了上述优化,但是仍然难以保证他不会爆栈,所以掌握非递归还是很有必要的。

快速排序的非递归类似于二叉树的前序遍历,我们在这里需要借助于栈,当然队列也可,但是这样的话就类似于二叉树的层序遍历了。

栈和队列参考:栈和队列的实现

二叉树的前序遍历参考:二叉树的几个递归问题

二叉树的层序参考:二叉树的层序遍历及判断完全二叉树

void QuickSortNonR(int* a, int left, int right)
{

	Stack stack;
	Init(&stack);

	Push(&stack, right - 1);
	Push(&stack, left);

	while (!Empty(&stack))
	{
		int begin = GetTop(&stack);
		Pop(&stack);
		int end = GetTop(&stack);
		Pop(&stack);

		int mid = SortWay_two(a, begin, end);

		if (mid + 1 < end)
		{
			Push(&stack, end);
			Push(&stack, mid + 1);			
		}

		if (begin < mid)
		{
			Push(&stack, mid);
			Push(&stack, begin);		
		}

	}
}

这里注意栈的特性是先进后出。 

快排的三路划分实现:

在力扣的针对下,有大佬推出了这个算法,使得快排终于能够通过。

我们的快排是大等于或小等于,而三路划分是小的在左,相等于keyi的在中间,大的在右,使得我们直接递归相等数的左边和右边就可。

//快排三路划分
void QuickSort_ThrDiv(int *arr,int left,int right)
{
	if (left >= right)
		return;

	srand((unsigned int)time(NULL));
	int mid = GetMidNum(arr, left, right);
	Swap(&arr[left], &arr[mid]);

	int begin = left;
	int end = right;
	int keyi = arr[left];

	int cur = left + 1;	
	while (cur <= right)
	{
		if (arr[cur] < keyi)
		{
			Swap(&arr[cur], &arr[left]);
			left++;
			cur++;
		}
		else if (arr[cur] > keyi)
		{
			Swap(&arr[cur], &arr[right]);
			right--;
		}
		else
		{
			cur++;
		}
	}

	QuickSort_ThrDiv(arr, begin, left - 1);
	QuickSort_ThrDiv(arr, right + 1, end);

}

今晚的风,吹得好浪漫~

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

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

相关文章

嵌入式Linux应用开发-驱动大全-第一章同步与互斥④

嵌入式Linux应用开发-驱动大全-第一章同步与互斥④ 第一章 同步与互斥④1.5 自旋锁spinlock的实现1.5.1 自旋锁的内核结构体1.5.2 spinlock在UP系统中的实现1.5.3 spinlock在SMP系统中的实现 1.6 信号量semaphore的实现1.6.1 semaphore的内核结构体1.6.2 down函数的实现1.6.3 u…

Android etc1tool之png图片转换pkm 和 zipalign简介

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、etc1tool2.1、用法 三、zipalign3.1 使用 四…

day49 ARM

.text .globl _start _start:mov r1,#1mov r2,#0mov r3,#100 fun2:cmp r2,r3bcc fun1 stop:b stop fun1: ADD r2,r2,r1add r4,r4,r2b fun2 .end

华为云云耀云服务器L实例评测|部署个人在线电子书库 calibre

华为云云耀云服务器L实例评测&#xff5c;部署个人在线电子书库 calibre 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 应用场景1.3 支持镜像 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 calibre3.1 calibre 介绍3.2 Docker 环境搭建3.3 c…

javaScript-事件循环-微任务-宏任务

为什么引入事件循环?如何理解&#xff1f; js是单线程的语言&#xff0c;需要把异步任务交给宿主浏览器执行&#xff0c;仿制js引擎堵塞 以下面的代码为例 异步的代码交给浏览器之后 进入队列中等待被调用&#xff1a; <!DOCTYPE html> <html lang"en"…

嵌入式Linux应用开发-驱动大全-第一章同步与互斥③

嵌入式Linux应用开发-驱动大全-第一章同步与互斥③ 第一章 同步与互斥③1.4 Linux锁的介绍与使用1.4.1 锁的类型1.4.1.1 自旋锁1.4.1.2 睡眠锁 1.4.2 锁的内核函数1.4.2.1 自旋锁1.4.2.2 信号量1.4.2.3 互斥量1.4.2.4 semaphore和 mutex的区别 1.4.3 何时用何种锁1.4.4 内核抢占…

CharacterEncodingFilter的用法

CharacterEncoding是SpringMVC提供的一个一个过滤器,用于设置请求和响应的字符编码,解决乱码问题,他本身是一个过滤器 那么在SpringBoot中,CharacterEncoding就有一个很好的秒用 setEncoding("UTF-8")设置编码 setForceEncoding(true) 设置请求和响应编码 还需要在配…

树的存储结构以及树,二叉树,森林之间的转换

目录 1.双亲表示法 2.孩子链表 3.孩子兄弟表示法 4.树与二叉树的转换 &#xff08;1&#xff09;树转换为二叉树 &#xff08;2&#xff09;二叉树转换成树 5.二叉树与森林的转化 &#xff08;1&#xff09;森林转换为二叉树 以下树为例 1.双亲表示法 双亲表示法定义了…

javaee之通用mapper

通用mapper可以帮我们写sql语句 我们需要引入依赖是 通用mapper的核心依赖 它本身就依赖一个jpa的依赖&#xff0c;通用mapper的整体依赖就包含了通用mapper的核心依赖 下面说一下通用mapper里面的常见注解 KeySql的用法 tk.mybatis.mapper.common.Mapper 这个是通用Mapper的一…

c#设计模式-行为型模式 之 模板方法模式

&#x1f680;简介 模板方法模式定义了一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下&#xff0c;重新定义算法中的某些步骤。通常用于应对在开发中设计一个系统时知道了算法所需的关键步骤&#xff0c;而且确定…

用 Pycharm 远程连接 Linux 服务器——超详细

用 Pycharm 远程连接 Linux 服务器——超详细 一、介绍二、要求三、服务器配置四、Pycharm远程连接Linux服务器 实战 一、介绍 本人是做NLP的&#xff0c;pycharm写的项目&#xff0c;数据集很大&#xff0c;在自己电脑上运行很慢&#xff0c;但是放到服务器上跑就很快。下面详…

FileZila 实现wind10与Linux系统文件互传

【FileZila】实现windows与Linux系统文件互传

哨兵(Sentinel-1、2)数据下载

哨兵&#xff08;Sentinel-1、2&#xff09;数据下载 一、登陆欧空局网站 二、检索 先下载2号为光学数据 分为S2A和S2B&#xff0c;产品种类有1C和2A&#xff0c;区别就是2A是做好大气校正的影像&#xff0c;当然数量也会少一些&#xff0c;云量检索条件中记得要按格式&#x…

Covert Communication隐蔽通信论文复现

文章目录 前言Covert Communications: A Comprehensive Surveyabstract简介隐蔽通信的概念和机制隐蔽通信的简要历史经典的Alice-Bob-Willie Model与其他安全技术的区别 一、Limits of Reliable Communication with Low Probability of Detection on AWGN Channels摘要introduc…

STM32复习笔记(三):串口

目录 Preface&#xff1a; &#xff08;一&#xff09;CUBEMX配置串口 &#xff08;二&#xff09;轮询方式 &#xff08;三&#xff09;中断 DMA Preface&#xff1a; 串口通信协议简单&#xff0c;因此被广泛应用&#xff1b;串口有UART&#xff08;Universal Asynchron…

谷歌注册手机号码无法验证

1. 打开设置,在语言中点击添加语言搜索English并添加 2. 点击添加后把首选语言换成英语 3. 然后重启浏览器&#xff0c;这时候浏览器就是英文了&#xff0c;最后打开注册页面就能接收短信了

微服务技术栈-认识微服务和第一个微服务Demo

文章目录 前言一、认识微服务二、微服务技术栈三、Eureka注册中心四、微服务DEMO1、搭建eureka-server2、服务注册和服务发现 总结 前言 随着业务的不断复杂&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。 本章就从微服…

vuejs中缓存组件状态-keepAlive

前言 在 vuejs中&#xff0c;我们经常需要缓存一些组件的状态&#xff0c;比如用户登录后&#xff0c;切换到其他页面&#xff0c;再切换回来&#xff0c;需要保留之前的登录状态&#xff0c;而不是重新登录。 或者在切换不同组件的时候&#xff0c;需要保留之前的组件状态&…

[Linux]线程同步

[Linux]线程同步 文章目录 [Linux]线程同步线程同步线程饥饿问题概念 线程同步控制--条件变量pthread_cond_init函数pthread_cond_destroy函数pthread_cond_wait函数pthread_cond_signal函数pthread_cond_broadcast函数条件变量相关函数的使用示例生产者消费者模型基于Blocking…

基于SpringBoot的体育馆场地赛事预约管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…