1、查找算法

  • 常用的查找算法有
    • 线性查找
    • 二分查找法
    • 插值查找法
    • 菲波那契(黄金分割点)查找
  • 统一的接口 ```java package com.daijunyi.structure.search;

/**

  • @author djy
  • @createTime 2022/2/28 下午2:35
  • @description / public interface Search { /*

    • 查找其中一个就行
    • @param list
    • @param query
    • @return */ int indexOf(int[] list,int query);

      /**

    • 找到所有
    • @param list
    • @param query
    • @return */ int[] indexOfAll(int[] list,int query);

}

  1. <a name="Whhr9"></a>
  2. ## 1、线性查找
  3. - 通过遍历从头到尾查找值
  4. - 不需要数据是有序的
  5. <a name="cdIGF"></a>
  6. ### 1、线性搜索代码
  7. ```java
  8. package com.daijunyi.structure.search.impl;
  9. import com.daijunyi.structure.search.Search;
  10. /**
  11. * @author djy
  12. * @createTime 2022/2/28 下午2:31
  13. * @description
  14. */
  15. public class SeqSearch implements Search {
  16. @Override
  17. public int indexOf(int[] list, int query) {
  18. for (int i=0;i<list.length;i++){
  19. if (list[i] == query){
  20. return i;
  21. }
  22. }
  23. return -1;
  24. }
  25. @Override
  26. public List<Integer> indexOfAll(int[] list, int query) {
  27. ArrayList<Integer> integers = new ArrayList<>();
  28. for (int i=0;i<list.length;i++){
  29. if (list[i] == query){
  30. integers.add(i);
  31. }
  32. }
  33. return null;
  34. }
  35. }

2、测试

    public static void main(String[] args) {
        int[] list = {1,2,3,4,4,5,3,6};
        Search search = new SeqSearch();
        int result = search.indexOf(list, 3);
        System.out.println("结果:"+result);
    }
  • 输出结果为

    结果:2
    

    2、二分查找法

  • 需要被查找的数组是有序的

    1、思想实现步骤

  • 首先确定该数组的中间的下标mid=(left+right)/2

  • 然后让需要查找的数findVal和arr[mid]比较
    • findVal > arr[mid],递归向右查找
    • findVal<arr[mid],递归向左查找
    • findVal==arr[mid]说明找到,就返回
  • 递归退出条件
    • 找到就结束递归
    • 递归完整个数组,仍然没有找到findVal,当left>left就需要退出

      2、代码实现

      1、单值查找

      ```java package com.daijunyi.structure.search.impl;

import com.daijunyi.structure.search.Search;

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

/**

  • @author djy
  • @createTime 2022/2/28 下午3:03
  • @description */ public class BinarySearch implements Search { @Override public int indexOf(int[] list, int query) {
     return search(list, query, 0, list.length-1);
    
    } /*
    • @param list 查询的列表
    • @param query 需要被查询的值
    • @param left 左边开始角标
    • @param right 右边开始角标
    • @return */ public int search(int[] list, int query, int left, int right) { if (left > right) {
       return -1;
      
      } int mid = (left + right) / 2; int result = list[mid]; if (query > result) {
       return search(list, query, mid + 1, right);
      
      } else if (query < result) {
       return search(list, query, left, mid - 1);
      
      } else {
       return mid;
      
      } } }
<a name="YX2SC"></a>
#### 2、多值查找

- 多值查找就是在找到mid的时候,进行向左和向右扫描
```java
package com.daijunyi.structure.search.impl;

import com.daijunyi.structure.search.Search;

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

/**
 * @author djy
 * @createTime 2022/2/28 下午3:03
 * @description
 */
public class BinarySearch implements Search {
    @Override
    public List<Integer> indexOfAll(int[] list, int query) {
        return searchAll(list,query,0,list.length-1);
    }

    /**
     *
     * @param list 查询的列表
     * @param query 需要被查询的值
     * @param left 左边开始角标
     * @param right 右边开始角标
     * @return
     */
    public List<Integer> searchAll(int[] list, int query, int left, int right) {
        if (left > right) {
            return null;
        }
        int mid = (left + right) / 2;
        int result = list[mid];
        if (query > result) {
            return searchAll(list, query, mid + 1, right);
        } else if (query < result) {
            return searchAll(list, query, left, mid - 1);
        } else {
            //向左和向右进行扫描
            ArrayList<Integer> integers = new ArrayList<>();
            int index = mid-1;
            while (index >= 0 && list[index] == query){
                integers.add(index);
                index--;
            }
            index = mid+1;
            while (index < list.length && list[index] == query){
                integers.add(index);
                index++;
            }
            integers.add(mid);
            //排序返回
            integers.sort(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1-o2;
                }
            });
            return integers;
        }
    }
}

