快速排序
来源:互联网 发布:机器人编程入门教程 编辑:程序博客网 时间:2024/05/21 06:02
1.快速排序的描述
快速排序的最坏的情况的时间复杂度为 n的平方,但是期望时间是nlgn,并且他所包含的常数因子非常小,并且能够进行原址排序,因此快排通常是实际应用中最好的排序。快排依旧使用了分治的思想,与归并排序差不多,归并排序不是原址的,要使用辅助空间,原理都是将元数组划分为子数组,归并排序是平分数组,然后再将平分的数组合并排序,而快排则是,选定一个主元(主元的选定方法有很多,直接取最后一个元素,或者是随机抽取数组元素,与最后一个元素交换,或者随机取三个,选取中间的数与最后一个数交换),将数组以主元为边界划分为三部分,如图:
下面是实例:
最终形态分析:
2.快速排序代码
快速排序的关键部分就是如何原址的生成,以主元为边界的三部分,这也是程序的关键,划分好后,对每一个子数组递归调用快排。下面代码展示了三种partition的方法,第一种是《算法导论》上的算法,这种算法比较易懂,另一种是《数据结构》严蔚敏版的算法,其中在第一种上增加了对所有元素都一样的判断,若元素全都一样则返回中间元素的位置。第三种是Hoare算法,是partition的最早算法,主元也是分界线。与第二种算法思想是一样的,但是更精炼。注意:当判断这种情况能否判断出来时,要看看你给出判断条件,是否也包含别的情况。
/** * @param a * @param p * @param r * 快速排序:与归并排序一样都是将数组分成若干个部分: * 这里是分成了3各部分,以最后一个元素为key,以key * 为分界线 左边的部分比key小,key右边的铁元素比key大 * 时间复杂度:最坏n的平方 * 一般情况 nlgn */public void quickSort(int a[],int p,int r) { if(p<r) { // int q=partition(a, p, r); /********************************************/ // if(r-p+1>k)// insertSort(a,p,r); 快速排序的改进算法;当子数组小于k时接近排好序的数组,此时可以 //else 使用插入排序 /*******************************************/ int q=partition2(a, p, r); quickSort(a, p, q-1); quickSort(a, q+1, r); } } /** * @param a * @param p * @param r * 快速排序的关键部分,原址重排,将数组a分成左边比a[r]小右边比a[r]大,然后再将a[r]插入到 * 左边部分的后边(也就是把a[r]插到他应该在的地方) */public int partition(int a[],int p,int r){int key=a[r];int i=p-1;int repeat=0;for(int j=p;j<r;j++){if(a[j]<=key){i=i+1;int temp=a[i];a[i]=a[j];a[j]=temp;}if(a[j]==key){repeat++;}}//!if(i==p-1)当所有元素都重复时返回,中间元素的位置,仅用i==p-1 不能说明都重复,//!return (p+r)/2;//还有前面的元素都比key大所以要排除if(repeat==(p-r))return (p+r)/2;else{i++;int temp=a[i];a[i]=key;a[r]=temp;return i;}}public int partition2(int a[],int low ,int high){int key=a[high];while(low<high){while(low<high&&a[low]<=key) low++;a[high]=a[low];while(low<high&&a[high]>=key) high--;a[low]=a[high];}a[low]=key;return low;} public void insertSort(int a[],int low,int high) { for(int i=low+1;i<=high;i++) { int key=a[i]; int j=i-1; while(j>=0&&a[j]>key) { a[j+1]=a[j]; j--; } a[++j]=key; } }/** * @param a * @param low * @param high * @return * 最早的partition算法 */public int partitionHoare(int a[],int low ,int high){ int key=a[low]; while(true) { while(a[high]>key) high--; while(a[low]<key) low++; if(low<high) { int temp=a[low]; a[low]=a[high]; a[high]=temp; } else return high; }}
3.快速排序的改进算法
可以发现,插入排序在几乎排好序的情况下,时间复杂度是相当低的,接近最好的情况,这时候你可以规定一个k,当子数组的规模等于k时,
进行插入排序。代码在上面已经给出,至于k为多少,就要分析求解了。
4.快速排序的栈深度
上述的快速排序,使用了两个对其自身的调用,其实第二个并不是必须的,可以使用循环控制结构代替它,这一技术叫做尾递归 。第一次调用自身是对第一个子数组的递归调用,以后的循环部分是对另一半子数组的循环,这样也能给你达到递归分割数组的效果。栈深度就是递归的次数, 栈的深度就类似于递归树的深度,当数组正序时,递归的深度为Θ(n),栈的深度也为Θ(n),此时全部1—n-1个元素全纳入递归调用。
例如A={1, 2, ... , n}
<span style="font-size:18px;">/** * @param a * @param p * @param r * 对快速排序 的尾递归算法 */public void tailQuickSort(int a[],int p,int r){while(p<r){int q=partition(a, p, r);tailQuickSort(a, p, q-1);//第一次循环是对前一次的递归p=q+1;//对这一半子数组排序}}</span>
为了使站的深度为lgn 就要尽可能的平分原数组,所以主元的选定要使用所有元素的中位数。
说到寻找中位数,
下面给出两种求解中位数的算法
1.可以使用对数组中每个元素进行迭代,设当前迭代元素为key,找出比key小的数进行计数,如果是n/2,则退出。
2.使用计数排序,这不是比较排序,通过借助辅助空间进行排序。时间复杂度为n;具体参见Beauty Of algorithms(五)
3.可以联想到《编程珠玑》中的寻找第k个小的数(中位数就是第n/2小的数),使用快排的思想,先使用partition进行分区:当N较大时 约等于 2N 也就是 O(N)
1)如果左半部份的长度>K-1,那么这个元素就肯定在左半部份了
2)如果左半部份的长度==K-1,那么当前划分元素就是结果了。
3)如果。。。。。。。<K-1,那么这个元素就肯定在右半部分了。
并且,该方法可以用尾递归实现。效率更高。
快速排序总结:快速排序,能如此之火,也是其充分利用分治的思想,要掌握的不是快排而是这种思想。在处理一个问题时,将其划为许多子问题,关键就是怎么去划分这些子问题,1.利用主元(一个支点)划分为左右两部分 2直接从中间割开。所谓快排的关键就是patition,创建这个分区。就像应用于第k小的数字,归并排序和最大子数组的从中间划分。先 分 再 递归。
- 快速排序
- 快速排序
- 快速排序
- 快速排序!
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- 快速排序
- android初学------根据日期时间字符串返回date数据 获取年月日
- 织梦DedeCMS列表页、内页调用图集多张图片的方法
- <s:if>标签判断的使用
- hdoj problem 1211 RSA(快速幂求模算法)
- 第九周项目五填充一
- 快速排序
- Posix消息队列
- mybatis直接执行sql语句
- 首次开通博客
- Tokumx vs Mongodb
- HDOJ 5067Harry And Dig Machine(状态压缩DP)
- 二叉树的置空、创建、遍历
- 怎样批量新增关键字(看图)
- java web 设计原则之开闭原则