1. 程式人生 > >(一)演算法--查詢演算法順序查詢和二分查詢(遞迴和非遞迴方式)

(一)演算法--查詢演算法順序查詢和二分查詢(遞迴和非遞迴方式)

我們拋開二分查詢演算法,如果有這樣的一個需求,需要在一些數字中找出有沒有某個數字,我們應該怎麼做?

         1 首先我們會想到用什麼資料結構存放這些數?

資料結構就是計算機儲存組織、組織資料的方式。可以這樣理解,生活中我們穿的衣服需要放到一個地方,衣服可以放到衣櫥中,也可以放到行李箱中,也可以放到衣架中,這裡的衣架、衣櫥、和行李箱及是衣服的存放結構。所以我們在查詢某一個數字是否在一堆數字中的時候,我們要把資料放到資料結構中,不同的資料結構有不同的優勢,就像衣服放到不同的“儲存”中,會有不同的優勢。而精心選擇的資料結構可以帶來更高的執行或者儲存效率。

         計算機中有哪些資料結構呢?

常見的資料結構有陣列(Array)、堆疊(Stack)、佇列(Queue)、連結串列(Linked List)、樹(Tree)、圖(Graph)、堆(Heap)、散列表(Hash)。


具體的資料結構以及優勢就不再這裡介紹了,以後會一一介紹。

而java語言常用的資料結構有陣列,list,set,map。list實際上也是陣列結構,不太複雜和確定長度下,使用陣列的效率會高些,陣列是確定長度的。list的值可以重複,set的值不可以重複,map是一種鍵值對,鍵可以是多種型別的與值形成對應關係。

我們是在固定一些數字中查詢到我們想要的數字所在位置,當我們需要儲存或處理一系列資料,陣列就可以充當這樣的角色,它的記憶體是相連的資料,並且在棧中引用只有一個,如果不用陣列,就要一個一個的宣告,浪費儲存空間,顯然不合理,所以我們在這裡用陣列結構,而java的其他資料結構分別適合在哪些場景中使用呢?在這裡就不介紹了,後續文章會總結。

2  順序查詢

拋開所學,我們最簡單和最”笨”的方式就是挨個的查詢和比較,看是不是我想要的那個key。這就是順序查詢。java程式碼如下

  條件:無序或有序元素。
  原理:按順序比較每個元素,直到找到關鍵字為止。
  時間複雜度:O(n)

/**
     * 順序查詢
     * @param srcArray
     * @param key
     * @return
     */
    public static int orderSearch(int srcArray[],int key){
        for(int i=0;i<srcArray.length-1;i++){
            if(key == srcArray[i]){
                return i;
            }
        }
        return -1;
    }


3 二分查詢法

當我們的數字是有序的情況下,可以採用順序查詢法,也可以採用二分查詢法,而人們發現發了在元素有序的時候,使用二分查詢法效率更高。大概是誰想出來的二分查詢法的人物不再說明,說一下二分查詢思想。

條件:有序元素

思想: 

3.1 不像順序查詢那樣,我們取得陣列中間的數字,如果正好是我們要查詢的元素,則搜尋過程結束;

3.2 中間的元素不是我們想要的元素,我們比較中間元素和我們想要元素的大小

如果中間元素大於/小於中間元素,則我們在大於/小於的那一半查詢,而且和開始一樣從中間的元素開始比較(也就是把剩下的一半當做新的開始)。

3.3 直到某一步比較,找到我們的key,返回該key所在的陣列下標。

3.4 如果都沒有找到,存放陣列低地址的變數大於高地址變數時,則查詢結束,說明該有序序列中沒有我們想找到的。

複雜度:O(logn)

解決問題思想到程式實現,這裡面有幾個點。

1)取陣列中間位置,第一次可以是陣列元素長度/2,第二次我們如何從陣列剩下的一半中取中間位置?以及第三次呢?也就是每次我們都要知道”新陣列”的開始和結尾,以及中間位置;所以我們可以定義兩個陣列下標變數,low 和high(記憶體中的地址大小),以及mid(每次的中間下標),這樣通過移動他們來做到輪詢,有些類似c語言中的指標變數。

2)怎麼結束比較?每次都找不到,直到陣列的一半已經沒有元素了,也就是low指向的陣列地址已經大於high指向的陣列地址,說明不存在。java程式碼如下,之後再逐步分析程式碼

非遞迴方式實現

 /**
     * 二分查詢普通實現。
     * @param srcArray 有序陣列
     * @param key 查詢元素
     * @return  不存在返回-1,存在返回對應的陣列下標
     */
    public static int binSearch(int srcArray[], int key) {
        int mid = srcArray.length / 2;
        if (key == srcArray[mid]) {
            return mid;
        }

        int low= 0;
        int high = srcArray.length - 1;
        while (low<= high) {
            mid = (high - low) / 2 + low;
            if (key < srcArray[mid]) {
                high = mid - 1;
            } else if (key > srcArray[mid]) {
                low = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

           遞迴方式實現

 /**
     * 二分查詢遞迴實現。
     * @param srcArray  有序陣列
     * @param low 陣列低地址下標
     * @param high   陣列高地址下標
     * @param key  查詢元素
     * @return 查詢元素不存在返回-1,存在返回對應的陣列下標
     */
    public static int binSearch(int srcArray[], int low, int high, int key) {
        int mid = (high  - low) / 2 + low;
        if (srcArray[mid] == key) {
            return mid;
        }
        if (low >= high) {
            return -1;
        } else if (key > srcArray[mid]) {
            return binSearch(srcArray, mid + 1, high, key);
        } else if (key < srcArray[mid]) {
            return binSearch(srcArray, low, mid - 1, key);
        }
        return -1;
    }

          main方法

 public static void main(String[] args) {
        int srcArray[] = {3,6,11,17,21,23,28,30,81,89,99};
        System.out.println(binSearch(srcArray, 0, srcArray.length - 1, 222));
        System.out.println(binSearch(srcArray,222));
        System.out.println(orderSearch(srcArray,5));
    }


執行過程圖,大概的畫了一下,如下圖。


兩種實現方式,一種是普通實現方式,一種是遞迴思想實現方式,遞迴關注一個核心,而把變化的部分通過遞迴變數的改變而變化。

看了之前寫過二分查詢演算法,但是過了一段時間發現又會忘記,是當時沒有真正理解吧,之前的簡單粗暴,也感謝網友們當時指出問題:http://blog.csdn.net/lovesummerforever/article/details/24588989

        總結:

知識一定要變成自己的理解,才是自己的,不然會是瞬時記憶,都會還給書本。

 2 孤立的知識很難被記住,我們想要成塊的去理解,不要只學習其中的斷層,知道學習這個技術在知識體系中的什麼位置,和其他知識的聯絡。

         3 晦澀難懂的東西要追本溯源,因為這個技術一定是從簡單演變而來的,是多個簡單的疊加過程,最終我們才看到成型,必須要思考是怎麼衍變的。(例如文字的發展史)


4 技術來自生活卻高於生活,當不懂的時候,想想生活中的一些離我們近的事物,都是如出一轍的。