可以把查找中位数看成是一种特殊的选择:在数组中查找第k个小或大的元素。我们可以回想前面《快速排序及改进》中的partition(Comparable[] a,int lo,int hi)方法,它会将数组的a[lo]到a[hi]重新排列(局部排序)并返回一个整数j,使得:
a[lo ... j - 1] <= a[j] <= a[j + 1 ... hi]
那么,要获取第K小的值,当k = j时,a[j]就是要求解的数值了。
/** *查找一个数组中的第k小的元素 */ public static Comparable select(Comparable[] a,int k){ //对数组a洗牌 shuffle(a); int lo = 0; int hi = a.length - 1; int j = 0; while(hi > lo){ j = partition(a,lo,hi - 1); if(j == k){ return a[k]; }else if(j > k){ hi = j - 1; }else{ lo = j + 1; } } return a[k]; }
对于查找中位数,就是k = N/2时的a[j],a[j]就是要求的中位数。整个代码如下:
/** * 取中位数 */ public class MedianFinder extends SortBase{ /** * 局部排序,查找数组a的中位数 * @param a * @return */ public static Comparable find(Comparable[] a){ int len = a.length; int mid = 0; if((isOdd(len)){//奇数 mid = (len + 1)/2; }else{ mid = len/2; } return select(a,mid - 1); } /** * 判断整数i是否是奇数 * @param i * @return true:奇数,false:偶数 */ private static isOdd(int i){ return (k & 1) != 0; } /** * 查找数组a中第k小的元素 * @param a * @param k * @return */ public static Comparable select(Comparable[] a,int k){ shuffle(a); Comparable val = null; int lo = 0; int hi = a.length - 1; int j = 0; while(hi > lo){ j = partition(a,lo,hi); if(j == k){ val = a[k]; break; }else if(j > k){ hi = j - 1; }else{ lo = j + 1; } } return val; } /** * 在数组a的下标lo和hi范围内获取快速切分的基数的小标,使该基数的左边的 * 元素都不大于该数,而右边的元素都不小于该数 * @param a * @param lo * @param hi * @return 快速切分基数的下标 */ public static int partition(Comparable[] a,int lo,int hi){ //将数组切分为a[lo...i - 1],a[i],a[j + 1,hi] int i = lo,j = hi + 1; //设置左右扫描指针 Comparable v = a[lo]; //切分元素 while(true){ while(less(a[++i],v)){ if(i == hi) break; } while(less(v,a[--j])){ if(j == lo) break; } if(i >= j){ break; } exch(a,i,j); } exch(a,lo,j); //将v = a[i]放入正确的位置 //a[lo...j - 1] <= a[j] <= a[j + 1 ... hi] return j; } }