1. 程式人生 > 其它 >資料結構 —— 查詢演算法

資料結構 —— 查詢演算法

技術標籤:資料結構java資料結構演算法

十、查詢演算法

常用的查詢演算法:

  • 順序(線性)查詢
  • 二分查詢/折半查詢
  • 插值查詢
  • 斐波那契查詢

10.1 線性查詢

  • 一個數列可有序,可無序

程式碼實現

/**
 * 線性查詢
 * 這裡是找到一個即返回
 * @param arr 查詢的資料數列
 * @param val 需要查詢的值
 * @return
 */
public static int seqSearch(int[] arr,int val){
    //線性查詢是逐一比對,發現相同值,返回下標
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == val){
            return i;
        }
    }
    return -1;
}

10.2 二分查詢(Binary Search)

  • 有序數列才可使用二分查詢

思路分析

  1. 首先確定該陣列的中間下標mid = (left + right)/ 2
  2. 然後讓需要查詢的數findVal和arr【mid】比較
    • findVal > arr[mid],向右查詢
    • findVal < arr[mid],向右查詢
    • findVal == arr[mid],找到,返回
  3. 結束遞迴的條件
    • 找到就結束
    • 遞迴完整個陣列,未找到,結束遞迴,left > right

基本寫法

public static int binarySearch(int[] arr,int left,int right,int findVal){
    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;
    }
}

新需求

當一個數組中有多個相同的數值是 ,將所有數值都查到

程式碼實現

package com.why.search;

import com.sun.jdi.PathSearchingVirtualMachine;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;

/**
 * @Description TODO 當一個數組中有多個相同的數值是 ,將所有數值都查到,使用二分查詢
 * @Author why
 * @Date 2020/11/1 16:35
 * Version 1.0
 **/
public class NewBinarySearch {
    public static void main(String[] args) {
        int[] arr = {1,8,8,10,10,10,10,11};
        List res = newBinarySearch(arr, 0, arr.length - 1, 8);
        if (!res.isEmpty()){
            System.out.println(res);
        }else {
            System.out.println("未找到");
        }
    }

    /**
     * 二分查詢查詢多條資料
     *
     * 思路:
     * 找到mid值時,不要馬上返回
     * 向mid 索引值的左邊掃描將所有滿足查詢值的元素下標加入到集合
     * 向右掃描將所有滿足查詢值的元素下標加入到集合
     * @param arr
     * @param left
     * @param right
     * @param findVal
     * @return
     */
    public static List<Integer> newBinarySearch(int[] arr, int left, int right, int findVal){

        if (left > right){
            return new ArrayList<Integer>();
        }
        int  mid = (left + right) / 2;
        int midVal = arr[mid];


        if (findVal > midVal){
            return newBinarySearch(arr,mid + 1,right,findVal);
        }else if (findVal < midVal){
            return newBinarySearch(arr,left,mid - 1,findVal);
        }else {
            List<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 || arr[temp] != findVal){
                    break;
                }
                resIndexList.add(temp);
                temp += 1;
            }
            return resIndexList;
        }
    }
}

10.3 插值查詢

基本原理

  1. 插值查詢演算法類似於二分查詢演算法,不同的是插值查詢每次從自適應mid處開始查詢

  2. mid索引的公式,low表示左邊索引,high表示右邊索引,key表示查詢的值
    m i d = l o w + ( k e y − a [ l o w ] ) / ( a [ h i g h ] − a [ l o w ] ) ∗ ( h i g h − l o w ) mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low) mid=low+(keya[low])/(a[high]a[low])(highlow)

  3. 插值索引,對應程式碼:

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

解決的問題

用於查詢類似於[1,2,3,4,…,n]的資料序列中的值

程式碼實現

/**
 * 插值查詢演算法
 * @param arr
 * @param left
 * @param right
 * @param findVal
 * @return
 */
