快速排序

来源:互联网 发布:巨杉数据库 文档 编辑:程序博客网 时间:2024/05/29 15:10

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

0 0
原创粉丝点击