线性查找分析和实现

题目要求

有一个数组[1,9,11,-1,43,89,11,3,12,11],判断数组是否包含此名称(顺序查找);

要求:如果找到就返回下标值;

代码实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.jokerdig.search;

/**
* @author Joker大雄
* @data 2022/10/20 - 10:18
**/
// 线性查找
public class SeqSearch {
public static void main(String[] args) {
int arr[] = {1,9,11,-1,43,89,11,3,12,11};// 定义一个无序数组
int back = seqSearch(arr, 11);
if(back==-1){
System.out.println("没有找到")
}else{
System.out.println("下标为:"+back);
}
}
// 这里线性查找是找到一个满足条件的就返回
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
2
3
下标为:2

Process finished with exit code 0

思考:如何查找到全部符合条件的值?

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.jokerdig.search;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @author Joker大雄
* @data 2022/10/20 - 10:18
**/
// 线性查找
public class SeqSearch {
public static void main(String[] args) {
int arr[] = {1,9,11,-1,43,89,11,3,12,11};// 定义一个无序数组
List list = seqSearch(arr, 11);
System.out.println("下标为:"+list);
}
// 查找所有符合条件的值的下标
public static List<Integer> seqSearch(int[]arr, int value){
// 定义集合,存放下标
List<Integer> list = new ArrayList<>();
// 线性查找就是逐一比对,发现有相同值,就将下标放入数组
for (int i = 0; i < arr.length; i++) {
if(arr[i] == value){
list.add(i);
}
}
return list;
}
}

运行结果

1
2
3
下标为:[2, 6, 9]

Process finished with exit code 0

二分查找思路分析

题目要求

请对一个有序数组进行二分查找{1,8,10,89,1000,1234},输入一个数查看该数组是否存在次数,并且求出下标,如果没有就提示没有这个数;

思路分析

  1. 首先确定该数组中间值的下标(left+right)/2
  2. 然后让需要查找的数和数组中间值进行比较:
    • 大于中间值,说明要查找的数在中间值的右边,因此需要递归向右查找;
    • 小于中间值,说明要查找的数在中间值的左边,因此需要递归向左查找;
    • 如果相等,那么中间值本身就是我们要查找的值;
  3. 结束递归的条件
    • 找到值,就结束递归;
    • 递归完整个数组,仍然没有找到值(left>right),也需要结束递归;

二分查找代码实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.jokerdig.search;

/**
* @author Joker大雄
* @data 2022/10/21 - 13:03
**/
// 二分查找要求数组必须是有序的
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1,8,10,89,1000,1234};
int back = binarySearch(arr,0,arr.length-1,10);
if(back==-1){
System.out.println("没有找到该值");
}else{
System.out.println("要找的值下标为:"+back);
}
}
// 二分查找算法
/**
*
* @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
2
3
要找的值下标为:2

Process finished with exit code 0

二分查找功能完善

思考题:{1,8,10,89,89,89,1000,1234}当一个有序数组中有多个相同的值时,如何将所有相同数值的下标都查找到;

思路分析

  1. 在找到要查找的值时,不要直接返回,而是继续向左(右)扫描;
  2. 将扫描符合的值的下标放入一个集合中;
  3. 最后将集合返回即可;

代码实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.jokerdig.search;

import java.util.ArrayList;
import java.util.List;

/**
* @author Joker大雄
* @data 2022/10/21 - 13:03
**/
// 二分查找要求数组必须是有序的
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1,8,10,89,89,89,1000,1234};
List<Integer> list = binarySearch1(arr,0,arr.length-1,89);
if(list.isEmpty()){
System.out.println("没有找到该值");
}else{
System.out.println("要找的值下标为:"+list);
}
}
// 二分查找多个相同值
// 定义一个List集合,存放满足条件的值的下标
static List<Integer> list = new ArrayList<>();
public static List<Integer> binarySearch1(int[] arr, int left, int right, int findVal){
// 如果left>right,说明数组查找完毕且没有找到该值
if(left>right){
return list;
}
int mid = (left+right) /2; // 中间值下标
int midVal = arr[mid]; // 中间值

if(findVal>midVal){
// 向右递归
return binarySearch1(arr,mid+1,right,findVal);
}else if(findVal<midVal){
// 向左递归
return binarySearch1(arr,left,mid-1,findVal);
}else{
// 向左扫描
int temp = mid -1;
while(true){
if(temp<0 || arr[temp] != findVal){
break;
}
// 否则就将temp放入集合中
list.add(temp);
temp -= 1; // temp左移
}
// 如果中间值是要找到值,就放入
list.add(mid);
// 向右扫描
temp = mid + 1;
while(true){
if(temp>arr.length-1 || arr[temp] != findVal){
break;
}
// 否则就将temp放入集合中
list.add(temp);
temp += 1; // temp右移
}
}
return list;// 返回集合
}
}

运行结果

1
2
3
要找的值下标为:[3, 4, 5]

Process finished with exit code 0

插值查找思路分析

基本介绍

  1. 插值查找算法类似于二分查找,不同的是插值查找每次从自适应中间值处开始查找;

  2. 将二分查找中求中间值索引的公式,low表示左边索引,high表示右边索引,key是我们要查找的值;

    image-20221022091534614

  3. 插值查找索引:int midIndex = left +(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);

  4. 举例插值查找算法1-100的数组;

    数组 arr = [1,2,3,4,5,…,100];

    例如我们需要查找的值为1;

    使用二分查找的话,我们需要多次递归,才能找到1;

    使用插值查找算法:

    公式:int midIndex = left +(right-left)*(findVal-arr[left])/(arr[right]-arr[left])

    int midIndex = 0+(99-0)*(1-1)/(100-1)=0+99*0/99=0

