JAVA 數據結構與算法(三)—— 查找算法(順序/線性查找、二分查找/折半查找、插值查找、斐波那契查找)

一、查找算法

1、順序/線性查找

(1)介紹

  • 線性查找(Linear Search)又稱順序查找,是一種最簡單的查找方法,它的基本思想是從第一個記錄開始,逐個比較記錄的關鍵字,直到和給定的K值相等,則查找成功;若比較結果與文件中n個記錄的關鍵字都不等,則查找失敗。
  • 查找是對具有相同屬性的數據元素(記錄)的集合(數據對象)進行的,稱之爲表或文件,也稱字典。
  • 對錶的查找,若僅對錶進行查找操作,而不能改變表中的數據元素,爲靜態查找;對錶除了進行查找操作外,還可能對錶進行插入或刪除操作,則爲動態查找。

(2)示例

/*線性查找*/
public class LinerSearch {
    public static void main(String[] args) {
        /*定義一個數組*/
        int arr[] = {5,0,7,2,9,4,6,1,8,3};
        /*定義一個要查找的值,如4*/
        int value = 4;

        int index = linerSearch(arr, value);

        if(index == -1){
            System.out.println("該數字中沒有要查找的數值");
        }else{
            System.out.println("該數組中下標爲" + index + "的數值爲要查找的值");
        }
    }

    /*線性查找法:找到一個滿足條件的就滿足*/
    public static int linerSearch(int[] arr, int value){
        for(int i = 0; i < arr.length; i++){
            if(arr[i] == value){
                return i;
            }
        }
        return -1;
    }
}

---------------------
輸出結果爲:
該數組中下標爲5的數值爲要查找的值

2、二分查找/折半查找

(1)介紹
二分查找(Binary earch)也叫折半查找,是一種基本的查找算法,這種查找方法需要待查的表滿足兩個條件:

  • 查找表必須使用順序的存儲結構。
  • 查找表必須按關鍵字大小有序排列。

算法的基本思想是:

  • 在有序表中,取中間數據作爲比較對象,若給定值與中間記錄的關鍵字相等,則查找成功;
  • 若給定值小於中間記錄的關鍵字,則在中闊記錄的左半區繼續查找;
  • 若給定值大於中間記錄的關鍵字,則在中間記錄的右半區繼續查找;
  • 不斷重複上述過程,直到查找成功,或所有查找區域元記錄,查找失敗爲止。

(2)示例

/*二分查找*/
public class BinarySearch {
    public static void main(String[] args) {
        /*定義一個數組*/
        int arr[] = {11,22,33,44,55,66,77,88,99,111,122,133,144,155};
        /*定義一個要查找的值,如33*/
        int value = 33;

        int index = binarySearch(arr, value, 0, arr.length - 1);
        if(index == -1){
            System.out.println("該數字中沒有要查找的數值");
        }else{
            System.out.println("該數組中下標爲" + index + "的數值爲要查找的值");
        }
    }

    /*二分查找*/
    public static int binarySearch(int[] arr, int value, int left, int right){
        /*如果left > right,或者value小於最小值或大於最大值則表示沒有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            return -1;
        }

        /*left爲左下標,right爲右下標,mid爲中間值下標*/
        int mid = (left + right) / 2;
        /*中間下標對應的值*/
        int midValue = arr[mid];
        
        /*遞歸查找*/
        if(value < midValue){
            /*向左遞歸*/
            right = mid - 1;
            return binarySearch(arr,value,left,right);
        }else if(value > midValue){
            /*向右遞歸*/
            left = mid + 1;
            return binarySearch(arr,value,left,right);
        }else{
            return mid;
        }
    }
}

---------------------
輸出結果爲:
該數組中下標爲2的數值爲要查找的值

(3)優化
在上面的示例中,數組中每個值都只有一個,但是實際情況可能會出現多個相同的值,如arr[] = {11,22,33,33,33,33,33,44,55,66,77,88,99,111,122,133,144,155},這時在進行查找的話就必須要把所有相同的值全部查找出來。具體實現方式如下

