快速排序

基本思路

双指针+递归分治(本质是一个创建二叉树搜索树的过程)

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

我的理解

上面的基本思路是参考网络上大佬的文章整理的出来的,我来说说我的理解。

  1. 在将要排序的数据中选取一个数作为基准数,将这些数据中比所选取的基准数小的数放在所选取基准数的左边为左数组,将比所选取基准数大的数组放在右边为右数组。

  2. 通过递归的方式重复循环1中的过程达到排序的目的。

下面是我的代码

let testArray = [3, 1, 2, 5, 6, 4];
let quickSort = (array) => {
if (array.length < 2) return array;
let leftArray = [];
let rightArray = [];
let baseDigit = array[0];
array.forEach(element => {
if (element < baseDigit) {
leftArray.push(element);
} else if (element > baseDigit) {
rightArray.push(element);
}
});
return quickSort(leftArray).concat(baseDigit, quickSort(rightArray))
};
quickSort(testArray);

某乎上一篇文章的思路

基本思路跟我上述理解大同小异,主要来看看这篇文章具体的实现过程。下面借用原文的图来讲解(原文的图做的很好就不单独画图了,主要讲一讲原文没解释需要注意的地方,和对该篇文章做一个补充),底部附原文链接。

1.数组[2,3,1,5,6,4],创建两指针,一个只想头一个指向尾,再确定一个基准数。

注意:为了方便后面递归是能够确定基准数,这里基准数选取,第一个数或者最后一个数)

2.开始第一次的递归处理,尾指针先从右往左扫,扫到第一个小于(注意是小于,而不是小于等于哦)基准数的位置停住,这时候头指针再从左往右扫,扫到第一个大于基准数的位置停住,这时候是下面的图示状态:

注意:这里如果基准数选区的第一个数,应该尾指针先往左侧扫,若基准数选取为最后一个属则,应是头指针向往右扫)

交换两个指针所指的数,成为了下面的状态:

3.两个数交换完毕,右指针此时指的是arr[2] = 3, 左指针指着arr[1] = 1;交换完毕后右指针继续从当前位置往左扫,扫到1的时候发现和左指针相遇了,那么这个时候就结束左右指针的扫描,左右指针同时指着arr[1] = 1,即:

此时退出循环扫描的过程,交换基准数与左右指针同时所指的数的位置,开头说了,基准数我选择的是arr[0] = 2, 指针指的是arr[1] = 1; 交换过后就变成了:

这时候就发现基准数已经出现在了它排完序后应该在的位置(排完序后是[1,2,3,4,5,6],2出现在了第2位),比这个基准数小的数组出现在了它的左边([1]出现在了2的左边),比基准数大的出现在了它的右边([3,5,6,4]出现在了2的右边)。

4.之后的过程就是对左右数组的分别递归处理。

function quickSort(arr, begin, end) {
//递归出口
if(begin >= end)
return;
var l = begin; // 左指针
var r = end; //右指针
var temp = arr[begin]; //基准数,这里取数组第一个数
//左右指针相遇的时候退出扫描循环
while(l < r) {
//右指针从右向左扫描,碰到第一个小于基准数的时候停住
while(l < r && arr[r] >= temp)
r --;
//左指针从左向右扫描,碰到第一个大于基准数的时候停住
while(l < r && arr[l] <= temp)
l ++;
//交换左右指针所停位置的数
[arr[l], arr[r]] = [arr[r], arr[l]];
}
//最后交换基准数与指针相遇位置的数
[arr[begin], arr[l]] = [arr[l], arr[begin]];
//递归处理左右数组
quickSort(arr, begin, l - 1);
quickSort(arr, l + 1, end);
} var arr = [2,3,4,1,5,6]
quickSort(arr, 0, 5);
console.log(arr)

百科上的思路

百科上的思路跟上述某乎文章基本一致,不过再细节方面不同,这里主要讲已将它们不同的地方,详情请参考原文。(需注意之处也在和上文相同不在赘述)

主要的不同之处在于再上述2,3步骤。

百科上给的方式是:假设让右指针先扫,扫到了比基准数小的,就讲该数与基准数值交换位置,此时左指针指向基准数,再让左指针往右扫描,扫到比基准数大的交换左右指针数值,两指针相遇时直接退出这次递归,通过这样的的方式来达到第一次递归的目的。

上文中则是:假设让右指针先扫,扫到了比基准数小的,指针停住,再让左指针往右扫描扫到比基准数大的数再停住,然后交换两指针指向的值,反复调用,两指针相遇时与基准数的数值进行交换。

相对于理解来说我认为是,百科的方式更容易理解(其实是我先理解了百科的方式让后想到了自己的思路,最后才理解了某乎的方式)。

