在 java 中,我们常用的算法有四种:顺序(线性)查找、二分(折半)查找、插值查找、斐波那契查找。
线性查找:遍历数组,如果找到了就提示找到,并给出下标值。数组可以是有序也可以是无序的。
代码实现:
package search;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SeqSearch {
public static void main(String[] args) {
int[] arr = { 1, 9, 2, 6, 11, -2, 2 };
int index1 = seqSearch(arr,3);
int[] index2 = seqSearch2(arr,3);
if (index1 == -1 ){
System.out.println("没有查找到对应值。");
}else {
System.out.println("对应值的索引为:" + index1);
}
if (index2.length == 0){
System.out.println("没有查找到对应值。");
}else {
System.out.println("对应值的索引为:" + Arrays.toString(index2));
}
}
//返回第一个结果
public static int seqSearch(int[] arr, int value){
//线性查找就是逐一比对,发现有相同值,就返回下标
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value)
return i;
}
return -1;//没有找到就返回 -1
}
//返回多个结果
public static int[] seqSearch2(int[] arr, int value){
//线性查找就是逐一比对,发现有相同值,就返回下标
List<Integer> list = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value)
list.add(i);
}
if (list == null){
return null;
}
int[] res = new int[list.size()];
int index = 0;
for (int i = 0; i < list.size(); i++) {
res[ index++ ] = list.get(i);
}
return res;
}
}
二分查找
思路分析:
1. 首先确定该数组中间的下标 mid = (left + right) / 2
2. 然后让需要查找的数 findVal 和 arr[ mid ] 进行比较
2.1 findVal > arr[ mid ] ,说明你要查找的数在 mid 的右边,因此需要递归地向右查找;
2.2 findVal < arr[ mid ] ,说明你要查找的数在 mid 的左边,因此需要递归地向左查找;
2.3 findVal == arr[ mid] ,说明找到,就返回
注意:什么时候结束递归:1. 找到目标值就结束递归;2. 递归完整个数组,仍然没有找到 findVal,即 left > right 时就需要退出递归。
代码实现:
package search;
public class BinarySearch {
public static void main(String[] args) {
int[] arr = { 2,6,9,13,17,22,29,35,46,59,63,77 };//二分查找需要有序
int targetVal = 65;
int index = binarySearch(arr,0,arr.length-1, targetVal);
int index2 = binarySearch2(arr,targetVal);
if (index == -1){
System.out.println("递归版二分查找:未查找到该值!");
}else {
System.out.println("递归版二分查找 " + targetVal + " 对应的索引值为:" + index);
}
if (index2 == -1){
System.out.println("循环版二分查找:未查找到该值!");
}else {
System.out.println("循环版二分查找 " + targetVal + " 对应的索引值为:" + index2);
}
}
//二分查找递归版本
public static int binarySearch(int[] arr, int left, int right, int targetVal){
//判断 left > right ? 结束递归
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
int midValue = arr[mid];
if (targetVal > midValue){//向右递归
return binarySearch(arr,mid+1,right,targetVal);
}else if (targetVal < midValue){
return binarySearch(arr,left,mid-1,targetVal);
}else {
return mid;
}
}
//二分查找循环版本
public static int binarySearch2(int[] arr,int targetVal){
int left = 0;
int right = arr.length;
while (left <= right){
int mid = (left + right)/2;
if (targetVal > arr[mid])
left = mid + 1;
else if (targetVal < arr[mid])
right = mid - 1;
else
return mid;
}
return -1;
}
}
升级版:如果一个有序数组中有多个相同的数值时,如何将所有的数值都查找到并返回呢?
思路分析:
1. 当找到 mid 索引值时,不要马上返回;
2. 向 mid 索引值的左边扫描,将所有等于目标值的元素的下标加入到集合 ArrayList;
3. 向 mid 索引值的右边扫描,将所有等于目标值的元素的下标加入到集合 ArrayList;
4. 将 ArrayList 返回。
//二分查找升级版
public static ArrayList<Integer> binarySearch3(int[] arr, int left, int right, int targetVal){
ArrayList<Integer> indexList = new ArrayList<>();
//判断 left > right ? 结束递归
if (left > right) {
return new ArrayList<>();
}
int mid = (left + right) / 2;
int midValue = arr[mid];
if (targetVal > midValue){//向右递归
return binarySearch3(arr,mid+1,right,targetVal);
}else if (targetVal < midValue){
return binarySearch3(arr,left,mid-1,targetVal);
}else {
int index = mid;
while (arr[index] == targetVal && index >= 0){
indexList.add(0,index);
index--;
}
index = mid + 1;
while (arr[index] == targetVal && index < arr.length){
indexList.add(index);
index++;
}
return indexList;
}
}
插值查找
原理介绍:
(1)插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找;
(2)将二分查找中的求 mid 的公式进行变形,targetValue 即为查找的值,
(3)int midIndex = low + (high - low)*(targetValue - arr[low]) / (arr[high] - arr[low]);
(4)插值查找算法的举例说明
数组 arr = [1, 2, 3, ..., 100],加入查找 1 或 100
二分查找需要多次递归才能找到目标值,而使用插值查找算法则有
int midIndex = low + (high - low)*(targetValue - arr[low]) / (arr[high] - arr[low]);
int mid = 0 + (99 - 0) * (1-1)/(100 - 1) = 0 + 99 * 0 / 99 = 0;
int mid = 0 + (99 - 0) * (100 - 1) / (100 - 1) = 0 + 99 * 99 / 99 = 99
这样的情况下,插值查找的效率就会高得多。
注意:对于数据量较大,关键字分布较均匀的查找表而言,采用插值查找速度较快,对于关键字分布不均匀的情况,该方法不一定比折半查找效果好。
代码实现:
package search;
import java.util.Arrays;
public class InsertSearch {
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < arr.length; i++) {
arr[i] = i + 1;
}
System.out.println(Arrays.toString(arr));
int targetValue = 98;
int index = insertSearch(arr,targetValue);
if (index == -1){
System.out.println("插值查找未能查找到该值!");
}else {
System.out.println("插值查找:目标值 " + targetValue + " 的索引值为:" + index);
}
int index2 = binarySearch2(arr,targetValue);
if (index2 == -1){
System.out.println("二分查找未能查找到该值!");
}else {
System.out.println("二分查找:目标值 " + targetValue + " 的索引值为:" + index2);
}
}
//插值查找算法也要求数组有序
public static int insertSearch(int[] arr, int targetValue){
int left = 0, right = arr.length - 1, count = 0;
if (targetValue < arr[left] || targetValue > arr[right])//防止 targetValue 越界
return -1;
while (left <= right){
int mid = left + (right - left) * (targetValue - arr[left]) / (arr[right] - arr[left]);
count++;
if (targetValue > arr[mid]){
left = mid + 1;
}else if (targetValue < arr[mid]){
right = mid - 1;
}else {
System.out.println("插值查找次数为" + count);
return mid;
}
}
System.out.println("插值查找次数为" + count);
return -1;
}
//二分查找循环版本
public static int binarySearch2(int[] arr,int targetVal){
int left = 0;
int right = arr.length;
int count= 0;
while (left <= right){
int mid = (left + right)/2;
count++;
if (targetVal > arr[mid])
left = mid + 1;
else if (targetVal < arr[mid])
right = mid - 1;
else {
System.out.println("二分查找次数为" + count);
return mid;
}
}
System.out.println("二分查找次数为" + count);
return -1;
}
}