/*二分查找*/
public class BinarySearch {
    public static void main(String[] args) {
        /*定義一個數組*/
        int arr[] = {11,22,33,33,33,33,33,44,55,66,77,88,99,111,122,133,144,155};
        /*定義一個要查找的值,如33*/
        int value = 33;

        List<Integer> list = binarySearch(arr, value, 0, arr.length - 1);
        if(list.size() == 0){
            System.out.println("該數字中沒有要查找的數值");
        }else{
            System.out.println("該數組中要查找的值爲:" + list);
        }
    }

	/*二分查找優化*/
    public static List<Integer> binarySearch(int[] arr, int value, int left, int right){
        /*如果left > right,或者value小於最小值或大於最大值則表示沒有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            /*沒有找到則返回一個空的集合*/
            return new ArrayList<Integer>();
        }

        /*left爲左下標,right爲右下標,mid爲中間值下標*/
        int mid = (left + right) / 2;
        /*中間下標對應的值*/
        int midValue = arr[mid];


        /*遞歸查找*/
        if(value < midValue){
            /*向左遞歸*/
            right = mid - 1;
            return binarySearch(arr,value,left,right);
        }else if(value > midValue){
            /*向右遞歸*/
            left = mid + 1;
            return binarySearch(arr,value,left,right);
        }else{
            /*創建一個集合用於存放查找到的值的下標*/
            List<Integer> indexList = new ArrayList<Integer>();
            indexList.add(mid);
            /*向左查找相同值*/
            int temp = mid - 1;
            while(true){
                if(temp < 0 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp -= 1;
            }
            /*向右查找相同值*/
            temp = mid + 1;
            while(true){
                if(temp > arr.length - 1 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp += 1;
            }
            return indexList;
        }
    }
}

---------------------
輸出結果爲:
該數組中要查找的值爲:[3, 2, 4, 5, 6]

3、插值查找

(1)介紹

