简单排序算法实现——快速排序
2013-01-31 15:46
288 查看
快速排序 (Quick Sort) 是使用“分治”思想的又一个排序算法,通过将序列不断分为子序列,最终达到单元素情况来解决问题。
其大致实现过程是从待排序序列中挑选出一个主元 (pivot element,又叫枢纽元),然后将剩下的元素分割成两个部分,一个部分是小元素部分,其中所有的元素都比主元小;另一个是大元素部分,其中所有的元素都比主元大。在这一步分为小元素部分与大元素部分的过程中,每个部分暂不要求有序。然后对两个子序列再递归使用快速排序,从而最终解决问题。
在代码实现中,主要分为以下几个部分:1.暴露在外的public驱动方法QuickSort。2.实现递归的private方法QuickSOrt。3.在递归部分的一开始被调用的Partition分割方法。
首先给出驱动方法代码如下:
在该驱动方法中,调用递归方法,参数中指定了需要递归处理的部分是0~a.length-1,即整个数组部分。
接下来给出处理递归的方法QuickSort:
在该段代码中截止判断条件是 left<right,当该条件不再满足时,有left==right,表明分割已经到了单元素阶段,递归停止并退回。
pivotPos指示在Partition方法完成后的主元的位置,该位置用于标明下一步的递归的范围是 left~pivotPos-1, pivotPos+1~right。
以下给出Partition方法:
简述一下对于pivot的选取。一种简单的实现是不作特殊选择,默认使用最后一个元素作为主元。这样易于编程,但是却不太适合,尤其当数据已经是预排序过的,或者逆排序的
时候,这种选取将造成分割的不平衡,而分割的平衡性是快速排序算法的性能保证。在分割最不平衡的最坏情况下,即一个子序列只有一个元素,另一个有n-1个元素,如果该情况持续出现的话,算法时间是O(n^2),效率很低。
另一种选取的方法是随机选取,该种选取方法在实践中可以较好的保持平衡性,但是随机数的生成本身开销较大,在此也不采用。
这里采用的是三数中值分割法(Median-of-Three Partitioning),通过left,center,right三个数的中值来作为主元。经过三次交换,三个值将有序排列,此时center的元素必然是中值,即为主元,将它交换到数列的末尾。
代码中的pos指向小元素区的最后一个元素,通过这个下标的分割,既可分开小元素区与大元素区。
循环下标 j 将遍历除主元外的每一个元素。如果该元素比主元大,不作操作,只将 j 加一推向下一个位置。如果该元素比主元小,则将 i 的值先加一推进,然后将 i 现在指向的实际是比主元大的值与 j 指向的当前操作值交换,继而保证了分割的正确性。
在 j 遍历完成以后,分割也就完成了,然后将主元交换到中间,即与大元素区的第一个元素,即pos+1的位置的元素交换。最后partition方法返回主元的位置pos+1。
最后附上用于测试的main方法,整个程序可以正确运行:
探讨一下快速排序与归并排序的联系与区别:
1.两者都是采用了分治的递归思想,平均算法复杂度相同。
2.归并排序是先分割,在分割的时候只须简单的一份为二,分的时候不要求有序,在整个的分割完成后合并的时候通过精心的合并过程来达到有序,因此是先分后治,治中求序的过程;快速排序也是先分割,但是并不是简单的分割,而是通过选择分割的方法,在分割的时候就保证有序性,相反在合并的时候已经没有实质性的操作,只是简单的退出递归即可,因此是在分的时候就消除了逆序。
3.归并排序保证每次都是一分为二,因此其性能是有保证的。快速排序的分割并不要求平均,实际上分割是依赖于主元的选取的,因此主元的选取是会影响整体性能的。
4.在以上给出的算法中,实现的是快速排序的原地排序版本(in-place),故无需额外空间,但是牺牲了稳定性。相对的,归并排序需要O(n)的额外空间,空间性能较差。
关于时间复杂度:
平均时间复杂度是O(nlogn)
最优时间复杂度是O(nlogn)
最差时间复杂度是O(n^2)
附图一张,以便理解记忆:
其大致实现过程是从待排序序列中挑选出一个主元 (pivot element,又叫枢纽元),然后将剩下的元素分割成两个部分,一个部分是小元素部分,其中所有的元素都比主元小;另一个是大元素部分,其中所有的元素都比主元大。在这一步分为小元素部分与大元素部分的过程中,每个部分暂不要求有序。然后对两个子序列再递归使用快速排序,从而最终解决问题。
在代码实现中,主要分为以下几个部分:1.暴露在外的public驱动方法QuickSort。2.实现递归的private方法QuickSOrt。3.在递归部分的一开始被调用的Partition分割方法。
首先给出驱动方法代码如下:
//This public method is used as the driven method to actually invoke //the private QuickSort method public static <AnyType extends Comparable<? super AnyType>> void QuickSort(AnyType[] a){ //Invoke private QuickSort method, ranging from start to end. QuickSort(a,0,a.length-1); }
在该驱动方法中,调用递归方法,参数中指定了需要递归处理的部分是0~a.length-1,即整个数组部分。
接下来给出处理递归的方法QuickSort:
private static <AnyType extends Comparable<? super AnyType>> void QuickSort(AnyType[] a, int left, int right){ //This is the cut-off condition which if unsatisfied indicates //the partition has already gone to a singe-element stage and can thus stopping. if(left<right){ int pivotPos; //Partition method will separate the array, //and also return an Integer indicating the position of pivot. //This position of pivot will be used for further QuickSort. pivotPos=Partition(a,left,right); QuickSort(a,left,pivotPos-1); QuickSort(a,pivotPos+1,right); } }
在该段代码中截止判断条件是 left<right,当该条件不再满足时,有left==right,表明分割已经到了单元素阶段,递归停止并退回。
pivotPos指示在Partition方法完成后的主元的位置,该位置用于标明下一步的递归的范围是 left~pivotPos-1, pivotPos+1~right。
以下给出Partition方法:
private static <AnyType extends Comparable<? super AnyType>> int Partition(AnyType[] a,int left,int right){ //Use Median-of-three partitioning method. //After three times swaping, a[left],a[center],[aright] are placed orderly //So a[center] must be the mean value, it's swaped to the end of the array. int center=(left+right)/2; AnyType temp; if(a[left].compareTo(a[right])>0){temp=a[left];a[left]=a[right];a[right]=temp;} if(a[left].compareTo(a[center])>0){temp=a[left];a[left]=a[right];a[right]=temp;} if(a[center].compareTo(a[right])>0){temp=a[center];a[center]=a[right];a[right]=temp;} temp=a[center]; a[center]=a[right]; a[right]=temp;//Now, a[right] is the pivot element. int pos=left-1;//Indicate the last element in small part. for(int j=left;j<right;j++){ if(a[j].compareTo(a[right])<0){ pos++; temp=a[pos]; a[pos]=a[j]; a[j]=temp; } } //We swap the pivot element back between the small part and large part. //That position is pos+1. temp=a[pos+1]; a[pos+1]=a[right]; a[right]=temp; //We need to return the position of the pivot element. return pos+1; }
简述一下对于pivot的选取。一种简单的实现是不作特殊选择,默认使用最后一个元素作为主元。这样易于编程,但是却不太适合,尤其当数据已经是预排序过的,或者逆排序的
时候,这种选取将造成分割的不平衡,而分割的平衡性是快速排序算法的性能保证。在分割最不平衡的最坏情况下,即一个子序列只有一个元素,另一个有n-1个元素,如果该情况持续出现的话,算法时间是O(n^2),效率很低。
另一种选取的方法是随机选取,该种选取方法在实践中可以较好的保持平衡性,但是随机数的生成本身开销较大,在此也不采用。
这里采用的是三数中值分割法(Median-of-Three Partitioning),通过left,center,right三个数的中值来作为主元。经过三次交换,三个值将有序排列,此时center的元素必然是中值,即为主元,将它交换到数列的末尾。
代码中的pos指向小元素区的最后一个元素,通过这个下标的分割,既可分开小元素区与大元素区。
循环下标 j 将遍历除主元外的每一个元素。如果该元素比主元大,不作操作,只将 j 加一推向下一个位置。如果该元素比主元小,则将 i 的值先加一推进,然后将 i 现在指向的实际是比主元大的值与 j 指向的当前操作值交换,继而保证了分割的正确性。
在 j 遍历完成以后,分割也就完成了,然后将主元交换到中间,即与大元素区的第一个元素,即pos+1的位置的元素交换。最后partition方法返回主元的位置pos+1。
最后附上用于测试的main方法,整个程序可以正确运行:
public static void main(String[] args){ System.out.println("Quick Sort:"); Integer[] elements={3,4,1,8,10,2,0,6,5}; System.out.print("Original elements: "); for(int i=0;i<elements.length;i++) System.out.print(elements[i]+" "); System.out.println(); QuickSort(elements); System.out.print("After sorting: "); for(int i=0;i<elements.length;i++) System.out.print(elements[i]+" "); System.out.println(); }
探讨一下快速排序与归并排序的联系与区别:
1.两者都是采用了分治的递归思想,平均算法复杂度相同。
2.归并排序是先分割,在分割的时候只须简单的一份为二,分的时候不要求有序,在整个的分割完成后合并的时候通过精心的合并过程来达到有序,因此是先分后治,治中求序的过程;快速排序也是先分割,但是并不是简单的分割,而是通过选择分割的方法,在分割的时候就保证有序性,相反在合并的时候已经没有实质性的操作,只是简单的退出递归即可,因此是在分的时候就消除了逆序。
3.归并排序保证每次都是一分为二,因此其性能是有保证的。快速排序的分割并不要求平均,实际上分割是依赖于主元的选取的,因此主元的选取是会影响整体性能的。
4.在以上给出的算法中,实现的是快速排序的原地排序版本(in-place),故无需额外空间,但是牺牲了稳定性。相对的,归并排序需要O(n)的额外空间,空间性能较差。
关于时间复杂度:
平均时间复杂度是O(nlogn)
最优时间复杂度是O(nlogn)
最差时间复杂度是O(n^2)
附图一张,以便理解记忆:
相关文章推荐
- 排序算法(C实现)--------- 快速排序
- JS实现快速排序
- 快速排序代码实现_我来找茬
- java实现快速排序、插入排序、选择排序、冒泡排序算法
- 快速排序C++实现
- 快速排序_C语言实现
- 数据结构实现(插入排序、快速排序、统计排序类模板)
- 数据结构与算法(7)---Java语言实现:快速排序
- 快速排序的非递归实现
- 算法代码实现之快速排序,C/C++实现
- Java下实现快速排序
- 快速排序Java实现
- 爱创课堂每日一题第二十七天-2017/9/29 快速 排序的思想并实现一个快排?
- 快速排序和冒泡排序的时间复杂度分析(C++算法实现对比)
- 快速排序分析与C语言实现
- 【算法】快速排序——基于分治思想的实现
- C++学习 - 快速排序,更加优化的实现
- 快速排序:升序+降序----java实现
- Java中快速排序,冒泡排序和选择排序的实现
- c++实现冒泡,选择,插入,快速排序