交换排序——快速排序3 针对LeetCode某OJ的优化

交换排序——快速排序3 针对LeetCode某OJ的优化

  • 快速排序的优化
    • 小区间优化
    • 三数取中
    • 三路划分优化

快速排序的优化

这篇优化围绕这个测试OJ展开。

912. 排序数组 - 力扣(LeetCode)

这个测试OJ在早期用快排还能过。但现在用快排不能过了。
请添加图片描述

因为这个OJ针对快排加了一些样例使得快排不能过这个OJ。针对这一点,下文给出优化方案。

小区间优化

小区间优化针对需要产生递归的排序算法。也就是说归并排序也能用。

我们学习排序是为了能判断所有情况,并根据情况选择合适的排序算法来处理数据。

虽然快速排序性能很优异,但并不是所有情况快排都是最优选。甚至数据量小的情况比如10个数据,快排对这10个数据排序,耗时说不定比插入排序慢。

10个数据组成的数组看成完全二叉树就是4层,递归的话还要多调用3次函数,每次调用函数都要向内存申请空间,加大时间和空间的成本。

若使用插入排序来处理这种数量比较小的数据,则减少了多余的递归调用。能使排序完成时间缩短。

以10个数据的满二叉树为例子,最底层占 50 % 50\% 50%的结点,后层数网上逐级减半,减少3层的递归调用,优化效率 50 % + 25 % + 12.5 % = 87.5 % 50\%+25\%+12.5\%=87.5\% 50%+25%+12.5%=87.5%

