【算】選擇排序和二分查找

大概半個月前,偶爾看到《算法圖解》,沒翻幾頁便被數學戰五渣的我奉爲神書,怎一個相見恨晚、愛不釋手加老淚縱橫啊!遂寫文以做積累……java

選擇排序

思路

選擇排序的思路很好理解,以從小到大排序爲例:算法

  • 選出集合中最小的元素,置於目標集合第一個位置

clipboard.png

  • 重複上述過程,剩餘元素中選出最小的元素,置於目標集合第二個位置……以此類推,直到最後一個元素.

clipboard.png

代碼示例

  • PositionBean類

示例將對int[]進行排序,爲了方便處理 int[] 數組 中的索引信息,構建了PositionBean類。(後續的探討其它算法的文章中,也多以int[]爲例,一樣會用到PositionBean)數組

public class PositionBean {
    int val;    //值
    int index;  //索引

    public PositionBean(int val, int index) {
        this.val = val;
        this.index = index;
    }
    
    //省略setter和getter方法……
}
  • 算法實現this

    • 查找最小值方法
    private static PositionBean findMin(int source[]){
        int minIndex = 0;
        int min=source[minIndex];    //最小值min,初始化爲第一個元素
        for(int i=1,len=source.length;i<len;i++){
            //輪詢source[],找到比當前min小的元素,給min從新賦值,並記錄最小值索引minIndex
            if(min>source[i]){    
                min=source[i];
                minIndex = i;
            }
        }
    
        return new PositionBean(min,minIndex);
    }
    • 選擇排序實現
    public static int[] doOrder(int source[]){
        if(source==null || source.length==0){
            return source;
        }
    
        int len=source.length;
        int res[] = new int[len];
        for(int i=0;i<len;i++){
            PositionBean bean = findMin(source);
            res[i] = bean.getVal();
            source = removeElement(source,bean.getIndex()); //移除元素
        }
        return res;
    }
    • 移除數組指定元素的方法
    /**
     * 移除source[]中,索引爲index的元素
     * @param source
     * @param index
     * @return
     */
    private static int[] removeElement(int source[],int index){
        int len = source.length;
        int res[] = new int[len-1];
        int resIndex =0;
        for(int i=0;i<len;i++){
            if(index==i){
                continue;
            }
    
            res[resIndex] = source[i];
            resIndex++;
        }
        return res;
    }

代碼很簡單,很少作解釋。spa

二分查找

思路

你們之前玩沒玩過猜數遊戲?一我的(張三)先寫下1到100的數字中任意一個數,另外一我的(李四)去猜,張三根據對方的猜想狀況提示「大了」、「小了」,直到猜對!code

  • 線性

李四能夠選擇從1開始猜,接下來的劇情會是這樣的:blog

李:1
張:小了
李:2
張:小
……

這種猜法,最多猜100次。也就是說,最壞狀況作了集合遍歷,時間複雜度記做O(n)排序

  • 折半

固然李四也有另外的選擇:索引

李:50
張:小了
李:25
張:大了
……

小李子每次都選擇了中間的數字進行猜,而經過張三的提示,每次都能排除當前集合近半的不符合條件的數字:將當前集合(1~100)以 中間數字 進行分隔,要麼在數字較小的結合中(1~50),要麼在數字較大的集合中(51~100)。遊戲

這種方式,就是咱們要探討的二分查找,也叫折半查找。給出示意圖:

clipboard.png

第一次比較後,因爲目標值大於中間數(target=10 > 中間數=8),所以中間數左側(含中間數)數字-1,1,5,8已然出局(圖中第二次比較,將出局的數字格畫成了虛線)

代碼示例

依照上面的示意圖寫出代碼:

/**
 * 在source[]中尋找target,若是找不到拋出異常RuntimeException(target+"不存在")
 * @param target
 * @param source
 * @return
 */
public static int doFind(int target,int source[]){
    if(source==null || source.length==0){
        throw new RuntimeException("集合爲空");
    }
    int low = 0;
    int height = source.length-1;
    do{
        int halfIndex = (low+height)/2; //中間索引
        int halfVal = source[halfIndex];    //中間索引對應的數字
        if(target==halfVal){    //發現目標
            return halfIndex;
        }else if(target>halfVal){   //若是target大於中間的數字,更新低位索引=中間索引+1
            low=halfIndex+1;
        }else{
            height=halfIndex-1;
        }
    }while (low<=height);

    throw new RuntimeException(target+"不存在");
}

探討

  • 時間複雜度

二分查找與線性查找相比,時效方面有着明顯的優點。
二分查找每次都將查找數據集縮小1/2,那問個問題——在n個數中查找,利用折半算法每次都能將數據集減半(除2),多少次能獲得結果(將數據集縮小到2之內)?這個問題再抽象一下:整數n除以多少次數字2,能獲得1或0?再換一種問法問:多少個2相乘,可以大於等於(正)整數n?

若是沒有把高中數學知識還給物理老師的話,你應該多多少少聞到了些對數的味道!

Tips:
    你可能不記得對數了,但極可能記得什麼是冪。"2³=?"就是一個冪運算,顯然2³=8。
    那麼多少個2相乘是8?這就是對數運算,能夠簡單記做"log8=?"。對數運算是冪運算的逆運算。

若是還想加深有關對數的理解,能夠看下這篇文章——如何理解對數?

說了這麼多,實際上是在推導這個結論:二分查找的時間複雜度是O(log n)

  • 劣勢

二分法快則快矣,可是有個很大的限制,只能用於有序集合的查找。若是自己是一個無序的集合,只能先排序再強行二分。

  • 其它

還有就是,java已經爲咱們實現了二分查找,詳見Collections.binarySearch

相關文章
相關標籤/搜索