前几天总结了一下几种常用的排序算法,而且,在比较排序算法的结尾有一个小tips:堆排序和归并排序都是渐进最有的比较排序算法。今天在回顾算法导论的过程中发现,堆排序,快排,归并排序的应用很广泛,重要的不是这种排序的方式,而是这种排序的思想值得我们学习。
以快排为例,这种算法思想是可以展开的。
首先看一下快排的代码:
public class QuickSort {
public static void sort(int []a ,int lo,int hi) {
if(hi<=lo)return ;
int j=partition(a,lo,hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
}
private static int partition(int[] a, int lo, int hi) {
int i=lo;
int j=hi+1;
int v=a[lo];
while(true) {
while(a[++i]<v) {
if(i==hi) {
break;
}
}
while (v<a[--j]) {
if (j==lo) {
break;
}
}
if (i>=j){
break;
}
swap(a,i,j);
}
swap(a, lo, j);
return j;
}
private static void swap(int[] a, int i, int j) {
// TODO Auto-generated method stub
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
主要是一个划分的思想。
以我当前遇到的题目来看,有
1.荷兰国旗问题:荷兰国旗有三横条块构成,自上到下的三条块颜色依次为红、白、蓝。现有若干由红、白、蓝三种颜色的条块序列,要将它们重新排列使所有相同颜色的条块在一起。本问题要求将所有红色的条块放最左边、所有白色的条块放中间、所有蓝色的条块放最右边。
/*
* 现有红白蓝三个不同颜色的小球,乱序排列在一起,请重新排列这些小球,使得红白蓝三色的同颜色的球在一起。
* 这个问题之所以叫荷兰国旗问题,是因为我们可以将红白蓝三色小球想象成条状物,有序排列后正好组成荷兰国旗。
*/
public class NetherlandsFlag {
public static int[] partation(int arr[], int l, int r, int p) {
int less = l - 1;
int more = r + 1;
while (l < more) {
if (arr[l] < p) {
swap(arr, ++less, l++);
} else if (arr[l] > p) {
swap(arr, --more, l);
} else {
l++;
}
}
return new int[] { less + 1, more - 1 };
}
private static void swap(int[] arr, int i, int j) {
// TODO Auto-generated method stub
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
2.随机快排
所谓随机快排,采用一种不同的,称为随机取样的随机化技术。使得分析更加简单。这种方法中,不是始终采用A[r]做为主元,而是从子数组A[p...r]中随机选择一个元素,即将A[r]与A[p...r]中随机选择的一个元素进行交换。在这一修改中,我们是从p,...r这一范围中随机取样的,这么做确保了在子数组的r-p+1个元素中,主元元素x=A[r]等可能的取其中任何一个。因为主元元素是随机选择的,我们期望在平均情况下,对输入数组的划分能够比较对称。
public class RandomQuickSort {
public static void Randomsort(int []a ,int lo,int hi) {
Random random=new Random();
if(hi<=lo)return ;
int i=random.nextInt(hi-lo);
swap(a, lo, lo+i);
int j=partition(a,lo,hi);
Randomsort(a, lo, j-1);
Randomsort(a, j+1, hi);
}
private static int partition(int[] a, int lo, int hi) {
int i=lo;
int j=hi+1;
int v=a[lo];
while(true) {
while(a[++i]<v) {
if(i==hi) {
break;
}
}
while (v<a[--j]) {
if (j==lo) {
break;
}
}
if (i>=j){
break;
}
swap(a,i,j);
}
swap(a, lo, j);
return j;
}
private static void swap(int[] a, int i, int j) {
// TODO Auto-generated method stub
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
3.三路快排问题
改进快速排序性能的第二个办法是使用子数组的一小部分元素的中位数来切分数组。这样做得到的切分最好,但代价是需要计算中位数。人们发现将取样大小设为3,并用大小居中的元素切分的效果最好。我们还可以将取样元素放在数组末尾作为哨兵来去掉partition中的数组边界测试。
思路很简单,从左到右遍历数组依次,维护一个指针lt使得a[lo...lt-1]中的元素都小于v,一个指针gt使得a[gt+1,...,hi]中的元素都大于v。一个指针i使得a[lt,...,i-1]的元素都等于v。a[i,...gt]中的元素还未确定。
public static void sort(int []a ,int lo,int hi) {
if(hi<=lo)return ;
int lt=lo;
int i=lo+1;
int gt=hi;
int v=a[lo];
while (i<=gt) {
int cmp=a[i]-v;
if (cmp<0) {
swap(a, lt++, i++);
}else if (cmp>0) {
swap(a, i, gt--);
}else {
i++;
}
}
sort(a, lo, lt-1);
sort(a, gt+1, hi);
}