在算法中,排序的方法有很多种,大致可以分为五类,八种。插入排序分为:希尔排序,直接插入排序;选择排序:简单选择排序,堆排序;交换排序:快速排序,冒泡排序;还有两类为:归并排序和基数排序。本文作为这个系列的第一讲,来介绍交换排序的第一种排序方式:快速排序。
前端程序猿在操作数组的时候一定会遇到排序的需求,javascript数组默认会提供一个升序排序方法(ARRAY.prototype.sort)。使用数组调用这个方法之后会返回一个排序之后的数组,但是这个数组默认是对字符进行排序,简单来说如果对数组[5,4,3,2,1]调用sort方法会得到升序的[1,2,3,4,5],但是如果对数组[10,20,1,2]进行sort方法调用,会得到[1,10,2,20]这样的进行首字符排序的数组。解决方法就是自己定义一个方法,返回任意两个变量的差值,将这个方法作为参数传入sort()方法。这时返回值就为按数字排序。具体实现:
var a = [20,10,2,1];
a.sort();//返回值[1,10,2,20]
function compare(a,b) {
return a-b;
}
a.sort(compare);//返回值[1,2,10,20]。
前面介绍了一下javascript提供的排序方法,这个排序算法的本质就是今天要介绍的快速排序算法。快速排序算法是由Tony Hoare在1959年提出的。这种排序算法和它的名字一样,特点就是排序速度很快,维基百科上是这么形容他的:
When implemented well, it can be about two or three times faster than its main competitors,merge sortandheapsort.
Mathematical analysisof quicksort shows that,on average, the algorithm takesO(nlogn) comparisons to sortnitems. In theworst case, it makes O(n2) comparisons, though this behavior is rare.
在较优的情况下,它所花费的时间可能是其他排序算法(如归并排序,堆排序)的二分之一甚至是三分之一。对于快速排序进行数学分析显示,它的平均时间复杂度仅仅为O(nlgn)。在极端的情况下,时间复杂度为O(n2)但是这种情况极难遇到。
可见快速排序是一种效率极高,速度很快的排序算法,快速排序实质上用到了分治的思想,就是将一个复杂的问题转化为几个简单的问题来逐一解决。接下来,就开始介绍快速排序算法。
要把一个数组进行快速排序和把大象装进冰箱一样,一共分三个步骤:
1、在数组中找到一个支点,也可以叫做基准。
注:这个基准就是数组中任意一个元素。
2、遍历整个数组,将比基准大的元素放到基准的右面,将比基准小的元素放到基准的左面。
3、对基准左面的数组和基准右面得数组重复前两步。
看完上面的三个步骤,很多人一定会觉得似懂非懂,实践才能出真知,我举一个例子:
待排序的数组:[5,4,3,2,1]
第一步、这里默认以3为基准(哪个元素为基准都可以)
第二步、将数组中比3大的元素放到3的右侧,比3小的元素放到3的左侧——》[2,1,3,5,4]
第三步、这个时候这个数组抛去基准可以分为两部分[2,1],[5,4];两个数组
[2,1]设基准为2,因为1比2小所以1放在2的左边——》[1,2]
[5,4]设基准为5,因为4比5小所以4放在5的左边——》[4,5]
然后再重复第一步,发现继续划分的数组只有一个元素了,所以不需要在进行下去了,最后将分开的数组和基准进行结合就得到了排序之后的数组:[1,2]+[3]+[4,5]——》[1,2,3,4,5]
思路有了,下面就来进行代码的实现:
代码的实现也是那三个步骤,按部就班的进行实现。
第一步:
首先将快速排序封装一个函数,参数可以传入一个待排序的数组。
var quicklySort = function(arr){
};
由于后面要进行递归的进行调用,所以要设置一个限定条件,也就是上面第三步最后,如果分治之后的数组的长度为1或者传入的数组是个空数组,将函数停止运行,并返回排序之后的数组:
if(arr.length<=1){
return arr;
}
准备工作做完真正的开始上面的第一步操作:在数组中找到一个支点,也可以叫做基准
这里为了方便我们将数组的二分之一的位置取为基准,并取到这个基准
var pivotIndex = Math.floor(arr.length/2);//这里调用了js当中的四舍五入方法,防止数组长度为奇数的情况,对数组进行小数位 // 舍去操作;另外Math.ceil()这个是舍去小数位,整数位+1的效果;Math.round() // 实现四舍五入;在js中最常用的三种对数字进行四舍五入的方法。
var pivot = arr.splice(pivotIndex,1);//数组的splice方法,实现的数组定位删除,插入的功能,第一个参数为删除元素的索引,第 //二个为删除元素的个数,第三个是在这个位置插入的元素。这里将基准保存到pivot变量 //中,并在数组中删除。
第二步:遍历整个数组,将比基准大的元素放到基准的右面,将比基准小的元素放到基准的左面。
var left = [];//先定义存放左右元素的数组
var right = []
for(var i=0;i<arr.length;i++){
if(arr[i]<pivot){
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
第三步、对左右数组继续进行前两步,并将左右数组和基准拼到一起
return quicklySort(left).concat([pivot]+quicklySort(right));//数组的concat方法,把两个数组合并,如果只用加法操作符最后得 //到的是一个排完序的字符串
虽然关于代码篇幅很多,但是主要是我讲解的文字,代码其实没有很多,将这些伪代码合并就是下面的这个方法。
关于快速排序的内容就是以上的这些,可能好多程序猿都会觉得前端开发碰不到数据结构,算法的知识,在日常工作中可能永远都不会需要去了解的很深,没什么用。其实我之前一直也是这种想法,上学的时候也觉得这种东西搞科学研究的时候才能用上,学会框架怎么用,怎么用语言的特性编代码才是最关键的,但是渐渐发现。程序猿这类工作和修炼武功非常像,小时候玩过一个游戏叫天龙八部,游戏里面的主角仅仅学会绝世武功是没用的,需要搭配内功心法才能发挥最大的威力。在程序开发中,程序语言也许就是绝世的武功,而数据结构算法这样的知识才是内功,因为这些内功不限于你会什么武功,只要你的内功足够深厚,什么武功能威震四方,但是如果你的武功非常强大,但是内功严重不足,也许就会练着绝世武功,但是发挥的效果也就和一般的野狐禅一样,水平一直停步不前。在日常的程序开发中,面对同样的问题,不同的人解决的方法可能会不同,虽然都能实现功能。但是只有内功深厚的人才会写出更优的代码,更易于维护的代码。我想这也是国内具有号召力的互联网公司招聘的第一条就是精通数据结构算法等计算机基础知识,因为一门程序语言对于有点基础的人来说一两周就可以上手干活了,但是基础知识的掌握不是一朝一夕可以掌握的,所以练好内功,才是王道!
这是我的第一篇博客,希望自己能坚持下去,加油!