public static int insertSearch(int[] arr,int left,int right,int findVal){
    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]);
    if (findVal < arr[mid]){
        return insertSearch(arr,left,mid - 1,findVal);
    }else if (findVal > arr[mid]){
        return insertSearch(arr,mid + 1,right,findVal);
    }else {
        return mid;
    }
}

10.4 斐波那契(黃金分割法)查詢演算法(Fibonacci Search)

黃金分割點

指把一條線段分割為兩部分,使其中一部分與全長之比等於另一部分與這部分之比。取其前三位數字的近似值0.618,此比例稱為黃金分割,也稱中外比

斐波那契數列

斐波那契數列{1,1,2,3,5,8,13,21,34,55},發現相鄰兩數的比例無限接近黃金分隔值0.618

基本原理

改變中間節點mid的位置,mid不再是中間節點,二十位於黃金分割點附近,即
m i d = l o w + F [ k − 1 ] − 1 mid = low + F[k - 1] - 1 mid=low+F[k1]1
F代表斐波那契數列,如圖所示

image-20201102163840552

對F(k - 1) - 1的理解

  1. 由斐波那契數列F[k] = F[k-1] + F[k-2]的性質可得,
    F [ k ] − 1 = ( F [ k − 1 ] − 1 ) + ( F [ k − 2 ] − 1 ) + 1 F[k] - 1 = (F[k-1] - 1) + (F[k-2] - 1) + 1 F[k]1=(F[k1]1)+(F[k2]1)+1
    該式說明,只要順序表的長度為F(k) - 1,則可將該表分為長度為F(k-1) - 1和F(k-2) - 1的兩段,即如上圖所示
    m i d = l o w + F [ k − 1 ] − 1 mid = low +F[k-1] - 1 mid=low+F[k1]1

  2. 類似的,每一子段也可分割

  3. 順序表長度不一定剛好等於F[k] - 1,故需將原來長度n增加至F[k] - 1,k使F[k] - 1剛好大於或等於n即可,新增的位置,都賦為n位置的值即可

有序陣列才可使用此查詢演算法

程式碼實現

package com.why.search;

import com.sun.source.tree.IfTree;

import java.security.Key;
import java.util.Arrays;

/**
 * @Description TODO 斐波那契查詢演算法
 * @Author why
 * @Date 2020/11/2 16:53
 * Version 1.0
 **/
public class FibonacciSearch {
    public static int maxSize = 20;
    public static void main(String[] args) {
        int[] arr = {1,8,10,89,1000,1234};
        int i = fibSearch(arr, 1000);
        if (i == -1){
            System.out.println("未找到");
        }else {
            System.out.println("下標是:"+i);
        }
    }

    /**
     * 需要使用斐波那契數列,首先獲取到斐波那契數列
     * 非遞迴方式
     * @return
     */
    public static int[] fib(){
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < f.length; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }

    /**
     * 斐波那契查詢演算法
     * 非遞迴
     * @param a
     * @param findVal
     * @return
     */
    public static int fibSearch(int[] a,int findVal){
        int low = 0;
        int high = a.length - 1;
        int k = 0;//表示斐波那契分割數值的下標
        int mid = 0;//存放mid值

        //獲取斐波那契數列
        int[] f = fib();
        //獲取斐波那契分割數值的下標
        while (high > f[k] - 1){
            k++;
        }
        //因為f[k]值可能大於陣列長度,
        // 因此需要使用Arrays類,構造新的陣列,並指向temp[]
        //不足的部分使用0填充
        int[] temp = Arrays.copyOf(a,f[k]);
        //實際上需使用a陣列最後 的數填充temp
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = a[high];
        }

        //迴圈處理查詢數
        while (low <= high){
            mid = low +f[k-1] -1;
            if (findVal < 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--
                k--;
            }else if (findVal > temp[mid]){//向右查詢
                low = mid + 1;
                k -= 2;
            }else {//找到
                //需要確定返回那個下標
                if (mid <= high){
                    return mid;
                }else {
                    return high;
                }
            }
        }
        return -1;//未找到
    }
}