算法導(dǎo)論公開課筆記(四)順序統(tǒng)計、中值

順序統(tǒng)計

問題場景:給定具有n個元素的數(shù)組,已知數(shù)組是無序的,請找到第k小的元素并返回該元素(TOP K問題)。
根據(jù)之前所學(xué)的算法我們可以得出一個原始方案:
使用運行時間為Θ (nlgn)的排序算法(堆排序、歸并排序)進(jìn)行排序后返回數(shù)組索引為K的元素。
該問題當(dāng)K為如下特殊值的時候的情形:

  • K=1 : 最小值
  • K=n : 最大值
  • K=(n-1)/2: 中值


    k值表示的不同意義

隨機(jī)化分治法

  • 隨機(jī)化選擇法

用到了前面講的隨機(jī)化快速排序的子操作randomizedPartition

方法的大致運行流程是這樣的:
如果開始位置和結(jié)束位置一樣時算法視為找到,返回開始位置的元素;否則進(jìn)行隨機(jī)化分割,當(dāng)分割后的主元位置和需要查找的第index小的位置對應(yīng)時視為找到,當(dāng)前主元就是需要查找的元素。否則,將問題劃分為更小規(guī)模的問題,如果主元位置p>index那么在[left,p]范圍內(nèi)的數(shù)據(jù)進(jìn)行隨機(jī)化選擇操作;否則從主元的右側(cè)進(jìn)行隨機(jī)話選擇操作。方法介紹完畢,需要注意的是不同的分割導(dǎo)致index的變化是一個需要關(guān)注的點。

    package me.ziuo.ai.intro;

import java.util.Random;

/**
 *            
 *             {48,6,57,88,60,42,83,73,49,85,99,1424,35,242,6567,34,2,5};
 *             
 *             ------------------------------------------------
 *             隨機(jī)化選擇第 index 小的元素
 *             top 1 is 2.
 *             top 2 is 5.
 *             top 3 is 6.
 *             top 4 is 34.
 *             top 5 is 35.
 *             top 6 is 42.
 *             top 7 is 48.
 *             top 8 is 49.
 *             top 9 is 57.
 *             top 10 is 60.
 *             top 11 is 73.
 *             top 12 is 83.
 *             top 13 is 85.
 *             top 14 is 88.
 *             top 15 is 99.
 *             top 16 is 242.
 *             top 17 is 1424.
 *             top 18 is 6567.
 *          
 * @author eboy
 *
 * @description 隨機(jī)化的選擇算法,用于求解top k 問題
 **/
public class RandomizedSelect {
    
    /**
     * @return 隨機(jī)化主元分割點位置
     * 
     * @param src
     *            待劃分的數(shù)組,分割后的結(jié)果保證,mid前的元素都是小于等于它的,右側(cè)的元素都是大于等于它的
     * @param left
     *            左邊界
     * @param right
     *            右邊界   
     */
    private static int randomizedPartition(int[] src, int left, int right) {

        /*隨機(jī)選取主元元素*/
        Random random = new Random();
        int random_index = random.nextInt(right-left+1)+left;
        
        //感興趣的話,可以打印一下主元的位置
//      System.out.println("random_index="+random_index);
    
        /**
         * 交換
         */
        int temp = src[random_index];
        src[random_index] = src[left];
        src[left]=temp;
        
        int key = src[left];//挖坑
        int i = left;
        int j = right;
        while (i < j) {
            while (i < j && src[j] > key) {
                j--;
            }
            
            if (i < j) {
                src[i++] = src[j];// 挖坑填數(shù)
            }

            while (i < j && src[i] < key) {
                i++;
            }
            
            if (i < j) {
                src[j--] = src[i];// 挖坑填數(shù)
            }

        }
        src[i] = key;//填空

        // 這種情況下第一趟結(jié)束 i的坐標(biāo)都比他小i的右邊都比他大
        return i;

    }
    
