算法:快速排序及优化
前言
如果想深入了解的话建议先看看 算法概述
为了方便用来测试的数组一成不变,我们可以来一个随机数组
const arr = []
const arrLength = 11 //可以随机长度 Math.floor(Math.random() * 30) + 1
for (let i = 0; i < arrLength; i++) {arr.push(Math.floor(Math.random() * 99) + 1)
}
console.log(arr, 'arr')
算法描述
来到快排了,面试中最常见面的排序之一,这个排序比前面的要复杂些,先简单说一次思路
快排主要是使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数组中取出一个元素的值当基准(分界值)
- 把数组中比基准值小的放左边,比基准值大的放右边,这样形成左边一个数组,右边一个数组
- 接着,我们按照上面的方式继续分治左右的这两个数组,直到左右两边的元素没法再分
代码实现
function quickSort(arr) {if (arr.length < 2) return arrconst pivot = arr[0]let left = [], mid =[pivot], right = []for (let i = 1; i < arr.length; i++) {if (arr[i] < pivot) {left.push(arr[i])} else if (arr[i] === pivot) {//稳定性是稳定的mid.push(arr[i])} else {right.push(arr[i])}}return [...quickSort(left), ...mid, ...quickSort(right)]
}
上面的代码很简单,但是深入思考一下会发现,当样本数组元素比较多的时候,会创建出过多的数组,占用堆栈,以至于报 Maximum call stack size exceeded
,这是典型的空间换时间,接下来我们来思考如何优化
优化思考
- 上面的代码中,每次都要创建新的左边数组和右边数组,其实我们可以在原有的数组上进行交换位置以达到按照基准进行分割,这样就不会过多占用堆栈
- 从算法描述的流程中,第三步其实就是使用递归持续执行第一、二步,这一步是无法优化的,而
取基准
和分治数组
就是可以优化的点了
根据上面的思考,我们可以先把第三部的代码优先实现
function quickSort(arr, start = 0, end = arr.length - 1) {//start~end是此次要分治的区间let pivotIndex;//基准的下标if (start < end) {//这说明分治的元素起码有两个,还需要继续分治setPivotToLeft(arr, start, end)//setPivotToLeft函数实现从arr的start~end中取一个基准,并与start位置互换pivotIndex = partition(arr, start, end);//partition函数实现分治并返回基准的下标quickSort(arr, start, pivotIndex - 1);quickSort(arr, pivotIndex + 1, end);}return arr
}
下面我们来实现函数 partition
,也就是第二步的分治数组
,看看有哪些方案:
动图演示(分治方案一)
上面的图是第一轮排序,简单分析一下:
- 定第一项的值39为基准,并空出位置(为了更形象)
- 先从右边开始比较大小,不小于39的略过,比较从右往左的下一个,小于39的时候,则把值赋予空位,并记录下此时的位置为right
- 接着从左边开始比较,不大于39的略过,比较从左往右的下一个,大于39的时候,则把值赋予空位,并记录下此时的位置为left
- 再接着从右边开始上面的操作,直到left和right相遇(即left不小于right了),到此,把数组分成了以基准(39)为分界线的两个数组了
- 然后我们再进行新一轮的基准定义,重复以上操作,直到分无可分
代码实现(分治方案一)
//分治
function partition(arr, start, end) {let left = start, right = end, pivot = arr[left];while (left < right) { //过程4:left与right相遇则结束此次递归循环,此时left===right//left++和right--是参与完逻辑运算后才自增,不太熟悉的同学可以去看看left++和++left的区别while (left < right && arr[right] > pivot) right--if (left < right) arr[left++] = arr[right] //这里用i++,被换过来的必然比x小,赋值后直接让i自加,不用再比较,可以提高效率,下同while (left < right && arr[left] < pivot) left++if (left < right) arr[right--] = arr[left]}arr[left] = pivotreturn left
}
动图演示(分治方案二)
以上面的图是第一、二轮排序,简单分析一下:
- 定第一项为基准(3),从第二项从左往右开始比较大小
- 因为从第二项开始,所以记录下index为2,index代表可替换的位置
- 不小于基准(3)的略过,小于基准(3)的则和第index项互换位置,并且index加一,代表可替换位置往右移了一位
- 直到第一轮结束了,index为3,交换第二(index-1)项与第一项的位置,此时以基准(3)把数组分为了左边的区
[2]
,和右边的区[44,38,5,47,15,36,26,27,46,4,19,50,48]
- 然后我们再进行新一轮的基准定义,重复以上操作,直到分无可分
代码实现(分治方案二)
function partition(arr, start ,end) {var pivot = arr[start],index = start + 1;for (var i = index; i <= end; i++) {if (arr[i] < pivot) {if(i!==index){var temp = arr[i];arr[i] = arr[index];arr[index] = temp;}index++;} }arr[start] = arr[index - 1]arr[index - 1] = pivotreturn index-1;
}
到此我们把两种优化的分治方法给实现了,接下来我们可以考虑一下基准值如何选取更加合理
一般而言是选取固定位置,如取第一位为基准,但是当序列已经有序时,此时每轮分治后,总有一边是只有一个元素,也就是快排会沦为冒泡,时间复杂度为O(n²)
。而且,数据是有序或者部分有序是相当常见的,因此,我们得更加合理的选取基准值
下面我们来实现方法 setPivotToLeft
,目的是选取基准值并放到分治区间的开始位置
基准值选取方案一:
从区间中随机选取一个值当基准值
代码实现(随机选取基准值)
//调整基准
function setPivotToStart(arr, start, end) {const diff = end - startif (diff > 2) {const pivotIndex = Math.floor(Math.random() * diff) + startconsole.log('pivotIndex',pivotIndex,arr[pivotIndex])var temp = arr[start];arr[start] = arr[pivotIndex];arr[pivotIndex] = temp;}
}
基准值选取方案二:三数取中
从区间中选取头尾中三个元素对比后,选择中间值当基准
代码实现(三数取中选取基准值)
//调整基准
function setPivotToStart(arr, start, end) {const diff = end - startif (diff > 2) {let mid = Math.round(diff/2) + startconsole.log(arr[start],arr[mid],arr[end])if(arr[mid]>arr[end]) {var temp = arr[end];arr[end] = arr[mid];arr[mid] = temp;}if(arr[start]>arr[end]) {var temp = arr[end];arr[end] = arr[start];arr[start] = temp;}if(arr[mid]>arr[start]) {//把中间值放在start位置var temp = arr[mid];arr[mid] = arr[start];arr[start] = temp;}}
}
三数取中法选取的基准减少了大约14%的对比次数
复杂度
最坏时间复杂度:O(n²)
最好时间复杂度:O(nlogn)
平均时间复杂度:O(nlogn)
空间复杂度:O(nlogn)
稳定性:不稳定
算法:快速排序及优化相关推荐
- 排序算法 | 快速排序,算法的图解、实现、复杂度和稳定性分析与优化
今天讲解一下快速排序算法的原理以及实现.复杂度和稳定性分析与优化 目录 1 快速排序的原理 2 快速排序代码实现 3 复杂度和稳定性分析.优化 4 习题练习 1 快速排序的原理 快速排序是所有内部排序 ...
- java版排序算法简介及冒泡排序以及优化,选择排序,直接插入排序,希尔排序,堆排序,快速排序及其优化前言 2 分类 2 稳定性 3 时间复杂度 4 Java实现版本 5 1、冒泡排序 6 2、选择排序
好吧 ~~csdn太难用了....尼玛...写了半天的也无法弄进去...lz正在找更好地博客,or放在github上打算.. 下边是lz自己的有道云分享,大概内容是 http://note.youda ...
- 【数据结构与算法】排序优化
冒泡.插入.选择 O(n^2) 基于比较 快排.归并 O(nlogn) 基于比较 计数.基数.桶 O(n) 不基于比较 总结:如何实现一个通用的高性能的排序函数? 一.如何选择合适的排序算法? 1.排 ...
- 快速排序深度优化详解
正如它的名字所体现,快速排序是在实践中最快的已知排序算法,平均运行时间为O(NlogN),最坏的运行时间为O(N^2).算法的基本思想很简单,然而想要写出一个高效的快速排序算法并不是那么简单.基准的选 ...
- 数据 + 进化算法 = 数据驱动的进化优化?进化算法 PK 数学优化
数据 + 进化算法 = 数据驱动的进化优化?进化算法 PK 数学优化 https://baijiahao.baidu.com/s?id=1600164518587031730&wfr=spid ...
- 空间索引 - GeoHash算法及其实现优化
转自原文 空间索引 - GeoHash算法及其实现优化 上篇博客中提到了空间索引的用途和多种数据库对空间索引的支持情况,那么在应用层以下,好学的小伙伴应该会考虑空间索引的实现原理了. 目前空间索引的实 ...
- 读《c#与算法--快速排序》随笔
<c#与算法--快速排序>地址: http://www.cnblogs.com/isun/archive/2009/04/25/1443603.html 随手写的测试DEMO,web程序不 ...
- 快速排序及优化(Java实现)
一. 普通快速排序 找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base.再分为两个子数组的排序.如此递归下去. public class Quic ...
- 5.6 matlab曲线拟合案例(股票预测问题、算法的参数优化问题)
1.股票预测问题 已知一只股票在2016年8月每个交易日的收盘价如下表所示,试预测其后面的大体走势. x = [2 3 4 5 8 9 10 11 12 15 16 17 18 19 22 23 24 ...
- 谱聚类算法(Spectral Clustering)优化与扩展
谱聚类算法(Spectral Clustering)优化与扩展 谱聚类(Spectral Clustering, SC)在前面的博文中已经详述,是一种基于图论的聚类方法,简单形象且理论基础充分,在社交 ...
最新文章
- Pycharm 建立工程,包含多个工程目录
- Oracle的介绍及其在安装和使用Oracle过程中可能遇到的困难及其相应的解决措施
- 职称计算机提前考试试卷,职称计算机考试多项选择考试卷模拟考^试题
- eclipse无法创建tomcat7.0的server
- 【液晶模块系列基础视频】4.5.X-GUI图形界面库-进度条等函数简介
- Javascript高级程序设计——函数声明与函数表达式的区别
- 大城市赚钱,小城市买房
- OpenDRIVE:学习文档
- 文本去重:I-Match算法
- linux怎么创建swap分区,linux下创建swap分区
- 【WebDriver】WebDriverWait 用法代码
- 怎么制作区域分布图,怎么做网点分布图
- PHP CLI模式 - 执行代码
- 维特根斯坦思想概述南京大学陈亚军
- matlab_医学CT重建 ART,SART算法
- redis分布式锁--》死锁问题解决策略研究
- element input自定义正则验证
- 蓝桥模拟赛 递增位数 JAVA
- MapReduce计数器--详解
- C语言系列——第三节-函数
热门文章
- zb怎么做渲染图_美术丨教程:使用ZBrush渲染制作女神的衣物和皮肤
- 世界十大美女城市排行榜出炉:荷兰首都居首位
- 激光雷达(LiDAR)技术
- Hive实现将分组取top2的数据进行相减(SQL面试题)
- css3 div从左到右滑入
- 怎么入门网络安全,学这两类证书就够了NISP或CISP
- Windows8简体中文 旗舰版 专业版 64位 32位 全系列 及win8 神key
- [JSOI2019]节日庆典
- ios 图片裁剪框架_iOS图片裁剪器 – RSKImageCropper
- 一周拿到5个面试机会,软件测试简历这么写通过率99%