快速排序
来源:互联网 发布:巨杉数据库 文档 编辑:程序博客网 时间: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,复杂度计算还在学习中。。。
- 快速排序
- 快速排序
- 快速排序
- 快速排序!
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- Gson使用及多层嵌套 json 定义类
- 52.[Python]使用threading进行多线程编程
- 使用ReactiveCocoa实现iOS平台响应式编程
- 单例模式——国庆收心
- 智慧北京:XUtils的View的注入
- 快速排序
- 如何使用VMware Workstation 12 安装Ubuntu虚拟机
- JSON
- html多媒体和图片元素
- 文章标题
- 数据库和表的增删改
- Orm之开篇
- SfM(三)-- bundle adjustment
- polya/burnside定理入门