查找算法 哈希表

顺序 (线性) 查找

代码实现:

package com.atguigu.search;

/**
 * @ClassName SeqSearchDemo
 * @Author Jeri
 * @Date 2022-02-21 11:40
 * @Description 线性查找
 */
public class SeqSearchDemo {

    /*
     * @Description 线性查找
     * @Date 2022/2/21 11:40
     * @param arr 带查找数组
     * @param value 查找数值
     * @return 返回查找数值的索引 否则 返回-1
     */
    public static int seqSearch(int[] arr,int value){
        for(int i = 0;i < arr.length;i++){
            if(arr[i] == value){
                return i;
            }
        }

        return -1;
    }

    public static void main(String[] args) {
        int arr[] = {1, 9, 11, -1, 34, 89};// 没有顺序的数组
        int index = seqSearch(arr, 11);
        if (index == -1) {
            System.out.println("没有该数值");
        } else {
            System.out.println("找到,下标为 = " + index);
        }
    }
}

找到,下标为 = 2

二分查找

思路分析:
在这里插入图片描述
说明: 二分查找 针对有序序列 进行查找

每次取中间数值进行比较,其中 mid = low + (high - low) / 2

代码展示:

package com.atguigu.search;

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

/**
 * @ClassName BinarySearchDemo
 * @Author Jeri
 * @Date 2022-02-20 22:04
 * @Description TODO
 */
public class BinarySearchDemo {

    /*
     * @Description 二分简单查找
     * @Date 2022/2/20 22:05
     * @param arr 待排序数组 默认升序
     * @param left 左索引
     * @param right  右索引
     * @param value  待查找的数值
     * @return 查找结果
     */
    public static int binarySearch(int[] arr,int left,int right,int value){
        //未找到元素,退出递归的条件

        if(left > right){
            return -1;
        }

        //正常查找
        int mid = (left + right) / 2;


        if(arr[mid] < value){//向右递归
            return binarySearch(arr,mid + 1,right,value);
        }else if(arr[mid] > value){//向左递归
            return binarySearch(arr,left,mid - 1,value);
        }else{
            return mid;
        }

    }

    /*
     * @Description 二分查找优化 将所有相同的数值查找出来
     * @Date 2022/2/21 10:16
     * @param arr 待排序数组 默认升序
     * @param left 左索引
     * @param right  右索引
     * @param value  待查找的数值
     * @return [arr, left, right, value] 查找结果
     */
    public static List<Integer> binarySearchOptimize(int[] arr, int left, int right, int value){
        //当 left > right 时 说明递归整个数组结束 没有找到
        if(left > right){
            return new ArrayList<Integer>();
        }

        int mid = (left + right) / 2;
        int midValue = arr[mid];

        if(midValue < value){//向右递归
            return binarySearchOptimize(arr,mid + 1,right,value);
        }else if(midValue > value){//向左递归
            return binarySearchOptimize(arr,left,mid - 1,value);
        }else{
            //当前 midValue == value
            //根据二分查找有序序列 所以接下来只需要在左右两侧寻找是否存在相同数值
            List<Integer> indexList = new ArrayList<>();
            //indexList 只创建一次

            //添加当前值
            indexList.add(mid);

            //向左查找
            int tempLift = mid - 1;
            while(true){
                if(tempLift < 0 || arr[tempLift] != value){ break; }
                //否则 说明 arr[tempLift] == value
                indexList.add(tempLift);
                tempLift--;
            }

            //向右查找
            int tempRight = mid + 1;
            while(true){
                if(tempLift > arr.length - 1 || arr[tempRight] != value){ break; }
                //否则 说明 arr[tempRight] == value
                indexList.add(tempRight);
                tempRight++;
            }

            //对有序序列左右两边遍历结束
            return indexList;
        }
    }


        public static void main(String[] args) {
//        int[] array = new int[]{1,8, 10, 89, 1000, 1234};
        int[] array = new int[]{1,2,2,2,3,4};
//        int findValue = 10;
        int findValue = 2;
        int index = binarySearch(array,0,array.length - 1,findValue);

        System.out.println("使用二分简单查找,结果为---------");
        if(index == -1){
            System.out.println("未找到该元素:" + findValue );
        }else{
            System.out.println("找到该元素,索引为" + index);
        }

        System.out.println();
        List<Integer> indexList = binarySearchOptimize(array,0,array.length - 1,findValue);
        System.out.println("使用二分优化查找,结果为---------");
        System.out.println("indexList = " +  indexList);
    }
}