插值查找代码实现

代码实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.jokerdig.search;

/**
* @author Joker大雄
* @data 2022/10/22 - 9:43
**/
public class InsertValueSearch {
public static void main(String[] args) {
// 生成1-100有序数组
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = i+1;
}
int back = insertValueSearch(arr,0,arr.length-1,10);
if(back==-1){
System.out.println("没有找到该值");
}else{
System.out.println("该值的下标为:"+back);
}
}
// 编写插值查找算法(要求数组有序)
/**
*
* @param arr 数组
* @param left 左边索引
* @param right 右边索引
* @param findVal 要查找的值
* @return 如果找到就返回下标,没找到返回-1
*/
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;
}
// 求出中间值
int midIndex = left +(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
int midValue = arr[midIndex];

if(findVal>midValue){
// 向右递归
return insertValueSearch(arr,midValue+1,right,findVal);
}else if(findVal<midValue){
// 向左递归
return insertValueSearch(arr,left,midValue-1,findVal);
}else{
// 找到值
return midIndex;
}
}
}

运行结果

1
2
3
该值的下标为:9

Process finished with exit code 0

注意事项

  1. 对于数据量较大,关键字分布比较均匀数组来说,采用插值查找速度较快;
  2. 关键字分布不均匀的数组,使用二分查找更合适;

斐波那契查找思路分析

基本介绍

斐波那契又称黄金分割法,是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618,由于按照比例设计的造型十分美丽,因此称为黄金分割,也成为中外比。

斐波那契数列{1,1,2,3,5,8,13,21,34,55},可以发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618

工作原理

斐波那契查找原理与前两种相似,仅仅改变了中间节点的位置,不再是中间值或者插值来找到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列)如下图所示:

image-20221024083933345

对F(K-1)-1的理解:

  1. 由斐波那契数列F[k]=F[k-1]+F[k-2]的性质,可以得到(F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1。该式说明:只要顺序表的长度为F[k]-1,则可以将该表分成长度为F[k-1]F[k-2]-1的两段,即中间位置为mid=low+F(k-1)-1

  2. 类似的,每一子段也可以用相同的方式分割;

  3. 但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加值F[k]-1;这里的k值只要能使得F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1F[k]-1的位置),都赋为n位置的值即可。

    1
    2
    while(n>fib(k)-1)
    k++;

斐波那契查找代码实现

应用案例

请对一个有序数组进行斐波那契查找{1,8,10,89,1000,1234},输入一个数并在该数组中进行查找,如果找到就返回下标,没找到就提示:没找到该数值。

代码实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.jokerdig.search;

import java.util.Arrays;

/**
* @author Joker大雄
* @data 2022/10/24 - 9:16
**/
// 斐波那契查找
public class FibonacciSearch {
public static int maxSize = 20;// 最大值
public static void main(String[] args) {
int[] arr = {1,8,10,89,1000,1234};
int back = fibSearch(arr, 10);
if(back==-1){
System.out.println("没找到该数值");
}else{
System.out.println("下标为:"+back);
}
}
// 斐波那契查找法,需要使用到斐波那契数列
// 因此我们需要先获构建一个斐波那契数列
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;
}
// 编写斐波那契查找算法,这里没使用递归来写
/**
*
* @param a 数组
* @param key 需要查找的数
* @return 下标,没找到返回-1
*/
public static int fibSearch(int[] a,int key) {
// 定义初始下标
int low = 0;
int high = a.length - 1;
int k = 0; // 表示斐波那契分割数值的下标
int mid = 0; // 存放mid
int f[] = fib(); // 获取斐波那契数列
// 获取斐波那契分隔值的下标
while (high > f[k] - 1) {
k++;
}
// 因为f[k]的值,可能大于a的长度
// 因此我们需要使用Arrays类,构造新的数组并指向temp[]
int[] temp = Arrays.copyOf(a, f[k]); // 不足的部分会使用0填充
// 需要使用a数组的最后的数填充temp
for (int i = high + 1; i < temp.length; i++) {
temp[i] = a[high];
}
// 使用while来循环处理,找到我们的数key
while (low <= high) {
// 只要这个条件满足,我们就可以找中间值
mid = low + f[k - 1] - 1;
if (key < temp[mid]) {
// 说明我们应该向数组左边查找
high = mid - 1;
// 1. 全部元素 = 前面的元素 + 后边元素
// 2. 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 = f[k-1-1]-1
k--;
} else if (key > temp[mid]) {
// 继续向数组的右边查找
low = mid + 1;
// 1.全部元素 = 前面的元素 + 后边元素
// 2. f[k] = f[k-1] + f[k-2]
// 3. 因为后面有f[k-2],所以可以继续拆分f[k-1] = f[k-3] + f[k-4]
// 在f[k-2]的前面进行查找k-=2
// 下次循环mid = f[k-1-2]-1
k -= 2;
} else {
// 找到了,并判断返回哪个下标
if (mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
}

运行结果

1
2
3
下标为:2

Process finished with exit code 0