数据结构:谈快速排序的多种优化和非递归展开,以及排序思想归纳

文章目录

  • 写在前面
  • 快速排序的基本体系
  • 快速排序的优化
  • 快速排序的非递归实现
  • 排序分类总结
    • 插入排序
    • 选择排序
    • 交换排序
    • 归并排序

写在前面

快速排序作为效率相当高的排序算法,除了对于特殊数据有其一定的局限性,在大多数应用场景中都有它特有的优势和应用,前面文章有对快速排序做总结,但实际上快速排序由于它广泛的应用和特殊的优势,应当值得单独拿来仔细琢磨分析,因此这篇主要对快速排序的各种细节进行打磨和分析,加深印象也能不断提升效率,打开思维举一反三用到更多的场景中

快速排序的基本体系

在进行快速排序的优化前,先进行一些回忆快速排序的方法,有利于后续对快速排序的总结
首先这里先介绍的都是递归形式的快速排序,从大的方向来看,快速排序实现是很简单的,需要借助一个让数据分布在某一值两侧的算法,再在此基础上递归到左边和右边即可,那么基本框架就是下面的这样:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = Partsort(a, begin, end);

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

而上面的算法有三种,分别是hoare算法,挖坑算法,前后指针算法,这三种都能完成快速排序,这里只介绍其中一种前后指针法,掌握一种即可让快速排序跑起来,完成我们对它的目标

下面进行前后指针法

前后指针法可以通俗的认为,cur在前面探路,只要遇到一个比key小的数字就把这个数字扔到后面,最后再把key往前放

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

快速排序的优化

下面我用leetcode中的一道题进行分析快速排序的多种优化

leetcode – 数组排序

首先,我们把上面的代码放上去,显然leetcode不会允许你这么简单的就通过测试

在这里插入图片描述
这里观察发现,过不去的原因是快速排序对于keyi的选择是很重要的,如果keyi恰好选到了最小的一个值,那么时间复杂度一下变成O(N^2),回到上面的测试样例看,如果测试样例让keyi每次都选了最小的,时间复杂度回到了冒泡排序一个级别的效率,很显然是过不了的,那么我们的第一步优化就是让keyi的选择发生一些改变

