快速排序算法的原理
快速排序算法是基于分治策略的另一个排序算法。其基本思想是,对于输入的子数组alp:r],按以下三个步骤进行排序。
①分解(Divide):以a[p]为基准元素将alp:r]划分成3段alp:q-1],a[q]和 a[q+1:r],使a[p:q-1]中任何一个元素小于等于a[q],而 a[q+1:r]中任何一个元素大于等于a[q]。下标q在划分过程中确定。
②递归求解(Conquer):通过递归调用快速排序算法,分别对ap:q-1]和 alq+1:r]进行排序。
合并(Merge):由于对'a[p:q-1]和 a[q+1:r]的排序是就地进行的,因此在 alp:q-1]和a[q+1:r]都已排好的序后,不需要执行任何计算,a[p:r]则已排好序。
基于这个思想,可实现快速排序算法如下:
private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) {
if (l >= r) {
return;
}
int p = partition(arr, l, r);
sort(arr, l, p - 1); // 对左半段排序
sort(arr, p + 1, r); // 对右半段排序
}
对含有n个元素的数组a[0:n-1]进行快速排序只要调用QuickSort(a,0,n-1)即可。
上述算法中的函数Partition()以一个确定的基准元素a[p]对子数组 a[p:r]进行划分,它是快速排序算法的关键。
private static <E extends Comparable<E>> int partition(E[] arr, int l, int r) {
// swap(arr,l,(l+r)/2); 将中间位置的元素和开始位置的元素做交换,作为基准
// arr[l+1..j] < v; arr[j+1...i]>= v
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i].compareTo(arr[l]) < 0) {
j++;
swap(arr, i, j);
}
}
swap(arr, l, j);
return j;
}
交换的代码
private static <E> void swap(E[] arr, int i, int j) {
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
上述为单路的快速排序,对于该快速排序,当数组中的元素本身已经有序的时,假设以第一个元素作为基准,一轮排序结束,待排序的元素只少了一个,如下所示:
此时时间复杂度为O(n^2) 递归深度为O(n)
容易看到,快速排序算法的性能取决于划分的对称性。通过修改函数Partition(),可以设计出采用随机选择策略的快速排序算法。在快速排序算法的每步中,当数组还没有被划分时,可以在a[p:sr]中随机选出一个元素作为划分基准,这样可以使划分基准的选择是随机的,从而可以期望划分是较对称的。随机化的划分算法可实现如下:
只需要在partition函数里面,增加生成随机数的方法即可。
双路排序
之前说的快速排序算法是将>v和<v两个部分元素都放在索引值i所指向的位置的左边部分,而我们
的双路快速排序算法则不同,他使用两个索引值(i、j)用来遍历我们的序列,将<v的元素放在索
引i所指向位置的左边,而将>v的元素放在索引j所指向位置的右边,这也正是双路排序算法的
partition原理。
import java.util.Arrays;
import java.util.Random;
public class QuickSort {
private static Random random;
private QuickSort() {
}
public static <E extends Comparable<E>> void sort2ways(E[] arr) {
random = new Random();
sort2ways(arr, 0, arr.length - 1);
}
private static <E extends Comparable<E>> void sort2ways(E[] arr, int l, int r) {
if (l >= r) {
return;
}
int p = partition2(arr, l, r);
sort2ways(arr, l, p - 1);
sort2ways(arr, p + 1, r);
}
private static <E extends Comparable<E>> int partition2(E[] arr, int l, int r) {
// 生成[l,r]之间的随机索引
int p = l+random.nextInt(r-l+1);
swap(arr,l,p);
// arr[l+1...i-1]<= v;arr[j+1...r]>=v
int i = l+1,j = r;
while (true){
while (i<=j && arr[i].compareTo(arr[l])<0)
i++;
while (j>=i && arr[j].compareTo(arr[l])>0)
j--;
if (i>=j)
break;
swap(arr,i,j);
i++;
j--;
}
swap(arr,l,j);
return j;
}
}
三路排序
将整个数组按照划分值v,分成小于v的区域称为小于区,等于v的区域等于区和大于v的区域大于区三部分,在下一次的递归中就不必再处理等于v的等于区,因为等于区的元素已经到达了最终位置,对于存在大量等于v的数组三路快排大大提升了效率。
/**
* 三路快速排序
* @param arr
* @param <E>
*/
public static <E extends Comparable<E>> void sort3ways(E[] arr) {
Random rnd = new Random();
sort3ways(arr, 0, arr.length - 1,rnd);
}
private static <E extends Comparable<E>> void sort3ways(E[] arr, int l, int r,Random rnd) {
if (l >= r) {
return;
}
// 生产 [l,r]直接的随机数
int p = l + rnd.nextInt(r-l+1);
swap(arr,p,l);
// arr[l+1,lt]< v ,arr[lt+1,i-1] == v , arr[gt,r] >v
int lt = l,i = l+1,gt = r+1;
while (i<gt){
if (arr[i].compareTo(arr[l])<0){
lt++;
swap(arr,i,lt);
i++;
}else if (arr[i].compareTo(arr[l])>0){
gt--;
swap(arr,i,gt);
}else{ // arr[i]==arr[l]
i++;
}
}
swap(arr,l,lt);
// arr[l,lt-1] < v ,arr[lt,gt-1] == v ,arr[gt,r]>v
sort3ways(arr,l,lt-1,rnd);
sort3ways(arr,gt,r,rnd);
}
三路快速排序,对于数组中有大量重复的元素,更有优势。