冒泡,快排算法之javascript初体验
引子:javascript实际使用的排序算法在标准中没有定义,可能是冒泡或快排。不用数组原生的 sort() 方法来实现冒泡和快排。
Part 1:冒泡排序(Bubble Sort)
- 原理:临近的两数两两进行比较,按从小到大或从大到小顺序排列,进行多趟,每一趟过去后(外循环),最大或最小的数字被交换到最后一位(内循环)。
- 代码:共进行6趟,每一趟比较5次
1 var a=[6,2,4,1,5,9],t; 2 for(var i=0;i<a.length;i++){ 3 for(var j=0;j<a.length-1;j++){ 4 if(a[j]>a[j+1]){ 5 t=a[j]; 6 a[j]=a[j+1]; 7 a[j+1]=t; 8 } 9 } 10 }
-----分割线----更正于2016.2.25
更正冒泡代码:
- 代码:共进行5趟(最后一趟即要开始比较的最后一个数不用比较了,它已经被其他趟挤到该在的位置了),每一趟比较5-i次,因为那i次在上一趟中已经排好序了在该在的位置了,肯定是前小后大没什么可比较的了,即后面继续比较无意义。
1 var a=[6,2,4,1,5,9],t; 2 for(var i=0;i<a.length-1;i++){ 3 for(var j=0;j<a.length-1-i;j++){ 4 if(a[j]>a[j+1]){ 5 t=a[j];i 6 a[j]=a[j+1]; 7 a[j+1]=t; 8 } 9 } 10 }
Part 2:快速排序(Quick Sort)
- 原理:在一个个数为n的序列中找一个数作为基准数(任意哪个皆可,一般找第一个),即找到该基准数所在位置k,k作为分界点,k左边数均小于基准数,k右边数均大于基准数。
- 具体做法:设置i,j两个指针分别指向最左端和最右端,每次比较都从j指针开始向左移动寻找比基准数小的数后停止移动,然后指针i向右移动寻找比基准数大的数后停止移动,交换此时i,j所指向的内容,这算一趟中的一次交换完成,直到i,j指针相遇位置即找到k,将基准数和k位置的数字交换,这算完成一趟排序。(解释一下为什么每次一定要从j指针移动开始:举个栗子说明,某序列为[3,1,2,5,4],基准数为3,i指向3,j指向4,如你所愿假设先从i移动找比3大的,i指针移动到5停止,j指针找比3小的,移动到5时相遇了,所以5的位置即为k,交换3和5,序列变为[5,1,2,3,4],这显然越排越乱。之所以一定要从右边开始就是保证了从右边过滤的是比基准数小的,然后再从左边移动时即使相遇了也能保证这个数比基准数小交换后不会影响序列)
- 分析原理:用分治的思想进行多趟寻找,每一趟都找出基准数所在位置,其中每一趟最坏情况都是两个指针指向了相邻元素,进行交换,这样比较次数和交换次数和冒泡一样了。所以快排在最坏情况下时间复杂度和冒泡一样O(n2)。但快排的平均时间复杂度为O(nlogn),快排之所以比冒泡有优势,在于每次交换都是跳跃式的,不一定每个位置都交换,而且每一趟序列的个数有可能是大于1那样的递减因为k位置不确定划分后才能确定左右两边序列个数,但冒泡每一趟比较序列的个数是规律的每次少一个(n,n-1,n-2...),快排不是像冒泡相邻交换,跳跃式的交换使交换距离变大,因此总的比较和交换次数变少,速度自然就提高。
- 举例:某序列[6,1,2,7,9,3, 4,5,10,8],说明以下图片均引用自网络(侵删)
以6作为基准数,寻找分界点k。
1.现在i,j指针均已就位
2.j指针先从右往左找小于6的数再停下,然后i指针再从左往右找大于6的数再停下,最后交换
3.第一趟的第一次交换完毕,序列变为[6,1,2,5,9,3,4,7,10,8]
4.j指针继续向左寻找小于6的元素,i指针继续向右寻找大于6的元素,最后交换
5.第一趟的第二次交换完毕,序列变为[6,1,2,5,4,3,9,7,10,8]
6.j指针继续向左寻找小于6的元素,i指针继续向右寻找大于6的元素,到3时相遇停止移动,即找到位置k
7.交换3和6,第一趟排序完毕。序列变为[3,1,2,5,4,6,9,7,10,8]
8.剩下的就每一趟都是按此方式重排,可用分治的思想解决,先解决6左边的序列,完毕后再解决6右边序列,直到不可拆分出新的子序列为止
- 代码:为了测试代码差点把浏览器搞崩,一不小心少了某个条件就进入死递归了。。。
1 function quicksort(a,left,right){ 2 if(left>right){ //一定要有这个判断,因为有递归left和i-1,若没有这个判断条件,该函数会进入无限死错位递归 3 return; 4 } 5 6 var i=left, 7 j=right, 8 jizhun=a[left]; //基准总是取序列开头的元素 9 10 while(i!=j){ //该while的功能为每一趟进行的多次比较和交换最终找到位置k。当i==j时意味着找到k位置了 11 while(a[j]>=jizhun&&i<j){j--} //只要大于等于基准数,j指针向左移动直到小于基准数才不进入该while。i<j的限制条件也是很重要,不然一直在i!=j这个循环里,j也会越界 12 while(a[i]<=jizhun&&i<j){i++} //只要小于等于基准数,i指针向右移动直到大于基准数才不进入该while。等于条件也是必要的,举例[4,7,6,4,3]验证一下是两个4交换 13 if(i<j){ //如果i==j跳出外层while 14 t=a[i]; 15 a[i]=a[j]; 16 a[j]=t 17 } 18 } 19 20 a[left]=a[i];//交换基准数和k位置上的数 21 a[i]=jizhun; 22 23 quicksort(a,left,i-1); 24 quicksort(a,i+1,right); 25 } 26 27 var array=[4,7,2,8,3,9,12]; 28 quicksort(array,0,array.length-1);//排完序后再看array是[2, 3, 4, 7, 8, 9, 12]
性能测试:在jsperf整了个链接测试一下冒泡,快排,sort()对100个数字排序的性能,http://jsperf.com/sortdemo 果然还是sort()原生方法快。
在chrome控制台统计代码执行的时间,也同样验证了sort()原生方法快:
参考:http://developer.51cto.com/art/201403/430986.htm