原理分析:
其基本思想是选取其中一个值,经过一轮排序,将小于此值的数据放置其左(右)边,大于此值的数据放置于其右(左)边,
这样就完成一次快速排序,进而通过递归对左右两侧分别进行快速排序,直到排序组内都剩余1项为止。
还是以数组[1,4,7,2,6,8,3,5,9]举例:
假设我们选取中间值6作为中间比较值那么经过一次快速排序之后结果应该是[1,4,2,3,5][6][7,8,9]
然后再对两侧重复递归快速排序
左边[1][2][4,3,5]
右边[7][8][9]
对[4,3,5]继续快速排序得到[3][4,5]
最后对[4,5]排序[4][5]
这样经过1,2,3,4,5轮快速排序之后将各个单独项连接起来即完成排序
结果[1,2,3,4,5,6,7,8,9]
ok,这很容易理解,这个过程是应用算法中快速排序递归去实现我们所谓的"快速排序".
真正的快速排序
接下来我们来看看算法中的快速排序是如何进行的,也就是每一次递归时候内部是经过了什么样的快速排序算法去执行
以我们第一轮快速排序举例:
原始数组:[1,4,7,2,6,8,3,5,9]
初始;i=0,j=8;
步骤一:先从j到左搜寻小于6的进行交换:找到该值后将j赋予该下标
步骤二:然后再从i到右搜寻大于6的进行交换:找到该值将i赋予该下标
直到i与j碰面为止;
具体排序步骤如下:
第一趟排序:
排序前:i=0,j=8;[1,4,7,2,6,8,3,5,9]
步骤一:从右向左递减找到5下标为7
排序后: i=0,j=7;[1,4,7,2,5,8,3,6,9]
第二趟排序:
排序前:i=0,j=7;[1,4,7,2,5,8,3,6,9]
步骤二后:从左到右递增找到大于6的下标为2的7;
排序后:i=2,j=7;[1,4,6,2,5,8,3,7,9]
第三趟排序:
排序前:i=2,j=7;[1,4,6,2,5,8,3,7,9]
步骤一:从下标为7向左找到小于6的值3下标为6;
排序后:i=2,j=6;[1,4,3,2,5,8,6,7,9]
第四趟排序:
排序前:i=2,j=6;[1,4,3,2,5,8,6,7,9]
步骤二:从下标为2向右递增找到大于6的下标为5的8;
排序后:i=5,j=6;[1,4,3,2,5,6,8,7,9]
第五趟排序:
排序前:i=5,j=6;[1,4,3,2,5,6,8,7,9]
步骤一:从下标为6向左找到小于6的值5,不过此时的5下标已经与i的值相同即j--后与i碰头了。于是第一轮排序终止
排序后:i=5,j=6;[1,4,3,2,5,6,8,7,9]
我们可以看出实际排序经历了四趟。到第五趟时候就停止了;这就是快速排序的真正算法
之后以6为中介将左右数据分开递归第一轮执行顺序,直到左右两侧都剩余一个值为止;
两种代码实现:
传统的以及网上盛行的"快速排序"是这样实现的
function QuickSort(arr){
if(arr.length<=1){ return arr; }
var self = arguments.callee;
var left = [],right = [],middle=[];
var mid = arr[Math.floor(arr.length/2)];
for(var i=0;i<arr.length;i++){
if(arr[i]<mid){
left.push(arr[i])
}else if(arr[i]>mid){
right.push(arr[i]);
}else{
middle.push(arr[i]);
}
}
return [].concat(self(left),middle,self(right));
}
而根据我所理解的快速排序算法衍生而来的js排序如下:
function quickSort(arr){if(arr.length<2){
return arr;
}var self = arguments.callee;
var left = [],right = [],middle=[];
var k=Math.floor(arr.length/2),start=0,end=arr.length-1,x=arr[k];
rtl(arr,start,end);
function rtl(arr, i, j){
if(i<j){
if(arr[j] <= x){
var temp=arr[j],t=j;
arr[j]=x;
arr[k]=temp;
j=k;
k=t;ltr(arr,start,end);
}else{
end--;
rtl(arr,i,end);}
}
}function ltr(arr,i,j){
if(i<j){
if(arr[i]>x){
var temp=arr[i],t=i;
arr[i]=x;
arr[k]=temp;i=k;
k=t;
rtl(arr,start,end)
}
else{start++
ltr(arr,start,j);}
}
}
return [].concat(self(arr.slice(0,k)),arr[k],self(arr.slice(k+1,arr.length)));}
其中k始终保持着当前关键比较值的下标在此我们一开始是选取的中间的值作为关键值,最后递归循环得到由大到小的排序结果。
比较两种方式:
第一种:也就是传统的"快速排序"其实不是真正的快速排序 而是根据分治法加上冒泡以及递归综合起来的一种排序,其时间复杂度依然是跟冒泡一样为O(n^2)
因为每一次分组进行比较都是将关键值跟所有对比一遍,不过优点还是代码优美,容易理解;
第二种:虽然代码看上去稍微复杂一些,不过是应用底层的快速排序思想去实现,其时间复杂度为O(nlogn)-O(n^2)
有关复杂度计算可以参考算法复杂度
也就是说第二种的时间复杂度在最差情况下跟第一种是一样的也是O(n^2)而最好的情况仅仅是O(nlogn)