取中位数的算法

        找出一个组元素中的中位数(中间值,它不大于一半的元素也不小于另一半的元素),是一个和排序有关但又不需要完全排序的重要应用。查找中位数在统计和许多数据处理中很常见。
        可以把查找中位数看成是一种特殊的选择:在数组中查找第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; 
	   }
 }
        

猜你喜欢

转载自flyingdutchman.iteye.com/blog/1864667