大概半個月前,偶爾看到《算法圖解》,沒翻幾頁便被數學戰五渣的我奉爲神書,怎一個相見恨晚、愛不釋手加老淚縱橫啊!遂寫文以做積累……java
選擇排序的思路很好理解,以從小到大排序爲例:算法
示例將對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)。遊戲
這種方式,就是咱們要探討的二分查找,也叫折半查找。給出示意圖:
第一次比較後,因爲目標值大於中間數(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
。