使用二分简单查找,结果为---------
找到该元素,索引为2

使用二分优化查找,结果为---------
indexList = [2, 1, 3]

插入查找

思路分析:
在这里插入图片描述
说明: 插值查找 针对有序序列 进行查找

根据 ( key - arr[low] ) / (mid - low) = ( arr[high] - arr[low] ) / (high- low) (二维平面的斜率 )
得到 mid 的递推公式 mid = left + high- low) * ( key - arr[low] ) / arr[high] - arr[low]

代码展示:

package com.atguigu.search;

/**
 * @ClassName InsertValueSearchDemo
 * @Author Jeri
 * @Date 2022-02-21 10:38
 * @Description 插值查找
 */
public class InsertValueSearchDemo {

    /*
     * @Description 插值查找
     * @Date 2022/2/21 11:50
     * @param arr 待查找序列
     * @param left 左索引
     * @param right 右索引
     * @param value 查找数值
     * @return 返回查找数值的索引 否则 返回-1
     */
    public static int insertSearch(int[] arr,int left,int right,int value){
        //排除特殊情况 避免 mid 越界
        if(left > right || arr[left] > value || arr[right] < value){
            return -1;
        }

        //采用自适应 找到mid
        //原理 (value - arr[left]) / (mid - left) == (arr[right] - arr[left]) / (right - left)
        int mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);
        int midValue = arr[mid];
        if(midValue < value) {//向右递归
            return insertSearch(arr,mid + 1,right,value);
        }else if(midValue > value){
            return insertSearch(arr,left,mid - 1,value);
        }else{
            return  mid;
        }

    }

    public static void main(String[] args) {
        //int[] array = new int[]{1,8, 10, 89, 1000, 1234};
        int[] array = new int[]{ 1, 8, 10, 89,1000,1000, 1234};
        //int findValue = 10;
        int findValue = 8;

        int index = insertSearch(array,0,array.length - 1,findValue);

        System.out.println("使用插值查找,结果为---------");
        System.out.println("index = " +  index);
    }
}

使用插值查找,结果为---------
index = 1

斐波那契查找

思路分析:

黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是 0.618。

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

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

  • 构造数列 temp 使得 //temp.length = f[k] - 1 = (f[k-1] - 1) + 1 + (f[k-2] - 1)
  • 上式 1 的位置就是 mid ,则 mid 在数组中的索引为 low + (f[k-1] - 1)
  • 循环查找对应的数值

说明: 斐波那契查找 针对有序序列 进行查找

mid = low + 0.618 * (high - low) 针对斐波那契数列 (high - low) = f[k] - 1,0.618 = f[k-1] / f[k]

代码展示:

package com.atguigu.search;

import java.util.Arrays;

/**
 * @ClassName FibonacciSearchDemo
 * @Author Jeri
 * @Date 2022-02-21 19:27
 * @Description 斐波那契查找算法
 */
public class FibonacciSearchDemo {

    //斐波那契数列最大长度
    public static int maxSize = 20;

    //获取斐波那契数列
    public static int[] fibonacci(){
        int[] fib = new int[maxSize];
        fib[0] = 1;
        fib[1] = 1;

        for(int i = 2;i < maxSize;i++){
            fib[i] = fib[i - 1] + fib[i - 2];
        }

        return fib;
    }