const quickSort = (array) => {
const sort = (arr, left = 0, right = arr.length - 1) => {
if (left >= right) {//如果左边的索引大于等于右边的索引说明整理完毕
return
}
let i = left
let j = right
const baseVal = arr[j] // 取无序数组最后一个数为基准值
while (i < j) {//把所有比基准值小的数放在左边大的数放在右边
while (i < j && arr[i] <= baseVal) { //找到一个比基准值大的数交换
i++
}
arr[j] = arr[i] // 将较大的值放在右边如果没有比基准值大的数就是将自己赋值给自己(i 等于 j)
while (j > i && arr[j] >= baseVal) { //找到一个比基准值小的数交换
j--
}
arr[i] = arr[j] // 将较小的值放在左边如果没有找到比基准值小的数就是将自己赋值给自己(i 等于 j)
}
arr[j] = baseVal // 将基准值放至中央位置完成一次循环(这时候 j 等于 i )
sort(arr, left, j-1) // 将左边的无序数组重复上面的操作
sort(arr, j+1, right) // 将右边的无序数组重复上面的操作
}
const newArr = array.concat() // 为了保证这个函数是纯函数拷贝一次数组
sort(newArr)
return newArr
}
 

性能

既然这里给出了三种方式来实现快排,那我们就来测试一下性能

由于百科的方法有问题再5位数以上会报错10000后面不测试百科方法

第一个数我的方法

在1000个相同随机数的情况下

在100000个相同随机数的情况下

结论

从性能上讲是某乎的方法更高。

附测试代码

// 我的方法
let myQuickSort = (array) => {
if (array.length < 2) return array;
let leftArray = [];
let rightArray = [];
let baseDigit = array[0];
array.forEach(element => {
if (element < baseDigit) {
leftArray.push(element);
} else if (element > baseDigit) {
rightArray.push(element);
}
});
return myQuickSort(leftArray).concat(baseDigit, myQuickSort(rightArray))
};

// 某乎的方法
let moHu = (arr, begin, end) => {
//递归出口
if (begin >= end)
return;
var l = begin; // 左指针
var r = end; //右指针
var temp = arr[begin]; //基准数,这里取数组第一个数
//左右指针相遇的时候退出扫描循环
while (l < r) {
//右指针从右向左扫描,碰到第一个小于基准数的时候停住
while (l < r && arr[r] >= temp)
r--;
//左指针从左向右扫描,碰到第一个大于基准数的时候停住
while (l < r && arr[l] <= temp)
l++;
//交换左右指针所停位置的数
[arr[l], arr[r]] = [arr[r], arr[l]];
}
//最后交换基准数与指针相遇位置的数
[arr[begin], arr[l]] = [arr[l], arr[begin]];
//递归处理左右数组
moHu(arr, begin, l - 1);
moHu(arr, l + 1, end);
};

//百科的方法
let baiKe = (array) => {
let sort = (arr, left = 0, right = arr.length - 1) => {
if (left >= right) {//如果左边的索引大于等于右边的索引说明整理完毕
return
}
let i = left;
let j = right;
const baseVal = arr[j];// 取无序数组最后一个数为基准值
while (i < j) {//把所有比基准值小的数放在左边大的数放在右边
while (i < j && arr[i] <= baseVal) { //找到一个比基准值大的数交换
i++
}
arr[j] = arr[i]; // 将较大的值放在右边如果没有比基准值大的数就是将自己赋值给自己(i 等于 j)
while (j > i && arr[j] >= baseVal) { //找到一个比基准值小的数交换
j--
}
arr[i] = arr[j] // 将较小的值放在左边如果没有找到比基准值小的数就是将自己赋值给自己(i 等于 j)
}
arr[j] = baseVal; // 将基准值放至中央位置完成一次循环(这时候 j 等于 i )
sort(arr, left, j - 1); // 将左边的无序数组重复上面的操作
sort(arr, j + 1, right) // 将右边的无序数组重复上面的操作
};
const newArr = array.concat();// 为了保证这个函数是纯函数拷贝一次数组
sort(newArr);
return newArr
};

// 生成一个1-count的随机数组
let createTestArray = (count) => {
let temArray = [];
while (count > 0) {
temArray.unshift(count);
count--;
}
let i = temArray.length;
while (i) {
let j = Math.floor(Math.random() * i--);
[temArray[j], temArray[i]] = [temArray[i], temArray[j]];
}
return temArray;
};

// 测试
let testQuickSort = (name, func, arr, moHu) => {
if (!!moHu) {
console.time(name);
func(arr, moHu.begin, moHu.end);
console.timeEnd(name);
return;
}
console.time(name);
func(arr);
console.timeEnd(name);
};

// 生成1-100000的随机数组
const testArray = createTestArray(100000);
testQuickSort('myQuickSort', myQuickSort, testArray);
testQuickSort('moHu', moHu, testArray, {begin: 0, end: 99999});
// testQuickSort('baiKe', baiKe, testArray);

最后

最后记一笔,快速排序是不稳定排序也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。

文章连接:

某乎:微软前端社招笔试详解

百科:百度百科

