天天看點

資料結構與算法-查找算法 | 尚矽谷韓順平筆記一、順序查找(線性查找)二、二分查找(折半查找)三、插值查找算法四、斐波那契查找(黃金分割法)

一、順序查找(線性查找)

順序查找太簡單了,沒什麼好說的,直接循環找到等于的就傳回,沒有找到就傳回-1

代碼實作 

//順序查找(線性查找)
public class SeqSearch {
    public static void main(String[] args) {
        int arr[] = {1,9,11,-1,34,89}; //沒有順序的數組
        int index = seqSearch(arr,11);
        if (index == -1){
            System.out.println("沒有找到");
        }else{
            System.out.println("找到了,下标為:"+index);
        }
    }
    public static int seqSearch(int[] arr,int value){
        //線性查找是逐一比對,發現有相同值,就傳回下标
         for (int i=0;i<arr.length;i++){
             if (arr[i]==value){
                 return i;
             }
         }
         return -1;
    }
}
           

二、二分查找(折半查找)

使用二分查找的前提是 這個數組是有序的

思路分析

1、首先确定改數組的中間的下标 mid = (left+right)/2

2、然後讓需要查找的數findVal和arr[mid]比較

  • 如果findVal>arr[mid],說明你要查找的數在mid右邊,遞歸向右查詢
  • 如果findVal<arr[mid],說明你要查找的數在mid左邊,遞歸向左查詢
  • 如果findVal==arr[mid],說明找到了直接傳回

3、找到就結束遞歸

4、遞歸完還沒找到也結束遞歸

代碼實作 (遞歸)

public class BinarySearch {
    public static void main(String[] args) {
        int arr[] = {1,8,10,89,1000};
        int resIndex = binarySearch(arr,0, arr.length-1,88 );
        if (resIndex!=-1){
            System.out.println("要找的值索引為:"+resIndex);
        }else {
            System.out.println("數組沒有這個數");
        }
    }

    //二分查找算法
    public static int binarySearch(int[] arr,int left,int right,int findVal){

        //當left>right說明遞歸完整個數組了還是沒有找到
        if (left>right){
            return -1;
        }

        int mid = (left+right)/2;
        int midVal = arr[mid];
        if (findVal>midVal){  //向右遞歸
            return binarySearch(arr,mid+1,right,findVal);
        }else if (findVal<midVal){
            return binarySearch(arr,left,mid-1,findVal);
        }else{
           return mid;
        }
    }
}
           

優化

我們又多了個需求:要找一個數,如果數組有很多這個相同的數,那麼我們都要找出來

思路:在找到結果的時候,我們先不急着傳回,先向左和向右掃描,将滿足的都加入集合中

public class BinarySearch {
    public static void main(String[] args) {
        int arr[] = {1,8,10,89,1000,1000,1000};
        ArrayList resIndexList = binarySearch(arr,0, arr.length-1,1000 );
        System.out.println(resIndexList);
    }

    //二分查找算法
    public static ArrayList<Integer> binarySearch(int[] arr,int left,int right,int findVal){

        //當left>right說明遞歸完整個數組了還是沒有找到
        if (left>right){
            return new ArrayList<>();
        }
        int mid = (left+right)/2;
        int midVal = arr[mid];
        if (findVal>midVal){  //向右遞歸
            return binarySearch(arr,mid+1,right,findVal);
        }else if (findVal<midVal){
            return binarySearch(arr,left,mid-1,findVal);
        }else{
            ArrayList<Integer> resIndexlist = new ArrayList<>();
            int temp = mid-1;
            while(true){
                if (temp <0 || arr[temp]!=findVal){
                    break;
                }
                //否則放入集合中
                resIndexlist.add(temp);
                temp-=1; //左移
            }
            resIndexlist.add(mid);
            temp = mid+1;
            while(true){
                if (temp > arr.length-1 || arr[temp]!=findVal){
                    break;
                }
                //否則放入集合中
                resIndexlist.add(temp);
                temp+=1; //左移
            }
            resIndexlist.add(mid);
            return resIndexlist;
        }
    }
}
           

 代碼實作(非遞歸)

public class BinarySearchNoRecur {

    public static void main(String[] args) {
        int[] arr = {1,3,8,10,11,67,100};
        int index=binarySearch(arr,11);
        System.out.println(index);
    }

    /**
     *
     * @param arr 待查找的數組
     * @param target  需要查找的數
     * @return 傳回對應的下标,-1表示沒有找到
     */
    public static int binarySearch(int[] arr,int target){
        int left = 0;
        int right = arr.length-1;
        while(left<=right){ //說明繼續找
            int mid = (left+right)/2;
            if (arr[mid]==target){
                return mid;
            }else if (arr[mid]>target){
                right = mid-1; //需要向左邊查找
            }else {
                left = mid+1;
            }
        }
        return -1;
    }
}
           

三、插值查找算法

插值查找算法類似于二分查找,不同的是插值查找每次從自适應mid處開始查找

是對二分查找算法進行的優化,如果每次折半其實效率并不高,是以我們對mid的公式進行優化

 int mid = left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]) 

他是一種自适應的寫法 因為他本身的值也參加了mid的運算

代碼實作