    /*
     * @Description 斐波那契查找算法
     * @Date 2022/2/21 19:31
     * @param arr 带查找数组
     * @param key 查找数值
     * @return [arr, key] 返回索引 否则 返回 -1
     */
    public static int fibonacciSearch(int[] arr,int key){
        int low = 0;
        int high = arr.length - 1;

        int k = 0;//斐波那契分割数值的下标
        int mid = 0;//存放中间值
        int f[] = fibonacci();//获取斐波那契数列
        //获取斐波那契分割数值的下标
        while(high > f[k] - 1){
            k++;
        }

        //构造临时数组
        int[] temp = Arrays.copyOf(arr,f[k]);
        for(int i = high + 1;i < temp.length - 1;i++){
            temp[i] = arr[high];
        }
        //实际上 在 temp 数组中查找 key 数值
        //temp.length == f[k] - 1 == (f[k-1] - 1) + 1 + (f[k-2] - 1)
        //中间的 1 就是给出的 mid 的位置
        //mid 在数组中的索引为 low +  (f[k-1] - 1)
        //在查找时 选择 向(f[k-1] - 1) 或者 向(f[k-2] - 1)查找

        //使用 while 进行循环查找
        while(low <= high){
            mid = low + f[k-1] - 1;
            if(temp[mid] > key){//向 (f[k-1] - 1) 查找
                high = mid - 1;
                //f[k-1] - 1 == (f[k-1-1] - 1) + 1 + (f[k-2-1] - 1)
                k -= 1;
            }else if(temp[mid] < key){//向(f[k-2] - 1) 查找
                low = mid + 1;
                //f[k-2] - 1 == (f[k-1-2] - 1) + 1 + (f[k-2-2] - 1)
                k -= 2;
            }else{
                //需要确定返回的是那个索引
                //temp数组经过扩容
                if(mid <= high){
                    return mid;
                }else{
                    return high;
                }
            }
        }

        return -1;
    }

    public static void main(String[] args) {
        //int[] array = new int[]{1,8, 10, 89, 1000, 1234};
        int[] array = new int[]{ 1, 8, 10, 89,1000, 1234};
        //int findValue = 10;
        int findValue = 8;

        int index = fibonacciSearch(array,findValue);

        System.out.println("使用斐波那契查找,结果为---------");
        System.out.println("index = " +  index);
    }
}

使用斐波那契查找,结果为---------
index = 1

哈希表

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

在这里插入图片描述
在这里插入图片描述

问题描述:

有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的 id 时, 要求查找到该员工的 所有信息

要求:

  • 不使用数据库,速度越快越好=>哈希表(散列)
  • 添加时,保证按照 id 从低到高插入 [课后思考:如果 id 不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?]
  • 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
  • 思路分析并画出示意图

思路分析:
在这里插入图片描述
代码展示:

代码效果:按照 id 顺序添加,冲突时,按照先放现存顺序,同时 存在头节点

  • 如果 id 不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?:实现方法 addByOrder()
  • 链表的第一个结点就存放雇员信息?:添加第一个员工时 head = emp,但是后续的方法在判断逻辑上需要修改
package com.atguigu.hashtable;

import java.util.Scanner;

/**
 * @ClassName HashTableDemo
 * @Author Jeri
 * @Date 2022-02-21 16:57
 * @Description TODO
 */
public class HashTableDemo {
    public static void main(String[] args) {
        //创建哈希表
        HashTable hashTable = new HashTable(7);

        //写一个简单的菜单
        String key = "";
        Scanner scan = new Scanner(System.in);
        while(true){
            System.out.println("------add:添加雇员----------");
            System.out.println("------list:显示雇员---------");
            System.out.println("------find:查找雇员---------");
            System.out.println("------delete:删除雇员-------");
            System.out.println("------exit:退出程序---------");

            key = scan.next();
            switch(key){
                case "add":
                    System.out.println("输入id:");
                    int id = scan.nextInt();
                    System.out.println("输入姓名:");
                    String name = scan.next();
                    hashTable.add(new Emp(id,name));
                    break;
                case "list":
                    hashTable.list();
                    break;
                case "find":
                    System.out.println("输入编号:");
                    int searchID = scan.nextInt();
                    hashTable.findEmpByID(searchID);
                    break;
                case "delete":
                    System.out.println("输入编号:");
                    int deleteID = scan.nextInt();
                    hashTable.deleteEmpByID(deleteID);
                    break;
                case "exit":
                    scan.close();
                    return;
                default:
                    break;
            }
        }

    }
}

//创建HashTable 管理多条链表
class HashTable{
    private EmpLinkedList[] empLinkedLists;
    private int maxSize;

    public HashTable(int maxSize){
        this.maxSize = maxSize;
        //初始化 哈希表
        empLinkedLists = new EmpLinkedList[maxSize];
        //分别初始化链表 否则为 null
        for(int i = 0;i < maxSize;i++){
            empLinkedLists[i] = new EmpLinkedList();
        }
    }