  • 插值查找,有序表的一種查找方式。插值查找是根據查找關鍵字與查找表中最大最小記錄關鍵字比較後的查找方法。
  • 插值查找基於二分查找,將查找點的選擇改進爲自適應選擇,提高查找效率。
  • 插值算法的優缺點:
    • 對於數據量較大,關鍵字分佈比較均勻的查找表來說,採用插值查找, 速度較快.
    • 關鍵字分佈不均勻的情況下,該方法不一定比折半查找要好

(2)示例

/*插值查找*/
public class InsertSearch {
    public static void main(String[] args) {
        /*定義一個數組*/
        int arr[] = {1,2,3,3,3,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
        /*定義一個要查找的值,如4*/
        int value = 3;
        List<Integer> list = insertSearch(arr, value, 0, arr.length - 1);
        if(list.size() == 0){
            System.out.println("該數字中沒有要查找的數值");
        }else{
            System.out.println("該數組中要查找的值爲:" + list);
        }
    }

    /*插值查找*/
    public static List<Integer> insertSearch(int[] arr, int value, int left, int right){
        /*如果left > right,或者value小於最小值或大於最大值則表示沒有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            return new ArrayList<Integer>();
        }

        /*left爲左下標,right爲右下標,mid爲中間值下標*/
        int mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);
        int midValue = arr[mid];
        if(value < midValue){
            /*向左遞歸查找*/
            right = mid - 1;
            return insertSearch(arr, value, left, right);
        }else if(value > midValue){
            /*向左遞歸查找*/
            left = mid + 1;
            return insertSearch(arr, value, left, right);
        }else{
            /*創建一個集合用於存放查找到的值的下標*/
            List<Integer> indexList = new ArrayList<Integer>();
            indexList.add(mid);
            /*向左查找相同值*/
            int temp = mid - 1;
            while(true){
                if(temp < 0 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp -= 1;
            }
            /*向右查找相同值*/
            temp = mid + 1;
            while(true){
                if(temp > arr.length - 1 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp += 1;
            }
            return indexList;
        }
    }
}

---------------------
輸出結果爲:
該數組中要查找的值爲:[2, 3, 4, 5]

4、斐波那契查找

(1)介紹

  • 黃金分割點是指把一條線段分割爲兩部分,使其中一部分與全長之比等於另一部分與這部分之比。取其前三位數字的近似值是0.618。由於按此比例設計的造型十分美麗,因此稱爲黃金分割,也稱爲中外比。這是一個神奇的數字,會帶來意想不到的效果。
  • 斐波那契數列{1,1, 2,3,5, 8, 13,21, 34, 55}發現斐波那契數列的兩個相鄰數的比例,無限接近黃金分割值0.618。
  • 斐波那契搜索(Fibonacci search),又稱斐波那契查找,是區間中單峯函數的搜索技術,斐波那契查找是在二分查找的基礎上根據斐波那契數列進行分割的。
  • 斐波那契查找與折半查找很相似,他是根據斐波那契序列的特點對有序表進行分割的。他要求開始表中記錄的個數爲某個斐波那契數小1,及n=F(k)-1;開始將k值與第F(k-1)位置的記錄進行比較(及mid=low+F(k-1)-1),比較結果也分爲三種:
    • 相等,則mid位置的元素即爲所求;
    • 大於>,則low=mid+1,k-=2;
      說明:low=mid+1說明待查找的元素在[mid+1,high]範圍內,k-=2 說明範圍[mid+1,high]內的元素個數爲n-(F(k-1))=Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1個,所以可以遞歸的應用斐波那契查找。
    • 小於<,則high=mid-1,k-=1。
      說明:low=mid+1說明待查找的元素在[low,mid-1]範圍內,k-=1 說明範圍[low,mid-1]內的元素個數爲F(k-1)-1個,所以可以遞歸的應用斐波那契查找。
  • 在最壞情況下,斐波那契查找的時間複雜度還是O(log2n),且其期望複雜度也爲O(log2n),但是與折半查找相比,斐波那契查找的優點是它只涉及加法和減法運算,而不用除法,而除法比加減法要佔用更多的時間,因此,斐波那契查找的運行時間理論上比折半查找小,但是還是得視具體情況而定。

(2)示例

/*斐波那契查找*/
public class FibonacciSearch {
    public static int SIZE = 20;
    public static void main(String[] args) {
        /*定義一個數組*/
        int arr[] = {1,11,22,33,44,55,66,77,88,99,100};
        /*定義一個要查找的值,如4*/
        int value = 22;

        int index = fibonacciSearch(arr, value);
        if(index == -1){
            System.out.println("該數字中沒有要查找的數值");
        }else{
            System.out.println("該數組中下標爲" + index + "的數值爲要查找的值");
        }
    }

    /*斐波那契查找*/
    public static int fibonacciSearch(int[] arr, int value){
        /*left爲左下標,right爲右下標,k表示斐波那契分割數值的下標,mid表示存放找到的值*/
        int left = 0;
        int right = arr.length - 1;
        int k = 0;
        int mid = 0;
        /*獲取斐波那契數列*/
        int[] fib = fib();

        /*如果left > right,或者value小於最小值或大於最大值則表示沒有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            return -1;
        }

        /*獲取斐波那契分割數值的下標*/
        while(right > fib[k] - 1){
            k++;
        }

        /*因爲fib[k]的值可能大於arr的長度,所以需要新建一個數組並指向arr數組*/
        int[] tempArr = Arrays.copyOf(arr,fib[k]);
        /*使用arr數組最後的值填充新數組tempArr*/
        for(int i = right + 1; i < tempArr.length; i++){
            tempArr[i] = arr[right];
        }

        /*循環查找*/
        while(left <= right){
            mid = left + fib[k - 1] -1;
            if(value < tempArr[mid]){
                /*繼續向數組的左邊查找*/
                right = mid - 1;
                k -= 1;
            }else if(value > tempArr[mid]){
                /*繼續向數組的右邊查找*/
                left = mid + 1;
                k -= 2;
            }else{
                if(mid <= right){
                    return mid;
                }
                else{
                    return right;
                }
            }
        }
        return -1;
    }

    /*創建一個斐波那契數列,並生成具體的數值*/
    public static int[] fib(){
        int[] fib = new int[SIZE];
        fib[0] = 1;
        fib[1] = 1;
        for(int i = 2; i < SIZE; i++){
            fib[i] = fib[i - 1] + fib[i - 2];
        }
        return fib;
    }
}

---------------------
輸出結果爲:
該數組中下標爲2的數值爲要查找的值
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章