java中常用的查找算法有4種:
- 順序(線性)查找
- 二分查找
- 插值查找
- 斐波那契查找
一、順序(線性)查找
順序(線性)查找只需要數組的順序依次進行查找,找到後返回數組的下標即可。順序查找適用於任何數據。缺點是當查找越後面的數花費的時間越長。
二、二分查找
二分查找的前提條件是這個數組是有序的。
二分查找思路:
- 確定數組中間的下標與值。
- 將要查找的數與中間的數進行比較:若比中間的數小,則向比中間值大的區間查找,若比中間數小,則向比中間值小的區間查找,重複第二步,知道找到該數。
代碼實現:
public int binarySearch(int[] arr, int left, int right, int searchValue){
if (left > right){ // 沒有查找到
return -1;
}
int mid = (left + right) / 2; // 中間值索引
int midValue = arr[mid]; // 中間值
if (searchValue < midValue){ // 左遞歸
return binarySearch(arr, left, mid - 1, searchValue);
}else if (searchValue > midValue){ // 右遞歸
return binarySearch(arr, mid + 1, right, searchValue);
}else {
return mid;
}
}
優化:當我們查找的數在該數組中有多個時,上面的方法只能返回一個值。優化返回相同的數的下標。利用集合存儲查找到相同數的下標。
當找到一個數時,繼續向左右尋找相同的數,並保持下標。
public ArrayList<Integer> binarySearch2(int[] arr, int left, int right, int searchValue){
if (left > right){ // 沒有查找到
return new ArrayList<Integer>();
}
int mid = (left + right) / 2; // 中間值索引
int midValue = arr[mid]; // 中間值
if (searchValue < midValue){ // 左遞歸
return binarySearch2(arr, left, mid - 1, searchValue);
}else if (searchValue > midValue){ // 右遞歸
return binarySearch2(arr, mid + 1, right, searchValue);
}else {
ArrayList<Integer> integers = new ArrayList<>();
int temp = mid - 1;
while (temp >= 0 && arr[temp] == arr[mid]){ //向左查找有無和查找值相同的數
integers.add(temp); // 添加下標
temp--;
}
integers.add(mid);
temp = mid + 1;
while (temp < arr.length && arr[temp] == arr[mid]){ // 向右查找有無和查找值相同的數
integers.add(temp); // 添加下標
temp++;
}
return integers;
}
}
三、插值查找
插值查找算法類似於二分查找,不同的是插值查找每次從自適應mid 處開始查找。
原理:將二分查找中的求mid 索引的公式 , low 表示左邊索引 left, high 表示右邊索引 right. key 就是要查找的值。
其餘分別與二分查找無異。
代碼實現:
public class InsertSearch {
public static void main(String[] args) {
int[] arr = {1,5,90,500,1000,1000};
InsertSearch insertSearch = new InsertSearch();
int i = insertSearch.binarySearch(arr, 0, arr.length - 1, 500);
System.out.println("i = " + i);
}
/**
* @Description: binarySearch 插值查找
* @param: [arr, left, right, searchValue]
* @return: int
* @auther: zqq
*/
public int binarySearch(int[] arr, int left, int right, int searchValue){
// 沒有查找到, searchValue < arr[0] || searchValue > arr[arr.length - 1] 防止越界
if (left > right || searchValue < arr[0] || searchValue > arr[arr.length - 1]){
return -1;
}
int mid = left + (right - left) * (searchValue - arr[left]) / (arr[right] - arr[left]); // 中間值索引:插值查找
int midValue = arr[mid]; // 中間值
if (searchValue < midValue){ // 左遞歸
return binarySearch(arr, left, mid - 1, searchValue);
}else if (searchValue > midValue){ // 右遞歸
return binarySearch(arr, mid + 1, right, searchValue);
}else {
return mid;
}
}
}
注意:
- 對於數據量較大,關鍵字分佈比較均勻的查找表來說,採用插值查找, 速度較快.
- 關鍵字分佈不均勻的情況下,該方法不一定比二分查找要好
四、斐波那契查找
- 黃金分割點:黃金分割點是指把一條線段分割爲兩部分,使其中一部分與全長之比等於另一部分與這部分之比。取其前三位 數字的近似值是 0.618。由於按此比例設計的造型十分美麗,因此稱爲黃金分割,也稱爲中外比。這是一個神 奇的數字,會帶來意向不大的效果。
- 斐波那契數列:{1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 發現斐波那契數列第二個(包括第二個)數之後的兩個相鄰數 的比例,無限接近 黃金分割值 0.618。
斐波那契查找原理: 就是要查找到數組一個黃金分割點(mid):mid = left + F[k] - 1(mid爲黃金分割點的下標)。若查找比黃金分割點的值小,則向比黃金分割點大的區域繼續使用黃金分割點查找。若查找比黃金分割點的值小,則向比黃金分割點小的區域繼續使用黃金分割點查找。這次依次查找,直到找到爲止,原理有和二分查找相似,這是中點(mid)不一樣。
那麼關鍵就是如何查找到黃金分割點:
黃金分割點查找:
- 黃金分割點查找要運用到斐波那契數列,要在該數列中從小到大找到一個數減一剛好大於等於待查找數組的長度,既順序表長度n <= F[k] - 1
- 但順序表長度 n 不一定剛好等於 F[k]-1,所以需要將原來的順序表長度 n 增加至 F[k]-1(將最後一個字依次向後填充)。
- 這樣一來帶查找的順序表就可以表示成:
由斐波那契數列 F[k]=F[k-1]+F[k-2] 的性質,可以得到F[k]-1=(F[k-1]-1)+(F[k-2]-1)+1
黃金分割點的位置:mid = left + F(k - 1) - 1
注: 爲什麼是F[k] - 1,而不是F[k]?
因爲斐波那契數列只有從第二個數開始與後一個數的比值才越來越接近黃金比率。
斐波那契數列的大小: 數組長度不同,那麼對應所需要的斐波那契數列長度就不一樣。若是寫死,則很浪費空間。故斐波那契數列的大小可根據數組的大小進行選擇。
- 當數組長度小於等於5時,數組的長度大於等於數組長度對應的斐波那契數,這時後面進行查找一個k使得fib[k]-1剛好大於等於數組長度就會越界,故寫死,給一個6。產生的最大斐波那契數是8,大於數組長度,就不會越界
- 當數組長度大於5時,數組的長度小於數組長度對應的斐波那契數,斐波那契數組長度爲待順序表長度,不會產生越界
實現動態斐波那契數列
public int[] fib( int right){
int[] fibonacii;
// 當數組長度小於等於5時,數組的長度大於等於數組長度對應的斐波那契數,
// 這時後面進行查找一個k使得fib[k]-1剛好大於等於數組長度就會越界,故寫死給一個6。產生的最大斐波那契數是8,大於數組長度,就不會越界
if (right <= 5){
fibonacii = new int[6];
}else {//當數組長度大於5時,數組的長度小於數組長度對應的斐波那契數,不會產生越界
fibonacii = new int[right];
}
fibonacii[0] = 1;
fibonacii[1] = 1;
for (int i = 2; i < fibonacii.length; i++) {
fibonacii[i] = fibonacii[i -1] + fibonacii[i - 2];
}
return fibonacii;
}
斐波那契查找完整代碼:
public class FibonacciSearch {
public static void main(String[] args) {
int[] arr = {1,5,90,500,1000};
FibonacciSearch fibonacciSearch = new FibonacciSearch();
int i = fibonacciSearch.fibonacciSearch(arr, 1000);
System.out.println("i = " + i);
}
/**
* @Description: fibonacciSearch 斐波那契查找
* @param: [arr, searchValue]
* @return: int
* @auther: zqq
* @date: 20/6/18 10:18
*/
public int fibonacciSearch(int[] arr, int searchValue){
int left = 0;
int right = arr.length - 1;
int[] fib = fib(arr.length);
int mid = 0;// 存儲
int k = 0;
while (arr.length > fib[k] - 1){ // 找到一個k使得這個斐波那契數剛好大於等於right
k++;
}
if (arr.length < fib[k]){
arr = Arrays.copyOf(arr, fib[k]);// 因爲right可能小於找到的斐波那契數,所以需要對數組擴容進行向後填充
for (int i = right+1; i < arr.length; i++) {
arr[i] = arr[right];
}
}
while (left <= right){
mid = left + fib[k - 1] - 1; // 分割點
if (searchValue < arr[mid]){ // 在黃金分割點左邊
right = mid - 1;
k--;// 向fib[k-1]的範圍查找
}else if (searchValue > arr[mid]){
left = mid + 1;
k -= 2;//向fib[k-2]的範圍查找
}else {
if (mid <= right){
return mid;
}else {
return right;
}
}
}
return -1;// 沒有找到
}
/**
* @Description: fib 構造斐波那契數列
* @param: [right, left]
* @return: int[]
* @auther: zqq
* @date: 20/6/18 9:50
*/
public int[] fib( int right){
int[] fibonacii;
// 當數組長度小於等於5時,數組的長度大於等於數組長度對應的斐波那契數,
// 這時後面進行查找一個k使得fib[k]-1剛好大於等於數組長度就會越界,故寫死給一個6。產生的最大斐波那契數是8,大於數組長度,就不會越界
if (right <= 5){
fibonacii = new int[6];
}else {//當數組長度大於5時,數組的長度小於數組長度對應的斐波那契數,不會產生越界
fibonacii = new int[right];
}
fibonacii[0] = 1;
fibonacii[1] = 1;
for (int i = 2; i < fibonacii.length; i++) {
fibonacii[i] = fibonacii[i -1] + fibonacii[i - 2];
}
return fibonacii;
}
}
需要查找重複的可再次基礎上修改。