查找算法
常用查找算法
- 顺序(线性)查找
- 二分查找/折半查找 (需要是有序数组)递归/非递归
- 插值查找
- 斐波那契查找/黄金分割点查找
线性查找
package search;
import java.util.ArrayList;
public class SequenceSearch {
public static void main(String[] args) {
int arr[] = {1,5,3,6,9,2,3,-1};
ArrayList res = seqSearch(arr,3);
System.out.println(res.toString());
}
// 这里找到一个满足条件的值就返回
// 找所有满足条件的,就返回一个集合
public static ArrayList<Integer> seqSearch(int[] arr, int value){
// 逐一比对,发现相同的值,返回下标
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < arr.length; i++) {
if(arr[i]==value){
list.add(i);
}
}
return list;
}
}
二分查找
必须为有序序列
思路分析
-
首先确定该数组的中间的下标 mid = (left+right)/2
-
然后让需要查找的数 findVal 和 arr[mid] 比较
- findVal > arr[mid],说明你要查找的数在 mid 的右边,因此需要递归的向右查找
- findVal < arr[mid],说明你要找的数在 mid 的左边,因此需要递归的向左查找
- findVal = arr[mid],返回
-
结束递归的条件
- 找到就结束递归
- 递归完整个数组,仍然没有找到 findVal,也需要结束递归 当 left > right 时,需要退出
package search;
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1,5,7,34,89,345,1234};
int left = 0;
int right = arr.length-1;
int res = binary(arr,7,left,right);
System.out.println(res);
}
public static int binary(int[] arr, int value,int left,int right){
int mid = (left + right)/2;
int midValue = arr[mid];
if(left>right){
return -1;
}
if(value > midValue){ // 向右递归
return binary(arr,value,mid+1,right);
}else if(value < midValue){
return binary(arr,value,left,mid-1);
}else{
return mid;
}
}
}
当序列中有重复值时,都返回
- 在找到 mid 索引值,不要马上返回
- 向 mid 索引值的左边扫描,将所有满足要求的元素的下标加入到集合 ArrayList
- 向 mid 索引值的右边扫描,将所有满足要求的元素的下标加入到集合 ArrayList
- 将 ArrayList 返回
package search;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class BinarySearch2 {
public static void main(String[] args) {
int[] arr = {1,5,7,34,89,89,89,345,1234};
int left = 0;
int right = arr.length-1;
ArrayList res = binary(arr,89,left,right);
System.out.println(res.toString());
}
public static ArrayList<Integer> binary(int[] arr, int value, int left, int right){
int mid = (left + right)/2;
int midValue = arr[mid];
if(left>right){
return new ArrayList<Integer>();
}
if(value > midValue){ // 向右递归
return binary(arr,value,mid+1,right);
}else if(value < midValue){
return binary(arr,value,left,mid-1);
}else{
ArrayList<Integer> list = new ArrayList<Integer>();
int temp = mid - 1;
while(true){ // 因为是有序序列,因此向两边找,如果相同,则返回
if (temp<0 || arr[temp]!=value){
break;
}
list.add(temp);
temp -= 1;
}
list.add(mid);
temp = mid + 1;
while(true){
if(temp>arr.length || arr[temp]!=value){
break;
}
list.add(temp);
temp += 1;
}
return list;
}
}
}
插值查找
-
类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找
-
将折半查找中的求 mid索引的公示,low 表示左边索引 left,high 表示右边索引 right,key 指的是要找的那个人
-
int midIndex = low + (high - low)*(key - arr[low]) / (arr[high] - arr[low])
interpolation
package search;
import javax.sound.midi.MidiChannel;
import java.util.Arrays;
public class InsertSearch {
public static void main(String[] args) {
// int[] arr = new int[100];
// for (int i = 0; i < 100; i++) {
// arr[i] = i + 1;
// }
int[] arr = {1,3,6,67,456,2345};
int index = insert(arr,0,arr.length-1,3);
System.out.println(index);
}
// insert search
public static int insert(int[] arr,int left,int right,int value){
if(left>right || value < arr[0] || value > arr[arr.length-1]){
return -1;
}
int midIndex = left + (right - left)*(value - arr[left]) / (arr[right] - arr[left]);
int midValue = arr[midIndex];
if(value>midValue){ // 向右边递归
return insert(arr,midIndex+1,right,value);
} else if(value<midValue){ // 向左递归
return insert(arr,left,midIndex-1,value);
} else{ // find it!
return midIndex;
}
}
}
对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快
关键字分布不均,折半查找较好
斐波那契查找算法
mid = low + F(k-1) - 1
F(k) = F(k-1) + F(k-2) 可以得到 F[k] -1 = (F[k-1]-1) + (F[k-2]+1)
需要顺序表的长度为 F[k] -1,则可以将该表分为长度为 F[k-1]-1 和 F[k-2]+1两部分,中间位置为 mid = low + F(k-1) - 1
如果顺序表的长度n不是 F[k] -1,则需要将原来的长度n添加至 F[k] -1,都赋值为n
借助斐波那契数列找到黄金分割点,先把数组扩成能找到分割点的长度
package search;
import java.util.Arrays;
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(fibSearch(arr,100));
}
// get fibonacci
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;
}
// search
// not recursion
public static int fibSearch(int[] a,int key){
int low = 0;
int high = a.length-1;
int k = 0; // the index of fib splitting data
int mid = 0;
int f[] = fib();
// get k
while(high>f[k]-1){
k++;
}
// high = 5
// f = 1,1,2,3,5,8,13,21,34,...
// k = 5
// f[k] = 8
// 所以需要新建一个数组 copy原数组,且新数组的长度为8,
// 超过的部分填充为原数组的最后一个数
// f[k] may greater than a.length, so Arrays is used to build
// a new array points to a[]
int[] temp = Arrays.copyOf(a,f[k]);
for (int i = high+1; i < temp.length; i++) {
temp[i] = a[high];
}
while(low<=high){
mid = low+f[k-1]-1;
if(key<temp[mid]){ // search left of the array
high = mid-1;
k--;
// 全部元素=前面的元素+后面的元素
// 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 = low+f[k-1-1]-1
} else if (key>temp[mid]){ // search right
low = mid + 1;
k-=2;
// why k-2 ?
// 全部元素=前面的元素+后面的元素
// F[K] = F[K-1]+F[K-2]
// 因为后面有F[K-2]个元素,所以可以继续拆分 F[K-2]=F[K-3]+F[K-4]
// 即在 F[K-2]的前面进行查找 k-=2
// 下次循环 mid = f[k-1-2]-1
}else{
// find it
if(mid<=high){
return mid;
}else{
return high;
}
}
}
return -1;
}
}