public class InsertValueSearch {
    public static void main(String[] args) {
        int arr[] = new int[100];
        for (int i=0;i<100;i++){
            arr[i]=i+1;
        }
        int res = insertValueSearch(arr,0, arr.length-1, 99);
        System.out.println(res);
    }

    //插值查找也要求數組有序
    public static int insertValueSearch(int[] arr,int left,int right,int findVal){
        //findVal<arr[0] || findVal>arr[arr.length-1]是必須要加的 否則可能越界
        if (left>right || findVal<arr[0] || findVal>arr[arr.length-1]){
            return -1;
        }

        int mid = left + (right - left)*(findVal - arr[left])/(arr[right]-arr[left]);
        int midVal= arr[mid];
        if (findVal<midVal){
            return insertValueSearch(arr, left, mid-1, findVal);
        } else if (findVal>midVal) {
            return insertValueSearch(arr, mid+1, right, findVal);
        } else {
            return mid;
        }
    }
}
           

注意事項

對于資料量較大,關鍵字分布比較均勻的查找來說,采用插值查找,速度比較快。

關鍵詞分布不均勻的情況下,改方法不一定比折半快

四、斐波那契查找(黃金分割法)

斐波那契數列{1,1,2,3,5,8,13,21,34,55}

發現斐波那契數列相鄰兩個數的比例,無限接近黃金分割值0.618

原理分析

原理與前面兩種相似,僅僅改變了中間點mid的位置,mid不再是中間或者插值得到,而是位于黃金分割點附近

資料結構與算法-查找算法 | 尚矽谷韓順平筆記一、順序查找(線性查找)二、二分查找(折半查找)三、插值查找算法四、斐波那契查找(黃金分割法)

根據對斐波那契數列 F[k] = F[K-1] + F[K-2] 可得 (F[k]-1)=(F[k-1]-1)+(F[k-1]-1)

說明:隻要順序表長度為F[k]-1 就可以分為(F[k-1]-1)和(F[k-1]-1)兩段,如圖

是以中間值 mid = low + F[K-1]- 1 

注意:我們的順序表不一定等于F[k]-1 是以我們需要把他從n擴充到F[K]-1 (這裡的k值隻要能使得F[k]-1恰好大于或等于n即可,由以下代碼得到,順序表長度增加後,新增的位置(從n+1到F[k]-1位置),都賦為n位置的值即可。)

while(n>fib(k)-1)
    k++;
           

代碼實作

public class FibonacciSearch {
    public static int maxSize = 20;
    public static void main(String[] args) {
        int [] arr = {1,8,10,89,1000,1234};
        System.out.println("index = "+fibSearch(arr,1));
    }

    //因為後面我們mid=low+f(k-1)-1 需要斐波那契數列 是以我們需要得到一個斐波那契數組
    //非遞歸得斐波那契數列(效率高些)
    public static int[] fib(){
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i=2;i<maxSize;i++){
            f[i] = f[i-1] + f[i-2];
        }
        return f;
    }

    //編寫斐波那契查找算法
    public static int fibSearch(int[] a,int key){
        int low = 0;
        int high = a.length-1;
        int k = 0; //斐波那契分割數值的下标
        int mid = 0;
        int f[] = fib();//擷取斐波那契數列
        //擷取斐波那契分割數列的下标
        while (high>f[k]-1){
            k++;
        }
        //因為f[k]值可能大于a的長度,是以我們需要使用Array構造新的數組并指向a[]
        int[] temp = Arrays.copyOf(a,f[k]); //不足的部分用0填充
        //實際用a數組的最後一個數來填充數組
        for (int i = high +1;i< temp.length;i++){
            temp[i] = a[high];
        }

        //使用while循環處理,得到我們的數key
        while(low<=high){
            mid = low+f[k-1]-1;
            if (key<temp[mid]){ //說明向左找
                high = mid -1 ;
                //1、全部的元素=前面的元素+後面的元素
                //2、f[k] = f[k-1]+f[k-2]
                //因為前面有f[k-1]個元素,是以可以繼續拆分  f[k-1] = f[k-2]+f[k-3]
                //即在f[k-1]的前面繼續查找,k--
                //即下次循環的時候 mid = f[k-1-1]-1
                k --;
            }else if (key> temp[mid]){
                high = mid+1;
                //1、全部的元素=前面的元素+後面的元素
                //2、f[k] = f[k-1]+f[k-2]
                //3、因為後面有f[k-2],是以可以繼續拆分 f[k-1] = f[k-3] +f[k-4]
                //4、即在f[k-2]的前面進行查找 k-=2
                //即下次循環的時候 mid = f[k-1-2]-1
                k-=2;
            }else { //找到
                if(mid<=high){
                    return mid;
                }else{
                    return high;//因為填充了 high可能找的就是那個high
                }

            }
        }
        return -1;
    }
}
           

分析難點 

資料結構與算法-查找算法 | 尚矽谷韓順平筆記一、順序查找(線性查找)二、二分查找(折半查找)三、插值查找算法四、斐波那契查找(黃金分割法)

第一個if判斷是向數組左邊開始查找,那麼對于左邊的資料,相比全部資料而言,它的長度是f[k-1]-1,之後f[k-1]-1又作為一個整體來查找,即k要自減1。對于右側的資料,它的長度為f[k-2]-1,之後,它也會作為一個整體來查找,是以k要減2。