    /**
     * 
     * @param src
     * @param left
     * @param right
     * @param index
     * @return 返回 第 index 小的元素
     */
    public static int  randomizedSelect(int[] src, int left, int right, int index) {
        
        if(left==right) return src[left];//待分割的邊界到只有一個元素時,該元素作為結(jié)果返回
        
        int mid =randomizedPartition(src, left, right);//隨機(jī)化分割后,主元所在位置
        
        int key=mid-left+1;
        
        if(index==key) return src[mid];
        else if(index<key) return randomizedSelect(src, left, mid-1, index);
        else return randomizedSelect(src, mid+1, right, index-key);//top i問題分解成top i-key
        
    }

}

該算法的最壞情況的運行時間是Θ(n2);期望運行時間為Θ(n)。

雖然,隨機(jī)化選擇算法的期望運行時間是線性的,但是出現(xiàn)最壞的情況的話效率很低,盡管出現(xiàn)最壞的情況的概率是極其低的。

最終人類還是創(chuàng)造出最壞情況為線性的選擇算法。

最壞情況為線性的選擇算法

選擇算法概述

該算法是對隨機(jī)化選擇主元的優(yōu)化版本,最壞情況的運行時間是線性的即O(n)。和隨機(jī)化的選擇排序一樣,快速選擇算法通過對輸入數(shù)組的遞歸劃分找出所需的元素,但是該算法那能給個保證得到對數(shù)組的一個好的劃分。快速選擇算法使用的也是來自快速排序的確定性劃分算法PARTITION,做了部分修改,將劃分的主元也作為輸入?yún)?shù)。
代碼如下:

package me.ziuo.ai.intro;

import java.util.Random;

/**
 *            
 *             {7,9,8,6,3,5,2,4,1
                ,18,10,15,17,10,12,19,14,13
                ,34,58,79,21,16,53,23,22,11
                ,23,48,29,16,55,37,28,51,50
                ,22,18,27,35,53,27,96,88,101}
 *             
 *             ------------------------------------------------
 *             最壞情況運行時間為線性的選擇第 index 小的元素
 *             top 1 is 1.
 *             top 2 is 2.
 *             top 3 is 3.
 *             top 4 is 4.
 *             top 5 is 5.
 *             top 6 is 6.
 *             top 7 is 7.
 *             top 8 is 8.
 *             top 9 is 9.
 *             top 10 is 10.
 *             top 11 is 10.
 *             top 12 is 11.
 *             top 13 is 12.
 *             top 14 is 13.
 *             top 15 is 14.
 *             top 16 is 15.
 *             top 17 is 16.
 *             top 18 is 16.
 *             top 19 is 17.
 *             top 20 is 18.
 *             top 21 is 18.
 *             top 22 is 19.
 *             top 23 is 21.
 *             top 24 is 22.
 *             top 25 is 22.
 *             top 26 is 23.
 *             top 27 is 23.
 *             top 28 is 27.
 *             top 29 is 27.
 *             top 30 is 28.
 *             top 31 is 29.
 *             top 32 is 34.
 *             top 33 is 35.
 *             top 34 is 37.
 *             top 35 is 48.
 *             top 36 is 50.
 *             top 37 is 51.
 *             top 38 is 53.
 *             top 39 is 53.
 *             top 40 is 55.
 *             top 41 is 58.
 *             top 42 is 79.
 *             top 43 is 88.
 *             top 44 is 96.
 *             top 45 is 101.
 *          
 * @author eboy
 *
 * @description 確定性劃分算法的選擇算法,用于求解top k 問題
 **/
public class Select {
    
     int[] midianArray;//中位數(shù)數(shù)組

    
    /**
     * @return 隨機(jī)化主元分割點位置
     * 
     * @param src
     *            待劃分的數(shù)組,分割后的結(jié)果保證,mid前的元素都是小于等于它的,右側(cè)的元素都是大于等于它的
     * @param left
     *            左邊界
     * @param right
     *            右邊界   
     */
    private  int partition(int[] src, int left, int right,int pivotIndex) {


        /**
         * 交換
         */
        int temp = src[pivotIndex];
        src[pivotIndex] = src[left];
        src[left]=temp;
        
        int key = src[left];//挖坑
        int i = left;
        int j = right;
        while (i < j) {
            while (i < j && src[j] > key) {
                j--;
            }
            
            if (i < j) {
                src[i++] = src[j];// 挖坑填數(shù)
            }

            while (i < j && src[i] < key) {
                i++;
            }
            
            if (i < j) {
                src[j--] = src[i];// 挖坑填數(shù)
            }

        }
        src[i] = key;//填空

        // 這種情況下第一趟結(jié)束 i的坐標(biāo)都比他小i的右邊都比他大
        return i;

    }
    
    
    /**
     *  最壞情況運行時間為線性的選擇排序算法的入口方法
     * @param src
     * @param left
     * @param right
     * @param key
     * @return
     */
    public int qSelect(int[] src, int left, int right, int key){
        midianArray=new int[src.length/5 + 1];
        
        return select(src, left, right, key);
    }

