快速排序

看了《啊哈!算法》,模仿里面的思路用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,复杂度计算还在学习中。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值