看了《啊哈!算法》,模仿里面的思路用js写了快速排序,代码如下:
var arr=[0,1,4,7,2,3,9,4,2,0,8,11,2,1];
function quickSort(arr,left,right){
if(left==undefined){
left=0;
}
if(right==undefined){
right=arr.length-1;
}
if(left<right){
var i=left,j=right;
while(i!=j){//左右哨兵还没相遇的时候,继续进行基数比较
while(arr[j]>=arr[left] && j>i){//先从右边开始,遇到第一个比基数小的停下
j--;
}
while(arr[i]<=arr[left] && j>i){
i++;
}
if(j>i){//哨兵还没有相遇,交换这两个数
var tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
//都交换完后,此时i的左边都<=基数,右边都>=基数,基数归位
var cardinal=arr[left];
arr[left]=arr[i];
arr[i]=cardinal;
//递归
quickSort(arr,left,i-1);
quickSort(arr,i+1,right);
}
}
quickSort(arr);
console.log("排序结果:",arr);
这个算法的思路是,先将数组的第一个数作为基数,设置一个左哨兵和右哨兵,左哨兵遇到的比基数大的数<=>右哨兵遇到的比基数小的数,两者交换,直到哨兵相遇,此时i的左边都<=基数,右边都>=基数,于是将基数与第i个数交换,然后对i左右两边的数组进行如上的操作,即递归。
这个算法有几个要注意的点,一是必须从右边(基数的对面)开始进行比较,为什么呢?比如数组[6,1,2,3,7,8,9],如果从左边开始比较,那么左哨兵在遇到7的时候就会停下,而右哨兵因为while(arr[j]>=arr[left] && j>i) 中j>i的限制,也会在遇到7的时候停下,这个时候如果将基数6和第i位数7进行交换,数组就变成[7,1,2,3,6,8,9],而不是我们设想中的[3,1,2,6,7,8,9]。而从右边开始就可以避免这种情况。
还有一个就是第一个基数必须是数组的第0个数,如果不是从数组的第0个数开始,那么这个数的左侧的数就会被忽略。
在网上看到一个很有趣的快速排序的舞蹈视频,很形象直观:http://v.youku.com/v_show/id_XMzMyODk4NTQ4.html
阮一峰大神也写过一篇js的快速排序:http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html,这个的思想和快排的一样,不过实现方法略有不同,复制一下代码:
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
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 quickSort(left).concat([pivot], quickSort(right));
};
思路:选择中间的数作为第一个基数,以此为分界线,把数组分成两半,然后遍历数组,比基数大的放到左数组,比基数小的放到右数组,最后将基数归位,即数组=[左数组,基数,右数组];然后递归对左、右数组进行同样的操作;当数组的长度为1时无需再排序,停止递归。
用循环写了个大数组测试了一下:
var arr=[];
for(var i=10000;i>=0;i--){
arr.push(i);
}
第一种写法,当i=10000时谷歌浏览器就会报错:
Uncaught RangeError: Maximum call stack size exceeded
阮大神的算法则可正常运行。我用C语言运行了一下第一种写法:
#include <stdio.h>
//快速排序
int arr[10001],n=10000;
void quicksort(int left,int right)
{
int tmp,//比较的基准数
i,//左哨兵的位置
j,//右哨兵的位置
t;//临时变量,用于交换
if(left>right) //如果左哨兵已经在右哨兵 右边,则停止交换
{
return;
}
tmp=arr[left];
i=left;
j=right;
while(i!=j)
{
while(arr[j]>=tmp && i<j) //必须先比较右边的
{
j--;
}
while(arr[i]<=tmp && i<j)
{
i++;
}
if(i<j)
{
t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
//基准数归位
arr[left]=arr[i];
arr[i]=tmp;
//递归左右,直至两边都排完,i为基准数已归位
quicksort(left,i-1);
quicksort(i+1,right);
return;
}
int main()
{
//printf("请输入个数:");
//scanf("%d",&n);
//printf("\n请输入数据:\n");
int i;
for(i=10000; i>=0; i--)
{
arr[i]=i;
}
quicksort(0,n-1);
printf("\n排序结果为:\n");
for(i=0; i<n; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
正常运行。。。所以应该不是算法的问题,而是js程序的递归写法有问题,于是上网搜了一下,有一篇给出了解释:http://www.zuojj.com/archives/1115.html
原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回值等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。
可以使用 闭包 解决这类问题,于是我把程序改成如下:
var arr=[];
for(var i=10000;i>0;i--){
arr.push(i);
}
var quickSort=function(arr,left,right){
if(left==undefined){
left=0;
}
if(right==undefined){
right=arr.length-1;
}
if(left<right){
var i=left,j=right;
while(i!=j){//左右哨兵还没相遇的时候,继续进行基数比较
while(arr[j]>=arr[left] && j>i){//先从右边开始,遇到第一个比基数小的停下
j--;
}
while(arr[i]<=arr[left] && j>i){
i++;
}
if(j>i){//哨兵还没有相遇,交换这两个数
var tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
//都交换完后,此时i的左边都<=基数,右边都>=基数,基数归位
var cardinal=arr[left];
arr[left]=arr[i];
arr[i]=cardinal;
//递归
return function(){
quickSort(arr,left,i-1);
quickSort(arr,i+1,right);
}
}
}
quickSort(arr);
console.log("排序结果:",arr);
正常运行!
此时每次调用时,返回一个匿名函数,匿名函数执行相关的参数和局部变量将会释放,不会额外增加堆栈大小。
据说快速排序的平均速度是所有排序算法中最快的,平均时间复杂度为nlogn,复杂度计算还在学习中。。。