資料結構 —— 查詢演算法
十、查詢演算法
常用的查詢演算法:
- 順序(線性)查詢
- 二分查詢/折半查詢
- 插值查詢
- 斐波那契查詢
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)
- 有序數列才可使用二分查詢
思路分析
- 首先確定該陣列的中間下標mid = (left + right)/ 2
- 然後讓需要查詢的數findVal和arr【mid】比較
- findVal > arr[mid],向右查詢
- findVal < arr[mid],向右查詢
- findVal == arr[mid],找到,返回
- 結束遞迴的條件
- 找到就結束
- 遞迴完整個陣列,未找到,結束遞迴,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 插值查詢
基本原理
-
插值查詢演算法類似於二分查詢演算法,不同的是插值查詢每次從自適應mid處開始查詢
-
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+(key−a[low])/(a[high]−a[low])∗(high−low) -
插值索引,對應程式碼:
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[k−1]−1
F代表斐波那契數列,如圖所示
對F(k - 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[k−1]−1)+(F[k−2]−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[k−1]−1 -
類似的,每一子段也可分割
-
順序表長度不一定剛好等於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;//未找到
}
}