当前位置: 首页 > news >正文

快速排序的深入优化探讨

目录

1. 快排性能的关键的分析:

1.1 三路划分算法思想:

1.2 三路划分的快排

1.3 introsort的快排


1. 快排性能的关键的分析:

决定快排性能的关键点是每次单躺排序后,key对数组的分割,如果每次选key基本二分居中,那么快排的递归树就是可均匀的满二叉树,性能最佳。但是实践中虽然不可能每次都是二分居中,但是性能也是可控的。但是如果每次选到最小值/最大值,划分成0个和N-1个子问题时,时间复杂度为O(N^2),数组序列有序就会出现这样的问题,我们前面已经使用三数取中或者随机数选key解决了这个问题,也就是说我们解决了绝大多数的问题,但是现在还是有一些场景没解决(数组中有大量重复数据时)。

//数组中有多个和key相等的值
int a1[] = { 6,1,8,6,6,6,6,4,9 };
int a2[] = { 3,2,3,3,3,3,3,2,3 };
//数组中全是相同的值
int a3[] = { 2,2,2,2,2,2,2,2,2 };

1.1 三路划分算法思想:

我们之前选key值,比key大的在右边,比key小的在左边,那么和key相等的值并没有规定,也就是说可以在左边也可以在右边,那么三路划分就规定了和key相等的值要放在中间。所谓三路就指的是左边,右边,和中间。

当面对有大量跟key相同的值时,三路划分的核心思想有点类似hoare的左右指针和lomuto的前后指针的结合。核心思想就是把数组中的数据分为3段,比key小的值,跟key相等的值,比key大的值,所有叫做三路划分。

  1. key默认取left位置的值。
  2. left指向区间最左边,right指向区间最右边,cur指向left+1位置。
  3. cur遇到比key小的值后跟left位置交换,换到左边,left++,cur++。
  4. cur遇到比key大的值后跟right位置交换,换到右边,right--。
  5. cur遇到跟key相等的值后,cur++。
  6. 直到cur>right结束。

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/sort-an-array/这个OJ,当我们用快排的时候,lomuto的方法过不了这个题目,hoare版本可以过这个题目。堆排序和归并和希尔是可以过的,其他几个O(N^2)也过不了,因为这个题的测试用例中部仅仅有数据很大的数组,也有一些特殊数据的数组,如大量重复数据的数组。堆排序和归并排序和希尔不是很受数据样本的分布和形态的影响,但是快排会,因为快排要选key,每次key都当趟分割都很偏,就会出现效率退化问题。

lomuto的前后指针面对大量重复数据时,效率会退化,hoare版本会好很多,所以hoare版本是可以过这个OJ的,但是OJ还是一个相对局限的测试,就像Leetcode官方刚开始写的答案是lomuto,说明那会lomuto是可以过的,后面加了大量重复数据的测试用例,所以就过不了。那么hoare现在可以过,leetcode哪天增加了一个特殊用例以后就过不了,三路划分也类似,因为他们的思想还是在特殊场景下效率会退化,比如大多数选key都是接近最小值或者最大值,导致划分不均衡,效率退化。

  1. introsort是由David Musser在1997年设计的排序算法,C++ sgi STL sort中用的introspectivesort(内省排序)思想实现的。内省排序可以认为不受数据分布的影响,无论什么原因划分不均匀,导致递归深度太深,它就转换成堆排了,堆排不受数据分布影响。
  2. 其次三路划分针对有大量重复数据时,效率很好,其他场景就一般,但是三路划分思想还是很有价值的,有些快排思想变形体,要用划分去选数,他能保证跟key相等的数都排到中间去,三路划分的价值就体现出来了。

1.2 三路划分的快排

代码:

/*** Note: The returned array must be malloced, assume caller calls free().*/
void Swap(int* x,int* y)
{int z = *x;*x = *y;*y = z;
}
void QuickSort(int* arr,int left,int right)
{if(left >= right) return;int begin = left;int end = right;//随机数选k,如果数据有序的情况下就保证k不是最小的int randi = left + (rand() % (right-left + 1));Swap(&arr[left], &arr[randi]);int key = arr[left];int cur = left+1;while(cur <= right){if(arr[cur] < key){Swap(arr+cur,arr+left);left++;cur++;}else if(arr[cur] > key){Swap(arr+cur,arr+right);right--;}else cur++;}QuickSort(arr,begin,left-1);QuickSort(arr,right+1,end);
}
int* sortArray(int* nums, int numsSize, int* returnSize) {srand(time(NULL));QuickSort(nums,0,numsSize-1);*returnSize = numsSize;return nums;
}

1.3 introsort的快排