int GetMid(int* a, int left, int right)
{
	int midi = (left + right) / 2;
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else  // a[left] > a[midi]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

int Partsort(int* a, int left, int right)
{
	int midi = GetMid(a, left, right);
	Swap(&a[midi], &a[left]);
	int cur = left + 1;
	int prev = left;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	Swap(&a[prev], &a[keyi]);

	return prev;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = Partsort(a, begin, end);

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

嗯,看起来这次靠谱了不少,这样的话对key的选择就相对随机了一点,再跑一次试试看

在这里插入图片描述
但依旧被卡住了,那这如何处理?究其原因还是因为这是题目的数据对快速排序进行了特殊照顾,破局的方法也是有的,利用rand随机值处理一下

void Swap(int* p, int* c)
{
	int tmp = *p;
	*p = *c;
	*c = tmp;
}

int GetMid(int* a, int left, int right)
{
	//int midi = (left + right) / 2;
	int midi = left+(rand() % (right-left));
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else  // a[left] > a[midi]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

int Partsort(int* a, int left, int right)
{
	int midi = GetMid(a, left, right);
	Swap(&a[midi], &a[left]);
	int cur = left + 1;
	int prev = left;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	Swap(&a[prev], &a[keyi]);

	return prev;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = Partsort(a, begin, end);

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

但似乎并不奏效,最后还是会被卡住,这里出现问题的原因很简单,如果全是一个数字,快速排序依旧不占优,这里就引入了第三次优化,叫三路划分

在这里插入图片描述

三路划分:

主要针对的就是这样的情况,解决的原理就是把定义left cur right指针,如果cur指向的内容比key大,就和left进行交换,如果cur的值和key的值相等就继续向后遍历,如果cur的值大于key,就和right进行交换,由于不知道right换过来的值是多少,但可以保证right此时的值一定是大于key的,因此只需要让right–即可

那么代码实现就可以改成这样

int Partsort(int* a, int left, int right)
{
	int cur = left + 1;
	int key = a[left];
	while (cur <= right)
	{
		if (a[cur] < key)
		{
			Swap(&a[cur], &a[left]);
			cur++;
			left++;
		}
		else if (a[cur] > key)
		{
			Swap(&a[cur], &a[right]);
			right--;
		}
		else
		{
			cur++;
		}
	}
	return left;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = Partsort(a, begin, end);

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

这样就能解决问题了

由此可见,快速排序从最初版本到可优化版本之间有很多优化点,也侧面反映出快速排序实际上是一个不算稳定的排序,在许多特定的算法下它并不适合

快速排序的非递归实现

快速排序是利用递归实现的,而凡是递归就有可能爆栈的情况出现,因此这里要准备快速排序的非递归实现方法

非递归实现是借助栈实现的,栈是在堆上malloc实现的,栈区一般在几十Mb左右,而堆区有几G左右的空间,在堆上完成操作是没有问题的

在这里插入图片描述

当left<keyi-1才会入栈,当keyi+1<right才会入栈

随着不断入栈出栈,区间划分越来越小,left最终会等于keyi-1,这样就不会入栈,右边同理,不入栈只出栈,最终栈会为空,当栈为空时,排序完成

后续STL中用栈可以很方便表示

排序分类总结

插入排序

插入排序:有直接插入和希尔排序,其中希尔排序是在直接插入的基础上衍生而来的

先说插入排序原理:从前向后遍历,如果遍历的数字比前面的数字小,就继续和前面的前面的数字比,直到该数字比前面的数字大,那么再让比它大的数字向后挪,它本身插入到这当中即可

再说希尔排序原理:希尔排序是在插入排序的基础上衍生出来的,原理是先进行预排序,再进行插入排序,直接插入排序对于数据差异很大的数据表现并不好,因此希尔排序先把数据进行一个预排序,再进行插入排序效果就会好很多

选择排序

选择排序:原理是每次排序都能选出一个最值,这样经过N次后就可以选出每次的最值,这样就能把数据排好,选择排序分为选择排序和堆排序

先说普通的选择排序:就是直接进行选择,效率较差,时间复杂度也很高

再说堆排序:要利用的是一种特殊的数据结构–堆,通过这个数据结构可以把数组中的元素搭建成堆的模型,再用堆的模型选出最大值,放到最后的位置,再重新调整堆,找出新的最大值,依次类推就可以找到正确的顺序,完成一组数的正确排序

交换排序

交换排序分为冒泡排序和快速排序,其中快速排序是比较好用的排序

先说冒泡排序:效率比普通插入排序高一点,冒泡排序的基本原理是把每次进行两两元素的交换,这样可以把一个最大的元素交换到末尾,再进行第二次交换,直到把所有元素按顺序交换到最后,这样就实现了排序的基本功能

再说快速排序:效率是很可观的,基本原理是利用递归的思想,把数据进行分割,选出一个关键数,让所有数字中比关键数大的排在关键数右边,比关键数小的排在关键数左边,再分别到左边和右边进行分割,直到分割的区域足够小,这样就能保证左边中间右边有序,每个区间都这样做,就能保证最终的区间是有序的,这样就能完成快速排序

归并排序

归并排序也是利用了递归的思想,把数字全部拆成零散的数据,再把这些数据都组合起来即可

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

类加载机制与类加载器

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ Java 源码是如何形成类文件的&#xff0c;类文件又是如何加载到虚拟机的&#xff0c;类加载有哪些机制和原则呢&#xff1f;本文将为大家一一介绍。 1 Java 源码形成类文件…

1.Flink概述

1.1 技术架构 应用框架层: 在API层之上构建的满足特定应用场景的计算框架&#xff0c;总体上分为流计算和批处理两类应用框架。API 层&#xff1a; Flink对外提供能力的接口 &#xff0c;实现了面向流计算的DataStream API和面向批处理的DataSet API。运行时层&#xff1a;Flin…

No Spring环境Mybatis-Plus批量插入并返回主键的两种方式

批量插入,可以把Mybatis-Plus看作是Mybatis加强版;故Mybatis中的相关操作都可以在Mybatis-Plus中使用;在mysql数据库中支持批量插入&#xff0c;所以只要配置useGeneratedKeys和keyProperty就可以批量插入并返回主键了。 下面是批量插入的Dao层接口 一注解方式: 直接撸代码:…

SpringBoot —程序包org.springframework.boot.test.context不存在

一. 遇到问题 &#xff1a;程序包org.springframework.boot.test.context不存在 发生错误的原因是项目中缺少spring-boot-starter-test依赖导致的&#xff0c;解决方案如下: 在项目根目录的pom.xm文件中的<dependencies>节点下增加以下依赖即可&#xff1a; <depen…

网上办理的三网低月租大流量,到底能不能选归属地?

网上办理的三网低月租大流量&#xff0c;到底能不能选归属地&#xff1f; 首先&#xff0c;小编就明确地告诉大家&#xff0c;如果默认的是归属地随机&#xff0c;那么是不可以选择归属地的。 看到这里&#xff0c;可能有人会有疑问&#xff0c;网上的流量卡也是运营商推出的…

编程小白的自学笔记十二(python爬虫入门四Selenium的使用实例二)

系列文章目录 编程小白的自学笔记十一&#xff08;python爬虫入门三Selenium的使用实例详解&#xff09; 编程小白的自学笔记十&#xff08;python爬虫入门二实例代码详解&#xff09; 编程小白的自学笔记九&#xff08;python爬虫入门代码详解&#xff09; 目录 系列文章…

【Python】Web学习笔记_flask(1)——getpost

flask提供的request请求对象可以实现获取url或表单中的字段值 GET请求 从URL中获取name、age两个参数 from flask import Flask,url_for,redirect,requestappFlask(__name__)app.route(/) def index():namerequest.args.get(name)agerequest.args.get(age)messagef姓名:{nam…

Electron逆向调试

复杂程序处理方式&#xff1a; 复杂方式通过 调用窗口 添加命令行参数 启动允许调用&#xff0c;就可以实现调试发布环境的electron程序。 断点调试分析程序的走向&#xff0c;程序基本上会有混淆代码处理&#xff0c; 需要调整代码格式&#xff0c;处理程序。

32.选择器

选择器 html部分 <div class"toggle-container"><input type"checkbox" id"good" class"toggle"><label for"good" class"label"><div class"ball"></div></label&…

Spring学习笔记之spring概述

文章目录 Spring介绍Spring8大模块Spring特点 Spring介绍 Spring是一个轻量级的控制反转和面向切面的容器框架 Spring最初的出现是为了解决EJB臃肿的设计&#xff0c;以及难以测试等问题。 Spring为了简化开发而生&#xff0c;让程序员只需关注核心业务的实现&#xff0c;尽…

flutter 导出iOS问题2

问题1:The Swift pod FirebaseCoreInternal depends upon GoogleUtilities, which does not define modules. To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries) 参考 正如上图报错第三方…

Mybatis中where 1=1 浅析

在一些集成mybatis的工程中经常看到where11 的代码&#xff0c;也有同事问我&#xff0c;这样写有什么用&#xff0c;下面对其进行简单的分析记录一下。 1、场景 看下面这样一段xml中的代码 <select id"queryBook" parameterType"com.platform.entity.Book…

k8s webhook实例,java springboot程序实现 对Pod创建请求添加边车容器 ,模拟istio实现日志文件清理

k8s webhook实例&#xff0c;java springboot程序实现 对Pod创建请求添加边车容器 &#xff0c;模拟istio实现日志文件清理 大纲 背景与原理实现流程开发部署my-docker-demo-sp-user服务模拟业务项目开发部署my-sidecar服务模拟边车程序开发部署服务my-docker-demo-k8s-opera…

Django快速上手

Django简介 Django 框架最初的诞生&#xff0c;主要是用来开发和管理 Lawrence Publishing Group&#xff08;劳伦斯出版集团&#xff09;旗下新闻网站的一款软件&#xff0c;是一款属于 CMS&#xff08;Content Management System&#xff0c;内容管理系统&#xff09;类的软…

面试典中典之线程池的七大参数

文章目录 一、七大元素解释1.corePoolSize&#xff08;核心线程数&#xff09;&#xff1a;2.maximumPoolSize&#xff08;最大线程数&#xff09;&#xff1a;3.keepAliveTime&#xff08;线程空闲时间&#xff09;&#xff1a;4.unit&#xff08;时间单位&#xff09;&#x…

Python 进阶(六):文件读写(I/O)

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 1. 打开文件2. 读取文件2.1 逐行读取文件2.2 读取所有行 3. 写入文件3.1 向文件中写入…

开放麒麟1.0发布一个月后,到底怎么样?另一款操作系统引发热议

具有里程碑意义 7月5日&#xff0c;国产首个开源桌面操作系统“开放麒麟1.0”正式发布。 标志着我国拥有了操作系统组件自主选型、操作系统独立构建的能力&#xff0c;填补了我国在这一领域的空白。 举国欢庆&#xff0c;算的上是里程碑意义了&#xff01; 发布后用着如何&a…

回归预测 | MATLAB实现WOA-ELM鲸鱼算法优化极限学习机多输入单输出回归预测

回归预测 | MATLAB实现WOA-ELM鲸鱼算法优化极限学习机多输入单输出回归预测 目录 回归预测 | MATLAB实现WOA-ELM鲸鱼算法优化极限学习机多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现WOA-ELM鲸鱼算法优化极限学习机多输入回归预测&#…

2.获取DOM元素

获取DOM元素就是利用JS选择页面中的标签元素 2.1 根据CSS选择器来获取DOM元素(重点) 2.1.1选择匹配的第一个元素 语法: document.querySelector( css选择器 )参数: 包含一个或多个有效的CSS选择器 字符串 返回值: CSS选择器匹配的第一个元素&#xff0c;一个HTMLElement对象…

分享一个赛车动画

先看效果&#xff08;动画太大了放不上来&#xff0c;甘心去复制代码运行即可&#xff09;&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>赛车</title><…