二分查找
原理:给定一个有序的数组,每次从中间开始查找,判断查找的val与中间值的比较,大于继续向后查找,小于向前查找。
步骤:
- 得到一个有序的数组。
- 计算中间索引,mid = (left + right)/ 2
- 与待查找的值进行比较,相等返回mid。
- 不相等,向左或向右重复2,3步。
JAVA代码实现:
/**
* 二分查找
*
* @param arr 排序好的数组
* @param left 数组左下标
* @param right 数组右下标
* @param findVal 待查找的值
* @return 查找值的下标
*/
public static int binarySearch(int[] arr, int left, int right, int findVal) {
// 左下标大于右下标,找不到该值
if (left > right) {
return -1;
}
// 计算mid
int mid = (left + right) / 2;
int searchVal = arr[mid];
if (findVal > searchVal) {
// 大于查找的值,向右递归查找
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < searchVal) {
// 小于查找的值,向左递归查找
return binarySearch(arr, left, mid - 1, findVal);
}
return mid;
}
如果数组有重复数据,可以返回一个集合,存储所有的下标。
/**
* 查找符合条件的值的下标
* 参数同二分查找
*
* @param arr
* @param left
* @param right
* @param findVal
* @return
*/
public static List<Integer> binarySearchAll(int[] arr, int left, int right, int findVal) {
if (left > right) {
return null;
}
int mid = (left + right) / 2;
int searchVal = arr[mid];
if (findVal > searchVal) {
return binarySearchAll(arr, mid + 1, right, findVal);
} else if (findVal < searchVal) {
return binarySearchAll(arr, left, mid - 1, findVal);
}
// 创建一个list集合,添加查找到的mid下标
List<Integer> result = new ArrayList<Integer>();
result.add(mid);
// 将mid-1得到查找值的左边下标
int temp = mid - 1;
while (true) {
// 一直循环,直到左边没有查找的值
if (temp < 0 || arr[temp] != findVal) {
break;
}
result.add(temp);
temp--;
}
temp = mid + 1;
while (true) {
// 一直循环,直到右边没有查找的值
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
result.add(temp);
temp++;
}
return result;
}
插值查找
跟二分查找大部分相同,只是计算mid位置的时候不一样,该查找算法适合数组中元素相差较大时使用。
mid计算:mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]),计算查找的值在数组中的比例。
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
// 除了计算mid不一样 其它跟二分一样
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 {
return mid;
}
}
斐波那契查找
斐波那契数列:开始为1,1后面每位数字都是前两个数字相加。
例如:1 1 2 3 5 8 13…
这个数列,越往下就越趋近于0.618,也就是黄金分割,所以该查找就是构建一个斐波那契数列来找到数组中的黄金分割点,进行查找。
有一个待查找的数组,假如长度只有7,则循环遍历斐波那契的数组,发现数组中8大于7,而数组长度只有7,构建一个长度为8的新数组,把原数组copy过去,多出来的部分使用原数组的最大值进行填充。(从小到大排序的),记录当前斐波那契数组下标为k。
现在就有一个长度8的数组,然后找到k - 1的位置下的斐波那契数,分割点就是mid = fib[k - 1] + 查找数组的最小下标(low) - 1,数组就分割成了0 1 2 3 【4】 5 6 7,然后发现这个数组位置的数据不是自己要的,判断是大于还是小于,小于就往左找,查找数组的最大下标(high) = mid - 1,将k自减,继续上面步骤。大于往右找,k自减2,mid+1,重复上面步骤。
JAVA代码实现:
// 获取斐波那契数列
public static int[] fib(int maxSize) {
// 定义一个斐波那契数列
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
// 从2开始,后面数组中的值等于前两项相加
for (int i = 2; i < maxSize; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
/**
*
* @param arr 待查找的数组
* @param key 查找的值
* @return 找到的下标值
*/
public static int fibSearch(int[] arr, int key) {
// 数组查找的最小下标
int low = 0;
// 最大下标
int high = arr.length - 1;
// 斐波那契数组的下标
int k = 0;
// 分割点
int mid = 0;
// 获取斐波那契数组
int[] fib = fib(20);
// 找到不小于high的斐波那契数
while (high > fib[k] - 1) {
k++;
}
// fib[k]值可能大于arr的长度,因此需要构造新的数组,并指向arr
int[] temp = Arrays.copyOf(arr, fib[k]);
for (int i = high + 1; i < temp.length; i++) {
// 用数组最大值填充多余部分
temp[i] = arr[high];
}
// 当最小下标小于等于最大下标时,一直循环
while (low <= high) {
// 计算分割点
mid = low + fib[k - 1] - 1;
if (key < temp[mid]) {
// key的值小于mid值 向左查找,k自减
high = mid - 1;
k--;
} else if (key > temp[mid]) {
// 向右查找,k自减2
low = mid + 1;
k -= 2;
} else {
// key等于mid 当mid小于等于high的时候,返回mid
if (mid <= high) {
return mid;
} else {
// 大于high的时候证明找到了扩充的数据,返回high
return high;
}
}
}
return -1;
}