我们可以通过chrono库的库函数和类来计算10个元素时两种排序的耗时。参考程序如下(c++程序):

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdlib.h>
#include<stdbool.h>
#include<chrono>
#include<iostream>
using namespace std;typedef int Datatype;//用的是c语言的库函数qsort
int cmp(const void* a, const void* b) {return (*(Datatype*)a) - (*(Datatype*)b);
}//插入排序
void insertSort(Datatype* a, int n) {int i = 0;for (i = 0; i < n - 1; i++) {int end = i;int tmp = a[i + 1];while (end >= 0)if (a[end] > tmp) {a[end + 1] = a[end];--end;}else break;a[end + 1] = tmp;}
}void f() {srand((size_t)time(0));Datatype a[10], b[10];int i = 0;for (i = 0; i < 10; i++) {a[i] = rand() % 1000 + 1;//随机数生成数据b[i] = a[i];}//高精度计时auto begin = std::chrono::high_resolution_clock::now();qsort(a, sizeof(a)/sizeof(a[0]), sizeof(a[0]), cmp);//用快排的库函数对10个数据进行排序auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double> time1 = end - begin;std::cout << time1.count() << endl;auto begin2 = std::chrono::high_resolution_clock::now();insertSort(b, 10);//用插入排序对10个数据进行排序auto end2 = std::chrono::high_resolution_clock::now();std::chrono::duration<double> time2 = end2 - begin2;std::cout << time2.count() << endl;//如果快排用时比插入排序用时长,则输出1,否则输出0cout << (time1.count() - time2.count() > 1e-15) << endl;
}int main() {f();return 0;
}

chrono 库的 high_resolution_clock 可以提供高精度的时间点,通过计算两个时间点的差值来得到代码执行的时间间隔, duration<double> 用于以秒为单位表示时间间隔, count() 函数返回该时间间隔的值。

用大量数据时表现不好,小量数据时表现优异的算法处理这种小规模数据的优化方式,有人称这种优化方式为小区间优化。这种优化的本质是一种锦上添花的行为,无法改变算法本身的弊端。

到这里为冒泡排序惋惜1秒钟,结束冒泡排序时至少还要枚举一遍,所以即使是小区间优化也上不了冒泡排序。

三数取中

三数取中优化可使快排的效率相对于直接用快排排序有序数据快。

三数取中即头、尾、中间取不是最差(大)也不是最好(小)的情况。三数取中优化只是对数组做一个简单的预处理,它并没有改变快排的缺陷,而是规避,使快排在排序过程中尽可能取相对中间接近中位数的值

虽然不如直接取中位数,但直接找中位数更麻烦。要排序找,还要返回下标。

三数取中写法不固定,只要能返回取样的中间值即可。

这里给个参考程序:

//三数取中,为优化快排
//选尽可能靠近中位数的值
int GetMidIndex(int* a, int left, int right) {int mid = (left + right) / 2;if (a[left] < a[mid]) {if (a[mid] < a[right])return mid;else if (a[left] < a[right])return right;elsereturn left;}else { // a[left] > a[mid] if (a[mid] > a[right])return mid;else if (a[left] > a[right])return right;elsereturn left;}
}

三数取中优化可以放在快排的框架做数组的预处理,也可以放在单趟排序开头做预处理。

但即使是这样,上面的OJ912. 排序数组 - 力扣(LeetCode) 依旧针对这种小计俩给了针对样例。

秉持道高一尺,魔高一丈的原则,我们可以尝试改进三数取中:用随机数代替中间的那个数。这个做法其实也没有比原来的三数取中做的多好,更多的是像开盲盒。也就是说依旧有一定几率抽中无法通关的数。

参考程序:

//三数取中,因为leetcode上有一题针对三数取中做出了对策样例,
//所以采取随机数取中。
int GetMidIndexRand(int* a, int left, int right) {srand((size_t)time(0));//随机数的种子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;elsereturn left;}else{// a[left] > a[mid]if (a[mid] > a[right])return mid;else if (a[left] > a[right])return right;elsereturn left;}
}

三路划分优化

三种单趟排序方案都没能很好地处理存在大量重复元素的情况。所以快排无论怎么优化都无法在规定时间内通过有大量重复数据的数据组。

三路划分是快排的众多优化方式中的一种。在数据量不大的情况下,未优化的快排反而比其他排序慢。且有序的情况系更慢。但全是随机的情况快排显著更优。这个OJ就针对快排放了一些特殊的测试样例。

912. 排序数组 - 力扣(LeetCode)

这个测试样例中很多测试样例是针对快排设计的。比如某个测试样例里有个测试数据全部是2。于是就有人改进快排使得快排能应对这些场景。

若数据相等,且快排中的判断符为>=<=,则会一路运行下去产生大量的无效递归。我们之前的三种单趟排序其实都是二路划分。所以有人提出了三路划分的优化方案。

三路划分将要排序的数据分成三路:
请添加图片描述

递归时只需要解决不等于key的部分即可,等于key的不用递归。三路划分可以认为hoare版本单趟排序和前后指针单趟排序的结合。

本质为小的排左边,大的排右边,和key相等的值排中间。

样例展示:
请添加图片描述

根据样例,给出3种情况来判断:

  1. if(a[c]<key) Swap(&a[c],&a[l]), ++l, ++c;
  2. if(a[c]>key) Swap(&a[c],&a[r]), --r;
  3. if(a[c]==key), ++c

参考程序:

//快排的三路划分优化方案
//针对大量重复数据的优化
//这个方案独立于已有的快排框架
void quickSortTRoute(int* a, int begin, int end) {if (begin >= end)return;int left = begin;int right = end;int cur = left + 1;int key = a[left];while (cur <= right) {if (a[cur] < key) {Swap(&a[cur], &a[left]);++left;++cur;}else if (a[cur] > key) {Swap(&a[cur], &a[right]);--right;}else++cur;}至此,数组被划分成3个部分:            c[...,......,...]     l    r[l,r]为同一个数key,[left,l-1]比key小[r+1,right]比key大quickSortTRoute(a, begin, left - 1);quickSortTRoute(a, right + 1, end);
}

即使是三路优化,依旧存在这样的样例无法用快排进行排序:无法准确找到中位数,也无法找到哪个数重复最多,特别是找三数全是最小的或最大的(三数取中的针对设计样例)。

即使是这样,依旧可以设计新的三数取中:用随机数代替中间的那个数。详细见上文的三数取中。

三路划分只是针对复杂情况做的对策,实际效率比快排本体慢,平时用正常的快排就行。

而无论那种预排,当key选中中位数时效率最高,此时相当于二分算法。除了这种极其好运抽到中位数的情况,快排少部分情景用别的排序更优,更多场景快排几乎是最优。弄清排序的意义是极端情况下有多重选择,可以设计出达到预期的排序算法(除了某冒泡排序实在救不回来)。

到这里,给出OJ912. 排序数组 - 力扣(LeetCode) 的快排解法:

//快排的三路划分再优化方案
//因为leetcode上有一题针对三数取中做出了对策样例,
//所以采取随机数取中。
void quickSortTRoute(int* a, int begin, int end)
{if (begin >= end)return;int left = begin;int right = end;int cur = left + 1;//针对leetcode的三数取中对策做的下策:随机数取中int midi = GetMidIndexRand(a, begin, end);Swap(&a[midi], &a[left]);int key = a[left];while (cur <= right) {if (a[cur] < key) {Swap(&a[cur], &a[left]);++left;++cur;}else if (a[cur] > key) {Swap(&a[cur], &a[right]);--right;}else {++cur;}}quickSortTRoute(a, begin, left - 1);quickSortTRoute(a, right + 1, end);
}

其他快排的单趟排序partSort依旧可以用随机数取中开盲盒,但是并不能通过这个OJ。

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

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

相关文章

【Vue笔记】基于vue3 + element-plus + el-dialog封装一个自定义的dialog弹出窗口组件

这篇文章,介绍一下如何使用vue3+element-plus中的el-dialog组件,自己封装一个通用的弹出窗口组件。运行效果如下所示: 目录 1.1、父子组件通信 1.2、自定义VDialog组件(【v-model】模式) 1.2.1、编写VDialog组件代码 1.2.2、使用VDialog组件 1.2.3、运行效果 1.3、自…

【支持向量机(SVM)】:算法原理及核函数

文章目录 1 SVM算法原理1.1 目标函数确定1.2 约束条件优化问题转换1.3 对偶问题转换1.4 确定超平面1.5 计算举例1.6 SVM原理小节 2 SVM核函数2.1 核函数的作用2.2 核函数分类2.3 高斯核函数2.3 高斯核函数API2.4 超参数 γ \gamma γ 1 SVM算法原理 1.1 目标函数确定 SVM思想…

【数据结构】树——链式存储二叉树的基础

写在前面 书接上文&#xff1a;【数据结构】树——顺序存储二叉树 本篇笔记主要讲解链式存储二叉树的主要思想、如何访问每个结点、结点之间的关联、如何递归查找每个结点&#xff0c;为后续更高级的树形结构打下基础。不了解树的小伙伴可以查看上文 文章目录 写在前面 一、链…

Java基于微信小程序+SSM的校园失物招领小程序

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

IDEA 2024.3 版本更新主要功能介绍

IDEA 2024.3 版本提供的新特性 IntelliJ IDEA 2024.3 的主要新特性&#xff1a; AI Assistant 增强 改进的代码补全和建议更智能的代码分析和重构建议Java 支持改进 支持 Java 21 的所有新特性改进的模式匹配和记录模式支持更好的虚拟线程调试体验开发工具改进 更新的 UI/UX 设…

Unity类银河战士恶魔城学习总结(P132 Merge skill tree with skill Manager 把技能树和冲刺技能相组合)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了解锁技能后才可以使用技能&#xff0c;先完成了冲刺技能的锁定解锁 Dash_Skill.cs using System.Collections; using System…

linux 中mysql查看慢日志

1、到mysql容器&#xff0c;先登录到数据库&#xff0c;查看是否开启 mysql -h 127.0.0.1 -uroot -p SHOW VARIABLES LIKE slow_query_log; 2、如果没有开启&#xff0c;需要先开启 set global slow_query_log ON; 3、查看慢日志文件 SHOW VARIABLES LIKE slow_query_log…

奶龙IP联名异军突起:如何携手品牌营销共创双赢?

在快节奏的互联网消费时代&#xff0c;年轻消费群体对产品和品牌的要求越来越挑剔。因此在品牌年轻化的当下&#xff0c;一方面需要品牌自身形象也要不断追求时代感&#xff0c;另一方面品牌也需要不断引领消费者需求&#xff0c;提升竞争力和产品力。 奶龙作为近年来异军突起…

项目中排查bug的思路案例

bug描述&#xff1a;调用了删除的接口&#xff0c;执行成功了&#xff0c;也删掉了选中的数据&#xff0c;但是不执行删除后的处理操作&#xff0c;会报一个“系统未知错误&#xff0c;请反馈给管理员” 解决&#xff1a; 成功删掉了数据&#xff0c;但删除后的操作没有执行&a…

欧瑞博智能家居掀起风潮 助力新加坡智慧国2.0发展

&#xff08;狮城快讯&#xff09;在新加坡智慧国2.0计划的推动下&#xff0c;智能科技日益融入生活&#xff0c;智慧社区建设成为提升生活品质的关键。智能家居品牌ORVIBO凭借创新的AI技术和优质用户体验&#xff0c;迅速成为本地智能家居的领导者&#xff0c;从别墅、公寓到H…

【AI人脸整合包及教程】FaceFusion 3.0.0:AI人脸技术的新高度

一、引言 在当今数字化时代&#xff0c;AI技术不断发展并渗透到各个领域&#xff0c;其中AI人脸技术尤为引人注目。FaceFusion 3.0.0作为这一领域的代表性工具&#xff0c;正引领着新的潮流。 二、FaceFusion 3.0.0的功能特点 高度精确的人脸效果 FaceFusion 3.0.0利用先进的…

OLED透明屏在零售行业有哪些优势

OLED透明屏在零售行业具有诸多优势&#xff0c;这些优势使得它成为零售行业中一种创新且高效的展示工具。以下是对OLED透明屏在零售行业优势的详细分析&#xff1a; 1. 视觉吸引力与沉浸感 高透明度&#xff1a;OLED透明屏能够实现40%以上的透明度&#xff0c;使得屏幕后的物体…

win10 pip 永久镜像

打开文件夹&#xff0c;找到目录 &#xff1a;C:\Users\xxx\AppData\Roaming // xxx是你的用户名 如果看不到AppData文件夹&#xff0c;可以点击上方的查看 &#xff0c;勾选上隐藏的项目即可 然后再Roaming 目录下创建文件夹 pip 在pip文件夹下面创建 pip.ini 文件&#xf…

计算机毕业设计 | SpringBoot+vue城镇保障性住房管理 公租房系统(附源码+论文)

1&#xff0c;绪论 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理城镇保障性住房管理系统的相关信…

软件测试学习笔记丨Selenium学习笔记:元素定位与操作

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/22510 本文为霍格沃兹测试开发学社的学习经历分享&#xff0c;写出来分享给大家&#xff0c;希望有志同道合的小伙伴可以一起交流技术&#xff0c;一起进步~ 说明&#xff1a;本篇博客基于sel…

任我行协同CRM普及版 CommonDict/Edit SQL注入漏洞复现

0x01 产品简介 任我行协同CRM普及版是由成都市任我行信息技术有限公司开发的一款客户关系管理软件。该软件旨在帮助中小企业简化管理流程,提升客户管理能力,以及优化销售业绩。集成了CRM、OA、HR等多项功能于一体,为企业提供了一个全面的管理平台。该软件通过高度集成的解决…

JVM调优理论

JVM调优 文章目录 JVM调优理论JVM内存结构堆栈方法区&#xff08;逻辑上的划分&#xff0c;不同版本略有区别&#xff09; 类加载过程编译与反编译类加载过程 编译器优化机制字节码如何运行Hotspot的即时编译器分层编译找热点方法Hospot 内置的两类计数器 方法内联逃逸分析 垃圾…

(C语言)数据在内存中的储存

目录 1>.存储的方式 2>.关于用%d来打印char类型数 3>.不同类型能表示的范围 4>.浮点数在内存中的存储 储存方式 E在内存中的存储 E在内存中的取出 1&#xff09;E不全是0和1 2&#xff09;E全为0 3&#xff09;E全为1 整数和浮点数在内存中是以二进制的方…

Tryhackme练习-Wonderland

基本信息 由于tryhackme是在线靶场&#xff0c;所以这里的IP均为对方的内网IP 攻击机器&#xff1a;10.10.242.186 靶机&#xff1a;10.10.173.3 目标&#xff1a;获取2个flagroot权限 具体流程 信息收集 首先我们使用fscan进行端口扫描&#xff0c;fscan -h 10.10.173.…

【设计模式-原型】

**原型模式&#xff08;Prototype Pattern&#xff09;**是一种创建型设计模式&#xff0c;旨在通过复制现有对象的方式来创建新对象&#xff0c;而不是通过实例化类来创建对象。该模式允许对象通过克隆&#xff08;复制&#xff09;来创建新的实例&#xff0c;因此避免了重新创…