思想

分治的思想,将原始数组分为较小的数组(但没有像归并排序一样将它们分隔开)。

  1. 主元选择: 从数组中任意选择一项作为主元,通常为数组的第一项,即arr[i];或数组的中间项, arr[Math.floor((i+j)/2)];
  2. 划分操作: 创建两个指针,左边一个指向数组的第一项,右边一个指向数组的最后一项。向右移动左指针,直到找到一个不小于比主元的项;向左移动右指针,直到找到一个不大于主元的项。交换它们。然后重复这个过程,直到左指针超过了右指针。这样使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。注意:在这个过程中,主元的位置也可能发生了改变;而主元本身在一次划分操作之后不会在正确的位置,其正确的位置应该在本次划分操作后最终得到的那个分割点上(即sliceIndex上),在下轮操作的右半路操作会立刻把主元换到正确的位置上
  3. 对子数组重复划分操作: 比主元小的项(即主元左边的部分)组成一个子数组,比主元大的项(即主元右边的部分)组成另一个子数组。对这两个小数组继续执行主元选择和划分操作。直到数组已完全排序。

代码

代码段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实现的更多相关文章

  1. 【前端也要学点算法】快速排序的JavaScript实现

    作为算法目录下的第一篇博文,快速排序那是再合适不过了.作为最基本最经典的算法之一,我觉得每个程序员都应该熟悉并且掌握它,而不是只会调用库函数,知其然而不知其所以然. 排序算法有10种左右(或许更多), ...

  2. 快速排序算法javascript实现

    function quicksort(arr){ function q(start,end){ if(start>=end){return;} var pivot = start, temp = ...

  3. 从Chrome源码看JS Array的实现

    .aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...

  4. JavaScript算法(归并排序与快速排序)

    归并排序与快速排序这两个算法放在一起,也是因为时间复杂度都是对数级别的. 目前看过的资料,归并排序看<学习JavaScript数据结构与算法>介绍的归并排序吧,快速排序直接看百度百科,讲的 ...

  5. JavaScript 快速排序(Quicksort)

    "快速排序"的思想很简单,整个排序过程只需要三步: (1)在数据集之中,选择一个元素作为"基准"(pivot). (2)所有小于"基准"的元 ...

  6. JavaScript排序算法——快速排序

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. JavaScript实现冒泡排序、快速排序、插入排序

    JavaScript实现冒泡排序.快速排序.插入排序 时间:2014-01-09 18:05:51  来源:  作者:胡晗 冒泡排序的基本思想:所谓冒泡就是泡泡一个一个往上冒,让体积最轻的泡泡浮在最上 ...

  8. Javascript中的冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序 算法性能分析

    阿里面试中有一道题是这样的: 请用JavaScript语言实现 sort 排序函数,要求:sort([5, 100, 6, 3, -12]) // 返回 [-12, 3, 5, 6, 100],如果你 ...

  9. javascript 递归之 快速排序

    1. 快速排序思想 (1)在数据集之中,选择一个元素作为"基准"(pivot). (2)所有小于"基准"的元素,都移到"基准"的左边:所有大 ...

随机推荐

  1. 聊聊数据库~6.SQL运维中篇

    上篇回顾:https://www.cnblogs.com/dotnetcrazy/p/10810798.html#top 1.6.5.MySQL日志相关 本文的测试环境:MySQL5.7.26.Mar ...

  2. vim 一键添加注释 自动添加文件头注释

    估计大家也都和我一样用过不少的编辑器,什么notepad2,emeditor,editplus,ultraedit,vs2005,sourceinsight,slickedit,emacs,vim(g ...

  3. pymysql插入datetime类型

    第一种 create_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 第二种 update_time=time ...

  4. Yii2之事件处理

    通过事件(Event)处理,可以在某个特定时刻执行指定的代码,可以解耦代码,同时也增加了可维护性,通常,事件在客户端软件中比较好理解,比如onClick,onFocus,当点击按钮,获取到焦点时执行指 ...

  5. c++ 之重要性

    c++的功能比c语言大的多,c语言偏最底层,且程序短小,而对于一个大的系统,用c++,因为它具备了c语言的优点. 很多学嵌入式的觉得学了c语言之后,就不用学c++了,会认为c++很简单,然而,c++并 ...

  6. 【leetcode刷题笔记】Largest Rectangle in Histogram

    Given n non-negative integers representing the histogram's bar height where the width of each bar is ...

  7. 【leetcode刷题笔记】Best Time to Buy and Sell Stock II

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  8. Docker 命令篇

    Docker命令比较对,我们来慢慢学 Docker run(运行Container) 常用选项: -d Run container in background and print container ...

  9. 最小生成树prim算法 POJ2031

    #include<iostream> #include<algorithm> #include<string.h> #include<ctype.h> ...

  10. MySQL存储过程入门教程

    存储过程介绍 存储过程是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中.用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它.存储过程可由应用程序通过一个调用来执行,而且 ...