    /**
     * 
     * @param src
     * @param left
     * @param right
     * @param key
     * @return 返回 第 index 小的元素
     */
    private  int  select(int[] src, int left, int right, int key) {
        
        
        /**
         * 1.尋找中位數(shù)的中位數(shù)
         * 2.進(jìn)行遞歸找到第k小的元素
         */
        
        int median=findMedian(src,left,right);
//      System.out.println("median is "+median);

        int medianIndex=findMedianIndex(src, left, right, median);
//      System.out.println("medianIndex is "+medianIndex);

        int pivotIndex =partition(src, left, right,medianIndex);//根據(jù)主元進(jìn)行劃分,返回主元劃分后的位置
        
        int index=pivotIndex-left+1;
        
        if(key==index) return src[pivotIndex];
        else if(key<index) return select(src, left, pivotIndex-1, key);
        else return select(src, pivotIndex+1, right, key-index);//top i問題分解成top i-key
        
    }
    
    private int findMedianIndex(int[] src ,int left,int right,int median){
        
        for(int i=left;i<=right;i++){
            if(src[i]==median) 
                return i;
        }
        return -1;
    }
    

    private  int findMedian(int[] src, int left, int right) {

        if(left==right) return src[left];//范圍內(nèi)只有一個元素時,該元素作為結(jié)果返回

        /**
         * 1.元素分組:將輸入數(shù)組中的n個元素劃分為【n/5】個中位數(shù),且至多只有一組由剩下的n mod 5個元素組成。
         * 2.尋找【n/5】個中位數(shù):首先對沒組元素進(jìn)行插入排序,然后確定每組元素的中位數(shù)。
         * 3.找到中位數(shù):找出中位數(shù)的中位數(shù)。
         */
        
        //1.元素分組
        //2.尋找【n/5】個中位數(shù)
        int index;
        for(index=left;index<right-5;index+=5){//分組整除部分的數(shù)據(jù)
            insertSort(src,index,4);
            int num=index - left;
            midianArray[num/5]=src[index+2];//中位數(shù)賦值
        }
        //分組余下的元素
        int remainNum=right-index+1;
        if(remainNum>0){
            insertSort(src,index,remainNum-1);
            int num=index - left;
            midianArray[num/5]=src[index+remainNum/2];//中位數(shù)賦值
        }
        //3. 遞歸找到中位數(shù)的中位數(shù)
        int elemAuxArray=(right-left)/5-1;
        if((right-left)%5!=0) elemAuxArray++;
        
        if(elemAuxArray==0) 
            return midianArray[elemAuxArray];
        else                
            return findMedian(midianArray,0,elemAuxArray);
    }


    /**
     * 子數(shù)組插入排序
     * @param src 母數(shù)組
     * @param left 開始為止
     * @param loopLength 步長
     */
    private  void insertSort(int[] src, int left, int loopLength) {

        for(int j=left; j<left+loopLength;j++){
            int key=src[j];
            int i=j-1;
            while(i>left && src[i]>key){
                src[i+1]=src[i];
                i--;
            }
            src[i+1]=key;
        }
    }

}

測試代碼如下:

