快速排序的JavaScript实现
思想
分治的思想,将原始数组分为较小的数组(但没有像归并排序一样将它们分隔开)。
- 主元选择: 从数组中任意选择一项作为主元,通常为数组的第一项,即arr[i];或数组的中间项, arr[Math.floor((i+j)/2)];
- 划分操作: 创建两个指针,左边一个指向数组的第一项,右边一个指向数组的最后一项。向右移动左指针,直到找到一个不小于比主元的项;向左移动右指针,直到找到一个不大于主元的项。交换它们。然后重复这个过程,直到左指针超过了右指针。这样使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。注意:在这个过程中,主元的位置也可能发生了改变;而主元本身在一次划分操作之后不会在正确的位置,其正确的位置应该在本次划分操作后最终得到的那个分割点上(即sliceIndex上),在下轮操作的右半路操作会立刻把主元换到正确的位置上
- 对子数组重复划分操作: 比主元小的项(即主元左边的部分)组成一个子数组,比主元大的项(即主元右边的部分)组成另一个子数组。对这两个小数组继续执行主元选择和划分操作。直到数组已完全排序。
代码
代码段1:
function quickSort(arr) {
return quick(arr, 0, arr.length-1);
}
function quick(arr, left, right) {
if(arr.length>1) {
const sliceIndex = partition(arr, left, right);
if (left < sliceIndex-1) {
quick(arr, left, sliceIndex-1);
}
if (sliceIndex < right) { //*1
quick(arr, sliceIndex, right);//*2
}
}
}
function partition(arr, left, right) {
let i = left;
let j = right;
//const pivot = arr[Math.floor((i+j)/2)];
const pivot = arr[i];
while(i< j) {
while (arr[i] < pivot) { // *3
//Q:*3,*4:为什么这里必须用<和>, 而不能用 <=或>= ?
//A:*3和*4必须是<和>,都不能包含=的情况——实际验证结果也是如此
//因为如果包含=,那么就永远不处理pivot及值等于pivot的情况
i++;
}
while (arr[j] > pivot) { // *4
j--;
}
if(i<j) { // *5
//思考:为什么这里必须是 <= 而不能用 < ?
//A:它其实是为了下面一段的i++,j--。如果分开两段写,那么这里应该是i<j,而下面一段应该是i<=j
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
if(i<=j) {//*6
//这里条件一定要是i<=j。
//如果条件仅仅是i<j,那么当i==j的时候就永远也进入不了这个条件,return 的值就是i也是j。那么这一整段partion代码下来,i可能从来没有变过,又原封不动地return了i,即 const sliceIndex = partition(arr, left, right);的这个sliceIndex是和参数left相等的,那么在接下来的quick(arr, sliceIndex, right)就等于之前的quick(arr,left,right),无限循环了。。。
//总之,这个条件的目的就是让i一定要改变一次
i++;
j--;
}
}
return i;
}
思考
1. 其实每次划分操作之后,主元并不在正确的位置上……那么为什么说每次划分操作都把比主元小的值排在了主元之前,而把比主元大的值都排在了主元之后?
主元本身在一次划分操作之后确实不在正确的位置,其正确的位置应该在本次划分操作后最终得到的那个分割点上(即sliceIndex上),在下轮操作的右半路操作会立刻把主元换到正确的位置上。所以其实在本轮划分操作最后,可以把主元交换到正确的位置上,再进行下轮操作,然后下轮操作就可以不管sliceIndex那个位置上了。
这种写法其实是把小于主元的放到左边,大于主元的放到右边,等于主元的可能在左可能在右,而并不是把主元放在了正确的位置。
代码改进成如下这样就好理解了。
代码段2:
function quickSort(arr) {
return quick(arr, 0, arr.length-1);
}
function quick(arr, left, right) {
if (arr.length ===1) {
return;
}
const sliceIndex = partition(arr, left, right);
if(left < sliceIndex-1) {
quick(arr, left, sliceIndex-1);
}
//下次划分不用再考虑主元了
if(sliceIndex + 1 < right) { //*1
quick(arr, sliceIndex + 1, right);//*2
}
}
function partition(arr, left, right) {
let i = left;
let j = right;
const pivot = arr[i];
while(i < j) {
while(arr[i] < pivot) { //*3
i++;
}
while(arr[j] > pivot) {
j--;
}
if(i < j) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
if(i <= j) {
i++;
j--;
}
}
//交换主元到划分位置上
const tempPivotIndex = arr.indexOf(pivot); //*7
arr[tempPivotIndex] = arr[i];//*8
arr[i]=pivot;//*9
return i;//其实也是等于j的
}
比起原代码,这种写法:把1,2做了修改,下次划分操作不再考虑本次的slickIndex(即主元已经排好了);然后新增7,8,*9,就是在本次的划分操作最后,把主元交换到了正确的位置上,这样写,与Es6写法(代码段3)的思路是一致的
工作过程
待画图
性能分析
- 时间复杂度: 最好、平均O(nlogn),最坏O(n^2)
- 空间复杂度: O(logn), 不稳定
- 特点:越有性能却差
延伸:es6的实现
代码段3:
function quickSortEs6(arr) {
if (!arr.length) {
//要处理的临界条件一定是arr为空的情况,因为可能filter过滤后就一项也不剩了
//arr长度为1的条件可以不单独处理,因为长度为1那么[pivot, ...rest]=arr的rest就为[]了
return [];
}
const [pivot, ...rest] = arr;
return [
...quickSortEs6(rest.filter(item => item < pivot)),
pivot,
...quickSortEs6(rest.filter(item => item >= pivot)) //一定要有=不然和pivot相等的其他值会被过滤掉
]
}
这个比起之前的排法更好理解。
参考资料
-《学习JavaScript数据结构和算法》10.1.5
-《数据结构(C语言版)》9.3.2
快速排序的JavaScript实现的更多相关文章
- 【前端也要学点算法】快速排序的JavaScript实现
作为算法目录下的第一篇博文,快速排序那是再合适不过了.作为最基本最经典的算法之一,我觉得每个程序员都应该熟悉并且掌握它,而不是只会调用库函数,知其然而不知其所以然. 排序算法有10种左右(或许更多), ...
- 快速排序算法javascript实现
function quicksort(arr){ function q(start,end){ if(start>=end){return;} var pivot = start, temp = ...
- 从Chrome源码看JS Array的实现
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...
- JavaScript算法(归并排序与快速排序)
归并排序与快速排序这两个算法放在一起,也是因为时间复杂度都是对数级别的. 目前看过的资料,归并排序看<学习JavaScript数据结构与算法>介绍的归并排序吧,快速排序直接看百度百科,讲的 ...
- JavaScript 快速排序(Quicksort)
"快速排序"的思想很简单,整个排序过程只需要三步: (1)在数据集之中,选择一个元素作为"基准"(pivot). (2)所有小于"基准"的元 ...
- JavaScript排序算法——快速排序
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- JavaScript实现冒泡排序、快速排序、插入排序
JavaScript实现冒泡排序.快速排序.插入排序 时间:2014-01-09 18:05:51 来源: 作者:胡晗 冒泡排序的基本思想:所谓冒泡就是泡泡一个一个往上冒,让体积最轻的泡泡浮在最上 ...
- Javascript中的冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序 算法性能分析
阿里面试中有一道题是这样的: 请用JavaScript语言实现 sort 排序函数,要求:sort([5, 100, 6, 3, -12]) // 返回 [-12, 3, 5, 6, 100],如果你 ...
- javascript 递归之 快速排序
1. 快速排序思想 (1)在数据集之中,选择一个元素作为"基准"(pivot). (2)所有小于"基准"的元素,都移到"基准"的左边:所有大 ...
随机推荐
- dubbo启动报错多个资源争缓存问题
Dubbo Failed to save registry store file, cause: Can not lock the registry cache file 目录(?)[+] 启动的Du ...
- php字符串操作: 去掉UTF-16的空格
$s = json_encode($s); $s = str_replace('\u00a0','',$s); $s = str_replace('\u3000','',$s); $s = str_r ...
- [luogu3413]萌数
[luogu3413]萌数 luogu 考虑数位dp 怎么判断一个数是不是萌数? 只要知道其中某一位和它的前一位相等或者和前一位的前一位相等,那么它就是一个萌数 什么样的数不是萌数? 对于它的每一位都 ...
- BZOJ3211花神游历各国
BZOJ3211花神游历各国 BZOJ luogu 分块 记一个all表示该块是否全部<=1,如果all不为真就暴力修改 因为一个数被开根的次数不多,即使\(10^{12}\)只要开根6次也会变 ...
- SAP内表转XML文件
今天有个兄弟问如何实现以XML的方式输出内表的内容,这个问题我以前好像没有写过.倒不是不会写,而是写的方法太多了,有极其简单的,也有很复杂的,而且网上资料也很多. 找到以前写的一个程序,稍微修改了一下 ...
- activiti基础--0------------------------------生成23张表
1.工作流activiti.cfg.xml配置文件 <beans xmlns="http://www.springframework.org/schema/beans" xm ...
- bd存储
var sessionData = new Array();var setSessionData=function(key,val){ if(sessionStorage){ sessionStora ...
- python基础11 ---函数模块1
函数模块 一.函数模块的作用(为什么要有函数模块) 1.函数模块可以减少代码量 2.函数模块方便阅读 3.函数模块维护性强二.函数模块的本质以及调用方法 1.函数模块的本质就是一个.py结尾的文件,该 ...
- pinpoint改造支持查询
原架构 改造后架构
- Gem简介
Rubyems:简称gems是一个用于对rails组建近些年个打包的ruby打包系统,它提供了一个分发ruby程序喝库的标准格式,还提供了一个管理程序包的工具.Rubyems的功能类似于linux下的 ...