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

news2024/10/5 22:24:26

文章目录

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

写在前面

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

快速排序的基本体系

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

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次后就可以选出每次的最值,这样就能把数据排好,选择排序分为选择排序和堆排序

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

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

交换排序

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

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

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

归并排序

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

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

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

相关文章

PHP8的数据类型转换-PHP8知识详解

什么是数据类型转换&#xff1f; 答&#xff1a;数据从一个类型转换成另外一个类型&#xff0c;就是数据类型转换。 在PHP8中&#xff0c;变量的类型就是由赋值决定的&#xff0c;也就是说&#xff0c;如果 string 赋值给 $var&#xff0c;然后 $var 的类型就是 string。之后…

Python:给MySQL创建1000张表和创建1张有50个字段的表

1、创建1000张表 import pymysqldbhost "10.1.1.143" dbuser "root" dbpassword "123456" dbname "demo_cg1000" dbport 3306 dbconn pymysql.connect(hostdbhost, userdbuser, passworddbpassword, dbdbname, portdbport)mycu…

前端学习--vue2--2--vue指令基础

写在前面&#xff1a; 前置内容 - vue配置 文章目录 插值表达式v-html条件渲染v-show和v-ifv-ifv-if的扩展标签复用组件 v-show v-on /事件v-bind /&#xff1a;属性v-modelv-for 循环元素v-slotv-prev-cloak vue指令只的是带有v-前缀的特殊标签属性 插值表达式 插值表达式{…

【MATLAB第62期】基于MATLAB的PSO-NN、BBO-NN、前馈神经网络NN回归预测对比

【MATLAB第62期】基于MATLAB的PSO-NN、BBO-NN、前馈神经网络NN回归预测对比 一、数据设置 1、7输入1输出 2、103行样本 3、80个训练样本&#xff0c;23个测试样本 二、效果展示 NN训练集数据的R2为&#xff1a;0.73013 NN测试集数据的R2为&#xff1a;0.23848 NN训练集数据的…

【机器学习】Feature Engineering and Polynomial Regression

Feature Engineering and Polynomial Regression 1. 多项式特征2. 选择特征3. 缩放特征4. 复杂函数附录 首先&#xff0c;导入所需的库&#xff1a; import numpy as np import matplotlib.pyplot as plt from lab_utils_multi import zscore_normalize_features, run_gradien…

qt添加图标

1.添加资源 选择QtWidgetsApp.qrc文件打开 添加图标文件路径 添加图标文件 2.按钮添加图标 图标路径为:/res/res/swicth.jpg &#xff08;1&#xff09;代码设置图标 ui.pushButton_OPen->setIcon(QIcon(":/res/res/swicth.jpg")); &#xff08;2&#xff09;属…

设计模式:生成器模式

这个模式书上讲的比较简单&#xff0c;但是感觉精华应该是讲到了。 引用下其它博客的总结&#xff1a;生成器模式的核心在于分离构建算法和具体的构造实现&#xff0c;从而使得构建算法可以重用。 【设计模式】建造者模式_鼠晓的博客-CSDN博客

27 用linprog、fmincon求 解线性规划问题(matlab程序)

1.简述 ① linprog函数&#xff1a; 求解线性规划问题&#xff0c;求目标函数的最小值&#xff0c; [x,y] linprog(c,A,b,Aeq,beq,lb,ub) 求最大值时&#xff0c;c加上负号&#xff1a;-c ② intlinprog函数&#xff1a; 求解混合整数线性规划问题&#xff0c; [x,y] intl…

AI+低代码:开启普惠人工智能时代的新篇章

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

mac pd安装ubuntu并配置远程连接

背景 一个安静的下午&#xff0c;我又想去折腾点什么了。准备学习一下k8s的&#xff0c;但是没有服务器。把我给折腾的&#xff0c;在抱怨了&#xff1a;为什么M系列芯片的资源怎么这么少。 好在伙伴说&#xff0c;你可以尝试一下ubantu。于是&#xff0c;我只好在我的mac上安…