public static void main(String[] args) {
        // TODO Auto-generated method stub

        int[] arr ={48,6,57,88,60,42,83,73,49,85};

        /**
         *  random_index=9
         *  random_index=3
         *  random_index=1
         *  random_index=2
         *  random_index=6
         *   =4
         *  random_index=5
         *  sorted is {6,42,48,49,57,60,73,83,85,88}
         */
        
        System.out.println("------------------------------------------------");
        System.out.println("隨機(jī)化選快排的運算過程");

        RandomizedQuickSort.randomizedQuickSort(arr,0, arr.length-1);
        for(int i=0;i<arr.length;i++){
            if(i==arr.length-1) System.out.print(arr[i]);
            else System.out.print(arr[i]+",");
        }
        
        System.out.println("");
        System.out.println("------------------------------------------------");
        System.out.println("隨機(jī)化選擇第 index 小的元素");
        
        /**
         * 隨機(jī)化選擇第 index 小的元素
         */
        int[] arr2 ={48,6,57,88,60,42,83,73,49,85,99,1424,35,242,6567,34,2,5};

        for(int i=0;i<arr2.length;i++){
            int indexMin=RandomizedSelect.randomizedSelect(arr2, 0, arr2.length-1, i+1);
            System.out.println("top "+(i+1)+" is "+indexMin+".");
        }

        System.out.println("------------------------------------------------");
        System.out.println("最壞情況運行時間為線性的選擇第 index 小的元素");

        /**
         * 最壞情況運行時間為線性的選擇第 index 小的元素
         */
        int[] arr3 ={7,9,8,6,3,5,2,4,1
                ,18,10,15,17,10,12,19,14,13
                ,34,58,79,21,16,53,23,22,11
                ,23,48,29,16,55,37,28,51,50
                ,22,18,27,35,53,27,96,88,101};
        for(int i=0;i<arr3.length;i++){
            Select select=new Select();
            int indexMin=select.qSelect(arr3, 0, arr3.length-1, i+1);
            System.out.println("top "+(i+1)+" is "+indexMin+".");
        }

    }

控制臺打印

------------------------------------------------
隨機(jī)化選快排的運算過程
random_index=4
random_index=1
random_index=3
random_index=2
random_index=2
random_index=8
random_index=8
6,42,48,49,57,60,73,83,85,88
------------------------------------------------
隨機(jī)化選擇第 index 小的元素
top 1 is 2.
top 2 is 5.
top 3 is 6.
top 4 is 34.
top 5 is 35.
top 6 is 42.
top 7 is 48.
top 8 is 49.
top 9 is 57.
top 10 is 60.
top 11 is 73.
top 12 is 83.
top 13 is 85.
top 14 is 88.
top 15 is 99.
top 16 is 242.
top 17 is 1424.
top 18 is 6567.
------------------------------------------------
最壞情況運行時間為線性的選擇第 index 小的元素
top 1 is 1.
top 2 is 2.
top 3 is 3.
top 4 is 4.
top 5 is 5.
top 6 is 6.
top 7 is 7.
top 8 is 8.
top 9 is 9.
top 10 is 10.
top 11 is 10.
top 12 is 11.
top 13 is 12.
top 14 is 13.
top 15 is 14.
top 16 is 15.
top 17 is 16.
top 18 is 16.
top 19 is 17.
top 20 is 18.
top 21 is 18.
top 22 is 19.
top 23 is 21.
top 24 is 22.
top 25 is 22.
top 26 is 23.
top 27 is 23.
top 28 is 27.
top 29 is 27.
top 30 is 28.
top 31 is 29.
top 32 is 34.
top 33 is 35.
top 34 is 37.
top 35 is 48.
top 36 is 50.
top 37 is 51.
top 38 is 53.
top 39 is 53.
top 40 is 55.
top 41 is 58.
top 42 is 79.
top 43 is 88.
top 44 is 96.
top 45 is 101.

算法系列文章,因為水平所限,只做記錄,不做過多介紹。

以上,謝謝閱讀,希望你有所收獲!

以上,謝謝閱讀,希望你有所收獲!

算法導(dǎo)論公開課筆記(一)算法分析與設(shè)計
算法導(dǎo)論公開課筆記(二)快速排序和隨機(jī)化算法
算法導(dǎo)論公開課筆記(三)線性時間排序
算法導(dǎo)論公開課筆記(四)順序統(tǒng)計、中值
動態(tài)規(guī)劃算法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容