    //添加雇员
    public void add(Emp emp){
        //根据员工的 id 得到员工信息存放在相应链表
        int linkedListID = heshFun(emp.id);
        //添加至相应的链表
        empLinkedLists[linkedListID].add(emp);
    }

    //散列函数
    public int heshFun(int id){
        return id % maxSize;
    }

    //遍历哈希表
    public void list(){
        for(int i = 0;i < maxSize;i++){
            empLinkedLists[i].showLinkedList(i);
        }
    }

    //根据 ID 查找员工
    public void findEmpByID(int id){
        //根据员工的 id 查找相应链表
        int linkedListID = heshFun(id);
        //相应的链表中进行查找
        Emp emp = empLinkedLists[linkedListID].findEmpByID(id);

        if(emp == null){
            System.out.println("在哈希表中没有找到该员工");
        }else{
            System.out.printf("在第%d条链表中找到雇员 id = %d\n", (linkedListID + 1), id);
        }
    }

    //根据 ID 删除员工
    public void deleteEmpByID(int id){
        //根据员工的 id 查找相应链表
        int linkedListID = heshFun(id);
        //相应的链表中进行查找
        empLinkedLists[linkedListID].deleteEmpByID(id);

    }

}
//创建 EmpLinkedList 表示链表 管理雇员对象
class EmpLinkedList{
    //头指针 链表的 head 直接指向第一个Emp
    private Emp head = new Emp(-1,"start");//默认为空

    //添加雇员到链表  添加至末尾
    //id限定自增长  即 id 的分配总是从小到大
    public void add(Emp emp){
        if(head == null){
            //添加第一个员工
            head.next = emp;
            return;
        }

        //定义辅助变量 找到最后一个节点
        Emp current = head;
        while (current.next != null){
            current = current.next;
        }
        current.next = emp;
    }

    //遍历链表的雇员信息
    public void showLinkedList(int i){
        if(head.next == null){
            System.out.printf("第" + (i + 1) + "链表为空\n");
            return;
        }

        System.out.printf("第" + (i + 1) + "链表信息为:");
        //定义辅助变量
        Emp current = head.next;
        while(current != null){
            System.out.printf(current + "\t");
            current = current.next;
        }

        System.out.println();
    }

    //根据 id 查找雇员
    //如果找到就返回 Emp 否则返回 null
    public Emp findEmpByID(int id){
        //判断链表是否为空
        if(head.next == null){
            System.out.println("该链表为空 无法查找");
            return null;
        }

        //辅助变量
        Emp current = head.next;
        while (current != null){
            if(current.id == id){
                return current;
            }

            current = current.next;
        }

        return null;
    }

    //根据 id 删除员工
    //如果删除成功 就返回该员工信息 否则 返回null
    public void deleteEmpByID(int id){
        //判断链表是否为空
        if(head.next == null){
            System.out.println("该链表为空 无法删除");
            return;
        }

        //辅助变量  查找删除节点的前一个节点
        Emp current = head;
        while (current.next != null){
            if(current.next.id == id){
                break;
            }
            current = current.next;
        }

        if(current.next == null){
            System.out.println("哈希表中没有找到该员工 无法删除");
        }else{
            Emp temp = current.next;
            current.next = temp.next;
            System.out.printf("删除雇员 id = %d\n", id);
        }
    }

}
//使用 Emp 表示一个雇员信息
class Emp{
    public int id;
    public String name;
    //节点类 next指向下一个节点 默认为空
    public Emp next;

    public Emp(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
add
输入id:
2
输入姓名:
john
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
add
输入id:
123
输入姓名:
smith
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
list
第1链表为空
第2链表为空
第3链表信息为:Emp{id=2, name='john'}4链表为空
第5链表信息为:Emp{id=123, name='smith'}6链表为空
第7链表为空
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
find
输入编号:
8
该链表为空 无法查找
在哈希表中没有找到该员工
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
find
输入编号:
2
在第3条链表中找到雇员 id = 2
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
delete
输入编号:
15
该链表为空 无法删除
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
delete
输入编号:
2
删除雇员 id = 2
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
list
第1链表为空
第2链表为空
第3链表为空
第4链表为空
第5链表信息为:Emp{id=123, name='smith'}6链表为空
第7链表为空
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
exit

Process finished with exit code 0

参考文献

尚硅谷Java数据结构与java算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值