intrsort是introspective sort采用了缩写,他的名字其实表达了他的实现思路,他的思路就是进行自我侦测和反省,快排递归深度太深(sgi stl中使用的是深度为2倍排序元素数量的对数值)那就说明在这种数据序列下,选key出现了问题,性能在快速退化,那么就不要再进行快排分割递归了,改换成堆排序进行排序。

void Swap(int* x, int* y){int tmp = *x;*x = *y;*y = tmp;}void AdjustDown(int* a, int n, int parent){int child = parent * 2 + 1;while (child < n){// 选出左右孩⼦中⼤的那⼀个if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
void HeapSort(int* a, int n)
{// 建堆-- 向下调整建堆 -- O(N) for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}// ⾃⼰先实现 -- O(N*logN) int end = n - 1;while (end > 0){Swap(&a[end], &a[0]);AdjustDown(a, end, 0);--end;}}
void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){int end = i-1;int tmp = a[i];// 将tmp插⼊到[0,end]区间中,保持有序while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];--end;}else{break;}}a[end + 1] = tmp;}
}
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{if (left >= right)return;// 数组⻓度⼩于16的⼩数组,换为插⼊排序,简单递归次数if(right - left + 1 < 16){InsertSort(a+left, right-left+1);return;        }// 当深度超过2*logN时改⽤堆排序if(depth > defaultDepth){HeapSort(a+left, right-left+1);return;}//递归层数depth++;int begin = left;int end = right;//随机数选k int randi = left + (rand() % (right-left + 1));Swap(&a[left], &a[randi]);int prev = left;int cur = prev + 1;int keyi = left;//lomuto前后指针 while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;// [begin, keyi-1] keyi [keyi+1, end]IntroSort(a, begin, keyi - 1, depth, defaultDepth);IntroSort(a, keyi+1, end, depth, defaultDepth);
}
void QuickSort(int* a, int left, int right)
{int depth = 0;int logn = 0;int N = right-left+1;for(int i = 1; i < N; i *= 2){logn++;}// introspective sort -- ⾃省排序//这里使用2倍的logn的话比较合适,3倍的logn和1倍的logn也是可以的IntroSort(a, left, right, depth, logn*2);
}
int* sortArray(int* nums, int numsSize, int* returnSize){//设置随机数的种子srand(time(0));QuickSort(nums, 0, numsSize-1);*returnSize = numsSize;return nums;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • RedisCache存入redis的数据key为何name和id的分隔符是两个冒号::
  • 2024年高教社杯全国大学生数学建模竞赛A题思路(2024数学建模国赛A题思路)
  • 【Effective Java】多构造器参数使用构建器 (快速上手)
  • 【HuggingFace Transformers】OpenAIGPTModel源码解析
  • MySQL学习--加强
  • MATLAB算法实战应用案例精讲-【人工智能】数据集市(概念篇)
  • 电子发射与气体导电
  • 【免费分享】25秋招提前批25秋招信息表
  • 《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 09部署OSPF
  • Gitflow基础知识
  • 【Python知识宝库】文件操作:读写文件的最佳实践
  • fetch-event-source 如何通过script全局引入
  • Autosar(Davinci) --- 创建一个S/R类型的port(下)
  • 【数据分享】《中国城市统计年鉴》(1985-2023)全PDF版本 第一次补档
  • linux curl命令介绍以及使用
  • Angular Elements 及其运作原理
  • conda常用的命令
  • css选择器
  • Java超时控制的实现
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • Redux 中间件分析
  • windows-nginx-https-本地配置
  • 基于Android乐音识别(2)
  • 简单数学运算程序(不定期更新)
  • 移动端 h5开发相关内容总结(三)
  • 与 ConTeXt MkIV 官方文档的接驳
  • 《天龙八部3D》Unity技术方案揭秘
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​经​纬​恒​润​二​面​​三​七​互​娱​一​面​​元​象​二​面​
  • #etcd#安装时出错
  • $(selector).each()和$.each()的区别
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (Ruby)Ubuntu12.04安装Rails环境
  • (第61天)多租户架构(CDB/PDB)
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (原)Matlab的svmtrain和svmclassify
  • (转)ORM
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .bat批处理出现中文乱码的情况
  • .NET Core 2.1路线图
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .net dataexcel 脚本公式 函数源码
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .NetCore实践篇:分布式监控Zipkin持久化之殇
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • ??如何把JavaScript脚本中的参数传到java代码段中
  • @property @synthesize @dynamic 及相关属性作用探究
  • @select 怎么写存储过程_你知道select语句和update语句分别是怎么执行的吗?
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [ vulhub漏洞复现篇 ] AppWeb认证绕过漏洞(CVE-2018-8715)
  • [Android]如何调试Native memory crash issue