3、测试

    public static void main(String[] args) {
        int[] list = {1,2,3,4,5,6,9,10,28,100,100,100,101,103};
//        Search search = new SeqSearch();
        Search search = new BinarySearch();
        int result = search.indexOf(list, 3);
        System.out.println("结果:"+result);
        List<Integer> integers = search.indexOfAll(list, 100);
        for (Integer item:integers){
            System.out.println("多个结果查找:"+item);
        }
    }
  • 结果

    结果:2
    多个结果查找:9
    多个结果查找:10
    多个结果查找:11
    

    3、插值查找法

    1、二分查找存在的问题

  • 当我们拥有一个有序的0到100的数时,我们通过查找一个数字1,需要查找至少5次以上才能找到,但是我们这是一个有序的数组,并且值基本都是连续的,所有有没有更好的方式来定位这个二分查找的点呢

    2、原理

  • 插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找

  • 公式
    • image.png
  • 换成我们这里公式:int mid = left+(right-left)*(query-list[left])/(list[right]-list[left])

    3、代码实现

    1、抽取公共接口方法Search

    ```java package com.daijunyi.structure.search;

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

/**

  • @author djy
  • @createTime 2022/2/28 下午2:35
  • @description / public interface Search { /*

    • 查找其中一个就行
    • @param list
    • @param query
    • @return */ int indexOf(int[] list,int query);

      /**

    • 找到所有
    • @param list
    • @param query
    • @return */ List indexOfAll(int[] list, int query);

      /**

    • 根据已经查找的中间值进行查找到所有
    • @param list 查找的列表
    • @param query 需要查找的值
    • @param mid 已经确定的中间值
    • @return */ default ArrayList getAllByMiddle(int[] list, int query, int mid) { //向左和向右进行扫描 ArrayList integers = new ArrayList<>(); int index = mid-1; while (index >= 0 && list[index] == query){
       integers.add(index);
       index--;
      
      } index = mid+1; while (index < list.length && list[index] == query){
       integers.add(index);
       index++;
      
      } integers.add(mid); //排序返回 integers.sort(new Comparator() {
       @Override
       public int compare(Integer o1, Integer o2) {
           return o1-o2;
       }
      
      }); return integers; }

}

<a name="TBw6b"></a>
#### 2、实现插值算法

- InterpolationSearch
- 关键性代码:mid = left+(right-left)*(query-list[left])/(list[right]-list[left]);
```java
package com.daijunyi.structure.search.impl;

import com.daijunyi.structure.search.Search;

import java.util.List;

/**
 * @author djy
 * @createTime 2022/2/28 下午3:48
 * @description
 */
public class InterpolationSearch implements Search {
    @Override
    public int indexOf(int[] list, int query) {
        return search(list,query,0,list.length-1);
    }

    /**
     *
     * @param list 查询的列表
     * @param query 需要被查询的值
     * @param left 左边开始角标
     * @param right 右边开始角标
     * @return
     */
    public int search(int[] list, int query, int left, int right) {
        if (left > right) {
            return -1;
        }
        int mid = left+(right-left)*(query-list[left])/(list[right]-list[left]);
        int result = list[mid];
        if (query > result) {
            return search(list, query, mid + 1, right);
        } else if (query < result) {
            return search(list, query, left, mid - 1);
        } else {
            return mid;
        }
    }

    @Override
    public List<Integer> indexOfAll(int[] list, int query) {
        return searchAll(list,query,0,list.length-1);
    }

    public List<Integer> searchAll(int[] list, int query, int left, int right) {
        if (left > right) {
            return null;
        }
        int mid = left+(right-left)*(query-list[left])/(list[right]-list[left]);
        int result = list[mid];
        if (query > result) {
            return searchAll(list, query, mid + 1, right);
        } else if (query < result) {
            return searchAll(list, query, left, mid - 1);
        } else {
            return getAllByMiddle(list,query,mid);
        }
    }
}

4、菲波那契(黄金分割点)查找

1、名词

1、什么叫黄金分割点

黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是 0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。

2、什么叫斐波那契数列

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

2、斐波那契(黄金分割法)原理

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