聊一聊快速排序(Js)的更多相关文章

  1. 今天聊一聊nuxt.js(上)

    背景 近期在做内部系统的重构,从一线业务彻底的重构,经过充分的考虑我们准备把这个项目打造成前台业务的试验站,比如ssr和一些其他的前沿技术的探索,积累充分的经验后待合适的契机应用到C端的项目中. 既然 ...

  2. 递归版快速排序-JS代码

    "use strict" var arr1=[11,21,3,4,0]; function qSort(arr){ var mid,left,right,len,i,j,empty ...

  3. io.js的服務器突破

    Node.js与io.js那些事儿 InfoQ中文站 05月20日 14:26 去年12月,多位重量级Node.js开发者不满Joyent对Node.js的管理,自立门户创建了io.js.io.js的 ...

  4. Underscore.js 常用类型判断以及一些有用的工具方法

    1. 常用类型判断以及一些有用的工具方法 underscore.js 中一些 JavaScript 常用类型检查方法,以及一些工具类的判断方法. 首先我们先来谈一谈数组类型的判断.先贴出我自己封装好的 ...

  5. Node.js 模块化你所需要知道的事

    一.前言 我们知道,Node.js是基于CommonJS规范进行模块化管理的,模块化是面对复杂的业务场景不可或缺的工具,或许你经常使用它,但却从没有系统的了解过,所以今天我们来聊一聊Node.js模块 ...

  6. 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  7. js 中的快速排序算法简单实现

    对于快速排序,最早是在c++中看到,它是利用指针来交换顺序,其实无论哪种语言,原理 和 思想都是一样,然而真正用起来的时候就特别容易忽略一些事实,导致实现失败.废话少说,下面用js实现一下快速排序: ...

  8. JS实现冒泡排序,插入排序和快速排序(从input中获取内容)

    以前参加面试的时候,被问到过让用JS实现一个快速排序,当时太年轻,并没有回答上来. 于是,这里便把三种排序都用JS来做了一下.结合html,从input文本框中获取输入进行排序. 关于这几种算法的原理 ...

  9. JS快速排序和去重

    JS的快速排序和JS去重在面试的时候问的挺多的.下面是我对快速排序的理解,和快速排序,去重的代码. 1.什么是快速排序? 第一步: 快速排序就是去个中间值,把比中间值小的放在左边设为arrLeft,比 ...

随机推荐

  1. Ubuntu16.04下安装Hadoop

    一.记录理由 刚开始只是想要学习怎么使用Hive的.想着安装应该很简单,没想到花了整整一天的时间来安装,为了避免下次犯同样的错误,特此记录. 二.安装Hadoop 网上教你怎么安装Hadoop的文章有 ...

  2. SOFA 源码分析 — 自定义线程池原理

    前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...

  3. 2018 CISCN reverse wp

    2018 CISCN reverse wp 这题比赛的时候没做出来,主要是心态崩了看不下去..赛后看了下网上的wp发现不难,是自己想复杂了.这里将我的思路和exp放出来,希望大家一起交流学习. mai ...

  4. java基础语法3

    逻辑运算符 &:与,和有共同的,必须条件都满足才是true 有false就返回false,必须都是true才返回true |:或者,其中有一个满足条件就返回true ^亦或,相同是false, ...

  5. oracle通用函数,nvl,nvl2,NULLIF ,coalesce

    Oracle 通用函数 ① NVL 函数--------将空值转换成一个已知的值: 可以使用的数据类型有日期.字符.数字. 函数的一般形式:   NVL(commission_pct,0)       ...

  6. Web开发环境搭建 Eclipse-Java EE 篇

    Web开发环境搭建 Eclipse-Java EE 篇 [原创内容,转载注名出处] 1. 下载和安装 1.1 下载JDK 在Java官方网站下载最新版本的 Java SE:  http://www.o ...

  7. 【转】JavaScript 错误处理与调试——“错误处理”的注意要点

    try-catch语句 该语句最适合处理那些我们无法控制的错误,在明明白白地知道自己的代码会发生错误时,再使用该语句就不太合适了. ECMA-262第3版引入了try-catch语句,基本的语法如下所 ...

  8. bootstrap模态对话框(最简单)

    根据公司的需求,需要一个对话框来返回给客户的失败原因,刚刚开在百度上搜了老半天,嫩是没有搜索一个自己想要的,后来发送私信给一个博友,经过他哪里找到了自己想要的答案,废话不多说直接看源码: <!D ...

  9. MySQL大量数据入库的性能比较

    单位IM改版了用户聊天内容要存放在数据库. 一般JAVA Insert MySQL有如下几种方式1.自动提交Insert2.事务提交Insert3.批量提交4.使用Load File接口 模拟表结构如 ...

  10. FPGA学习笔记(四)——Verilog基本语法

    ###### [该随笔部分内容转载自小梅哥] ######### 组合逻辑:    多路选择器.加法器.译码器.乘法器 时序逻辑:    计数器.分频器.定时器.移位寄存器 一.Verilog文件的基 ...