https协议 和 Charles 进行https抓包原理

1.对称加密 其变成复杂的加密密文发送出去。收信方收到密文后&#xff0c;若想解读原文&#xff0c;则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密&#xff0c;才能使其恢复成可读明文。在对称加密算法中&#xff0c;使用的密钥只有一个&#xff0c;发收信双方都…

C++——继承(1)详解

目录 1.继承的含义 2.继承的定义&#xff1a; 3.继承方式 例子1&#xff1a;基类的访问限定符为public&#xff0c;两个派生类的继承方式分别为public、protected时&#xff1a; 例子2&#xff1a; 基类的访问限定符为protected&#xff0c;两个派生类的继承方式分别为pub…

细讲TCP三次握手四次挥手(三)

TCP/IP 协议族 在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的 TCP/IP 并不一定是单指 TCP 和 IP 这两个具体的协议&#xff0c;而往往是表示互联网所使用的整个 TCP/IP 协议族。 互联网协议套件&#xff08;英语&#xff1a;Internet Pr…

【前端知识】React 基础巩固(四十一)——手动路由跳转、参数传递及路由配置

React 基础巩固(四十一)——手动路由跳转、参数传递及路由配置 一、实现手动跳转路由 利用 useNavigate 封装一个 withRouter&#xff08;hoc/with_router.js&#xff09; import { useNavigate } from "react-router-dom"; // 封装一个高阶组件 function withRou…

iOS--runtime

什么是Runtime runtime是由C和C、汇编实现的一套API&#xff0c;为OC语言加入了面向对象、运行时的功能运行时&#xff08;runtime&#xff09;将数据类型的确定由编译时推迟到了运行时平时编写的OC代码&#xff0c;在程序运行过程中&#xff0c;最终会转换成runtime的C语言代…

操作系统5

设备管理 I/O设备 什么是?--- 将数据Input/Output(输入/输出)计算机的外部设备。 分类: 按使用特性:人机交互类外设、存储设备、网络通信设备; 按传输速度:低速、中速、高速设备; 按信息交换的单位:块设备、字符设备。 1. 块设备和字符设备的区别? 答:块设备…

C#文件操作从入门到精通(2)——查看某个dll中有哪些函数

kernel32.dll中含有ini文件操作使用的函数&#xff0c;我们可以通过VisualStudio自带的dumpbin.exe查看dll所包含的函数&#xff0c;操作步骤如下&#xff1a; 1、找到dumpbin.exe所在的文件夹 我的电脑中安装了VisualStudio2019社区版以及VisualStudio2017Professional&…

《JeecgBoot系列》JeecgBoot(ant-design-vue)实现筛选框:支持下拉搜索+下拉多选+表字典(支持条件查询)功能

JeecgBoot(ant-design-vue)实现筛选框&#xff1a;支持下拉搜索下拉多选表字典(支持条件查询)功能 JSearchMultiSelectTag.vue源文件 一、需求介绍 在使用JeectBoot(ant-design-vue)设计表单时&#xff0c;需要实现下拉搜索下拉多选表字典(支持条件查询)。 但是系统目前有两…

【Java】零基础上手SpringBoot学习日记(day1)

前言 此帖为本人学习Springboot时的笔记&#xff0c;由于是个接触计算机一年左右的新手&#xff0c;也没有网站开发经验&#xff0c;所以有些地方的理解会比较浅显并且可能会出现错误&#xff0c;望大佬们多多包涵和指正。 Web应用开发 在我的理解中&#xff0c;Web应用的开发…

robot framework之第三方测试报告Allure

Allure 简单介绍 Allure是一个美化报告的工具。支持pytest, robot framework和junit等多种测试框架。 windows 安装allure 步骤1&#xff1a;gitlab 下载解压&#xff1a;https://github.com/allure-framework/allure2/releases 步骤2&#xff1a;进入bin目录, 点击allure.bat…