1、对F(k-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]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid=low+F(k-1)-1
  • 类似的,每一子段也可以用相同的方式分割
  • 但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1。这里的k值只要能使得F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1到F[k]-1位置),都赋为n位置的值即可。
    while(n>fib(k)-1)
    k++;
    

    2、应用

    1、代码实现查找一个

    ```java package com.daijunyi.structure.search.impl;

import com.daijunyi.structure.search.Search;

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

/**

  • @author djy
  • @createTime 2022/2/28 下午5:00
  • @description */ public class FibonacciSearch implements Search { @Override public int indexOf(int[] list, int query) {

     int left = 0;
     int right = list.length-1;
     int[] f = fib();
     int k = 0;
     //得到是第几个斐波那契
     while (right > f[k]-1){
         k++;
     }
     //进行原来数组扩充
     int[] tmp = Arrays.copyOf(list,f[k]);
     //tmp现在里面的最后那些是用0填充的,我们需要改为最高位填充
     for (int i=right;i<tmp.length;i++){
         tmp[i] = tmp[right];
     }
    
     //开始查找
     while (left <= right){
         int mid = left+f[k-1]-1;
         int result = tmp[mid];
         if (query < result){
             //向左
             right = mid-1;
             k--;
             //解释 因为f[k] = f[k-1]+f[k-2] => f[k-2]+f[k-1]=f[k];  这里我们就可以这样理解 f[k-2] 相当于left f[k-1]相当于right
             //因为我们是左移动所以我们要把修改right位置 相当于我们要移动到f[k-1] 所以我们要对k--
         }else if (query > result){
             //向右查找
             left = mid+1;
             k -= 2;
             //根据上面的解释,我们现在要右移动,所以我们要修改left值,所以我们就移动到f[k-2]所以k-2
         }else{
             return  mid < list.length ? mid : list.length-1;
         }
     }
     return -1;
    

    }

    /**

    • 获取一个斐波那契数列
    • @return */ public int[] fib(){ int[] fib = new int[20]; fib[0] = 1; fib[1] = 1; for (int i= 2;i<fib.length;i++){
       fib[i] = fib[i-1]+fib[i-2];
      
      } return fib; } }
<a name="ivocE"></a>
#### 2、测试
```java
    public static void main(String[] args) {
        int[] list = {1, 2, 3, 4, 5, 6, 9, 10, 28, 100, 100, 100, 101, 103};
        Search search = new FibonacciSearch();
        int result = search.indexOf(list, 3);
        System.out.println("结果:" + result);
    }
  • 结果

    结果:2
    

    2、哈希表

    1、哈希表的介绍

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
    image.png

    2、结构

  • 数组+链表

  • 数组+链表+二叉树

image.png

3、代码实现一个简单的HasHTab

1、创建代码HashTab

  • put方法
  • get方法
  • remove方法 ```java package com.daijunyi.structure.hash; /**

    • @author djy
    • @createTime 2022/3/1 下午5:11
    • @description */ public class HashTab { private Entry[] table;

      private int size;

      public HashTab() {

      super();
      this.size = 10;
      table = new Entry[10];
      

      }

      public HashTab(int size) {

      this.size = size;
      table = new Entry[size];
      

      }

      public V put(K key,V value){

      if (value == null){
          throw new NullPointerException();
      }
      int hash = key.hashCode();
      final Entry<K,V>[] tab = table;
      int index = getIndex(hash);
      Entry<K, V> kvEntry = tab[index];
      //替换逻辑
      while (kvEntry != null){
          if (kvEntry.hash == hash && kvEntry.key.equals(key)){
              //说明是同一个
              V old = kvEntry.value;
              kvEntry.value = value;
              return old;
          }
          kvEntry = kvEntry.next;
      }
      //把新的值加到第一个
      tab[index] = new Entry(key,value,hash,tab[index]);
      return null;
      

      }

      public V get(K key){

      if (key == null){
          throw new NullPointerException();
      }
      int hash = key.hashCode();
      int index = getIndex(hash);
      Entry<K, V> kvEntry = table[index];
      while (kvEntry != null){
          if (kvEntry.hash == hash && kvEntry.key.equals(key)){
              return kvEntry.value;
          }
          kvEntry = kvEntry.next;
      }
      return null;
      

      }

      public V remove(K key){

      if (key == null){
          throw new NullPointerException();
      }
      int hash = key.hashCode();
      int index = getIndex(hash);
      Entry<K, V> kvEntry = table[index];
      if (kvEntry!=null && kvEntry.hash == hash && kvEntry.key.equals(key)){
          table[index] = kvEntry.next;
          return kvEntry.value;
      }
      Entry<K,V> tmp = kvEntry;
      kvEntry = kvEntry.next;
      while (kvEntry != null){
          if (kvEntry.hash == hash && kvEntry.key.equals(key)){
              tmp.next = kvEntry.next;
              return kvEntry.value;
          }
          kvEntry = kvEntry.next;
          tmp = tmp.next;
      }
      return null;
      

      }

      private int getIndex(int hash) {

      return (hash & 0x7FFFFFFF) % size;
      

      }

      private static class Entry{

      V value;
      final K key;
      final int hash;
      Entry<K,V> next;
      
      public Entry(K key, V value,int hash, Entry<K, V> next) {
          this.value = value;
          this.key = key;
          this.hash = hash;
          this.next = next;
      }
      

      } }

<a name="I9F6s"></a>
### 2、测试
```java
class HashTabMain{
    public static void main(String[] args) {
        HashTab<String, String> tab = new HashTab<>();
        tab.put("123","值2");
        tab.put("12","值4");
        System.out.println("获取值:"+tab.get("123"));
        System.out.println("替换值:"+tab.put("123","值3"));
        System.out.println("获取值:"+tab.get("123"));
        System.out.println("删除值:"+tab.remove("123"));
        System.out.println("再次获取值:"+tab.get("123"));
        System.out.println("获取值:"+tab.get("12"));
    }
}

3、测试结果

获取值:值2
替换值:值2
获取值:值3
删除值:值3
再次获取值:null
获取值:值4