查找算法:
在java中,常用的查找算法有四种:
1、顺序(线性)查找
2、二分查找(必须掌握)
3、插值查找
4、斐波拉契查找(黄金分割点查找)
① 顺序查找:
思路一:找到第一个值为value的数的下标
public static int seqSearch(int[] arr, int value) {
// 线性查找是逐一比对,发现有相同值时,返回下标
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value)
return i;
}
return -1;
}
思路二:找到值为value数的所有位置
//查找数列里数value出现的所有位置
public static ArrayList<Integer> seqSearchArr(int[] arr, int value) {
ArrayList<Integer> temp = new ArrayList<Integer>();
int index = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value)
temp.add(i);
}
System.out.println("值为" + value + "的数,出现在该数列的序号有:");
for(int i = 0 ; i <temp.size() ; i++){
System.out.printf("%d " , temp.get(i));
}
System.out.println();
return temp;
}
② 二分查找:
思路:
1、首先确定该数组的中间下标 : mid = ( left + right ) / 2 ;
2、然后让需要查找的数findVal 和 arr[mid]进行比较;
a、findVal > arr[mid],说明要查找的数在mid的右边,因此需要递归从右侧查找;
b、findVal < arr[mid],说明要查找的数在mid的左边,因此需要递归从左侧查找;
c、findVal == arr[mid],说明找到,返回数值
3、结束递归的几种情形:
a、找到了需要查找的数;
b、递归完整个数组,仍然没有找到findVal,也需要结束递归,当left > right时,需要退出(找不到)。
4、使用二分查找的前提:该数组必须是有序的。
package Search;
import java.util.ArrayList;
import java.util.Arrays;
public class BinarySearch {
// 二分查找:该数组必须是有序的
public static void main(String[] args) {
int arr[] = { 1, 8, 10, 89, 1000, 1000, 1000, 1234 };
ArrayList<Integer> temp = new ArrayList<Integer>();
ArrayList<Integer> resIndex = binarySearchArr(arr, 0, arr.length - 1, 1000);
System.out.println("resIndex = " + resIndex);
}
/**
* 二分查找算法(基本写法,还可以升级)
*
* @param arr
* @param left
* 左边的索引
* @param right
* @param findVal
* 需要查找的数
* @return 如果找到,返回下标;反之返回 -1
*/
public static int binarySearch(int[] arr, int left, int right, int findVal) {
// 当left > right 时,说明找不到
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;
}
}
/**
* 加餐:课后思考题 对于{1,8,10,89,1000,1000,1234}这样的有序数列中,含有多个相同的值 如何将所有的数值都查找到,如1000
*
* 思路分析: 1、找到mid值时,不要马上返回 2、向mid
* 索引值的左边扫描,将所有满足查找值如1000的元素的下标加入到一个集合ArrayList中 3、向mid
* 索引值的右边扫描,将所有满足查找值如1000的元素的下标加入到一个集合ArrayList中
*/
public static ArrayList<Integer> binarySearchArr(int[] arr, int left, int right, int findVal) {
// 当left > right 时,说明找不到
if (left > right)
return new ArrayList<Integer>();
// 否则才向下查找
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearchArr(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearchArr(arr, left, mid - 1, findVal);
} else {
// 在找到mid的索引值后,不要马上返回,而是将其加入到一个ArrayList集合中
ArrayList<Integer> resIndexList = new ArrayList<Integer>();
// 向mid索引的左边扫描,将所有满足1000的元素的下标加入到集合中
int temp = mid - 1;
while (true) {
if (temp < 0 || arr[temp] != findVal) {
// 如果扫描到了最左端,或是扫描到了不是findVal的值,说明已经找不到了
break;
}
resIndexList.add(temp);// 把mid左边合格的下标放进去
temp--;
}
resIndexList.add(mid);// 然后把mid放入集合中,接着往mid右边扫描
temp = mid + 1;
while (true) {
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
resIndexList.add(temp);
temp++;
}
return resIndexList;
}
}
}
③ 插值查找:(前提也是有序数列)
主要是对二分查找的优化,用到了公式:
mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left])
通过对中间值进行了加权改进,在数据大小比较连续,关键字比较均匀的情形下,会有效减少定位次数。但如果关键字之间差距较大,效率可能低于二分查找。
public class InsertValueSearch {
// 插值查找算法
static int count = 0;//查找次数打印
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = i + 1;
}
// System.out.println(Arrays.toString(arr));
int index = insertValueSearch(arr, 0, arr.length - 1, 66);
System.out.println("index = " + index);
}
/**
* 插值查找算法(也要求数组必须有序)
*
*/
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
// 这个判断很重要,它不仅可以起到优化作用,而且必须需要(插值查找的公式要求)
count ++ ;
System.out.println("查找第" + count + "次");
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]);
int midVal = arr[mid];
if (findVal > midVal) {
return insertValueSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return insertValueSearch(arr, left, mid - 1, findVal);
} else {
System.out.println("已经找到位置,标号为: " + mid);
return mid;
}
}
}
④ 黄金分割查找法:
(1)黄金分割点:把一条线段分割成两个部分,其中一部分与全长之比 等于 另一部分与该部分之比,约为0.618。
(2)斐波拉契数列:1、1、2、3、5、8、13、21、34、55......发现斐波拉契数列的两个相邻数的比例,无限接近与0.618。因此斐波拉契数列来实现黄金分割查找,在查找中找到黄金分割点。
(3)mid = low + F(k -1 ) -1 ,之所以后面要 -1,是因为序号从 0 开始
原始斐波拉契递推式为 :
F( k ) - 1 ={ F( k - 1 ) - 1 + F(k - 2) - 1 }+ 1
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("index = " + fibSearch(arr, 8));
}
//因为后面我们必须使用到斐波拉契数列,所以构造一个方法用来获取斐波拉契数列
public static int[] fib(){
//这里用非递归的方式
int[] f = new int[maxSize];//用于获取斐波拉契数列的前maxSize个元素
f[0] = 1 ;
f[1] = 1 ;
for(int i = 2 ; i < maxSize ; i ++)
f[i] = f[i - 1] + f[i - 2 ];
return f ;
}
/** 使用非递归方式编写斐波拉契查找算法
*
* @param arr
* @param key
* @return 返回对应下标,没有找到就返回 -1
*/
public static int fibSearch(int[] arr , int key){
int low = 0 ;
int high = arr.length - 1 ; //high前半部分的数是数列存在的数,后半部分的数是补全数
int k = 0 ; //表示斐波拉契分割数值的下标fib(k)
int mid = 0 ;
int f[] = fib();
//接下来获取斐波拉契数列的下标:
while(high > f[k] - 1 ){
//遍历斐波拉契数列,看这个数组的元素个数需要用数列里的多少个数
k ++ ;
}
//因为f[k]的值可能大于 arr 的长度,因此我们需要使用Arrays类,因为构造一个新的数组,并指向arr
//不足的部分暂时使用 0 来填充
int[] temp = Arrays.copyOf(arr, f[k]);
//实际上需要使用arr数组最后的数来填充temp,使其满足斐波拉契数列的长度
//即{1,8,10,89,1000,1234,0,0} ->{1,8,10,89,1000,1234,1234,1234}
for(int i = high + 1 ; i < temp.length ; i++){
temp[i] = arr[high];
}
//使用while来循环处理,找到我们的数key
while(low <= high){
//在斐波拉契数列的查找公式,mid就是黄金分割点
mid = low + f[k - 1] - 1 ;
if(key < temp[mid]){
//往斐波拉契数列中值的左侧查找
high = mid - 1 ;
k -- ;
/**
* 之所以这里要k--,是因为
* 1、全部元素 = 前面的元素 + 后面的元素,即f[k] = f[k - 1] + f[k - 2]
* 因为前面有f[k - 1]个元素,所以如果在前部分查找,那前部分可以继续拆分f[k-1] = f[k-2] + f[k -3]
*
* 所以在前半部分查找,使下次循环是 mid = low + f[k-1-1] - 1
*/
}else if(key > temp[mid]){
low = mid + 1 ;
k -= 2 ; // 在数组黄金分割点的后半部分,即要在f[k-2]部分查找
//下次查找时,mid =low + f[k-1-2] -1
}else{
//找到了要查找的元素(key == temp[mid])
if(mid <= arr.length - 1) return mid;
else return arr.length - 1 ; //说明查找得到的元素是补全值
}
}
return -1;//如果在整个while循环中没有遇到return语句,说明没找到,返回-1
}
}