聊一聊快速排序(Js)
快速排序
基本思路
双指针+递归分治(本质是一个创建二叉树搜索树的过程)
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
我的理解
上面的基本思路是参考网络上大佬的文章整理的出来的,我来说说我的理解。
在将要排序的数据中选取一个数作为基准数,将这些数据中比所选取的基准数小的数放在所选取基准数的左边为左数组,将比所选取基准数大的数组放在右边为右数组。
通过递归的方式重复循环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)的更多相关文章
- 今天聊一聊nuxt.js(上)
背景 近期在做内部系统的重构,从一线业务彻底的重构,经过充分的考虑我们准备把这个项目打造成前台业务的试验站,比如ssr和一些其他的前沿技术的探索,积累充分的经验后待合适的契机应用到C端的项目中. 既然 ...
- 递归版快速排序-JS代码
"use strict" var arr1=[11,21,3,4,0]; function qSort(arr){ var mid,left,right,len,i,j,empty ...
- io.js的服務器突破
Node.js与io.js那些事儿 InfoQ中文站 05月20日 14:26 去年12月,多位重量级Node.js开发者不满Joyent对Node.js的管理,自立门户创建了io.js.io.js的 ...
- Underscore.js 常用类型判断以及一些有用的工具方法
1. 常用类型判断以及一些有用的工具方法 underscore.js 中一些 JavaScript 常用类型检查方法,以及一些工具类的判断方法. 首先我们先来谈一谈数组类型的判断.先贴出我自己封装好的 ...
- Node.js 模块化你所需要知道的事
一.前言 我们知道,Node.js是基于CommonJS规范进行模块化管理的,模块化是面对复杂的业务场景不可或缺的工具,或许你经常使用它,但却从没有系统的了解过,所以今天我们来聊一聊Node.js模块 ...
- 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- js 中的快速排序算法简单实现
对于快速排序,最早是在c++中看到,它是利用指针来交换顺序,其实无论哪种语言,原理 和 思想都是一样,然而真正用起来的时候就特别容易忽略一些事实,导致实现失败.废话少说,下面用js实现一下快速排序: ...
- JS实现冒泡排序,插入排序和快速排序(从input中获取内容)
以前参加面试的时候,被问到过让用JS实现一个快速排序,当时太年轻,并没有回答上来. 于是,这里便把三种排序都用JS来做了一下.结合html,从input文本框中获取输入进行排序. 关于这几种算法的原理 ...
- JS快速排序和去重
JS的快速排序和JS去重在面试的时候问的挺多的.下面是我对快速排序的理解,和快速排序,去重的代码. 1.什么是快速排序? 第一步: 快速排序就是去个中间值,把比中间值小的放在左边设为arrLeft,比 ...
随机推荐
- 部署网站: 配置项目到iis上运行报目录错误
配置项目到iis上运行报目录错误 以下三种方法可使用: 1.添加文件访问权限 everyone (线上环境慎用) 2.重新注册iis 3.web.config 加一段话 在<system.we ...
- 利用VMWare 11 在 Windows 8.1 下安装与优化 OS X 10.10
此文中相关工具点击我从百度网盘下载 一.安装 第一步安装参考百度经验:点击这里,鉴于其中提供的文件全部不能用,替换成了上面的,亲测Intel Core I5 4200M 可用. 二.优化 这里给出一些 ...
- SSM博客登录注册
我的博客采用的是 spring+springmvc+mybatis框架,用maven和git管理项目,之后的其他功能还有待进一步的学习. 首先新建一个maven项目,我的项目组成大概就这样, 建立好项 ...
- 火狐兼容window.event.returnValue=false;
火狐中window.event是未定义的,可用e.preventDefault();替代window.event.returnValue=false; 直接上图
- 使用FluentScheduler和IIS预加载在asp.net中实现定时任务管理
FluentScheduler介绍 github地址:https://github.com/fluentscheduler/FluentScheduler FluentScheduler是一个简单的任 ...
- Java中判断是否为空的方法
1.判断字符串或者对象是否为空 首先来看一下工具StringUtils的判断方法: 一种是org.apache.commons.lang3包下的: 另一种是org.springframework.ut ...
- 【转】js 好的程序设计,应该什么时候使用 try catch 呢?
比如在检测浏览器是否支持某些功能的时候 if (!xx) { console.error('此浏览器不支持 xx 功能') } 还是 try { xx; } catch(e) { throw new ...
- String的substring()用于截取字符串
substring() 用于返回一个字符串的子字符串,即截取字符串功能. substring()常用的重载方法如下: substring(int beginIndex,int endIndex) 意思 ...
- Mac命令行
参考:http://www.cnblogs.com/-ios/p/4949923.html 必读 涵盖范围: 这篇文章对刚接触命令行的新手以及具有命令行使用经验的人都有用处.本文致力于做到覆盖面广(尽 ...
- 修改eclipse的workspace目录
打开Window,选择Preferences->General-->Startup and Shutdown->Workspaces,勾选Prompt for workspace o ...