034.哈希表的分析和实现


博主的 Github 地址


1. 哈希表的介绍和内存布局

1.1. 哈希表的基本介绍

  • 哈希表(Hash Table)也叫散列表

  • 是根据关键码值(Key Value)而直接进行访问的数据结构

  • 即哈希表通过吧关键码值映射到表中一个位置来访问记录, 从而加快查找的速度.

  • 这个映射函数叫做散列函数, 存放记录的数组叫做散列表


1.2. 哈希表的由来

哈希表由来

1.2.1. 直接连接数据库的缺点
  • 通常来说 Java 程序操作数据最基础的方式是直接连数据库进行发送操作指令
  • 在数据库接收到指令后处理相关数据并最终返回给 Java 程序
  • 但这种操作存在着缺陷, 有时候需要对数据库频繁进行操作, 会导致速度变慢
1.2.2. 通过哈希表建立缓存提升速度
  • 因此可以在程序和数据库之间建立一个缓存层, 将一些常用数据放入其中
  • 在缓存层操作数据可以减少对数据库的操作, 从而提升了速度
  • 缓存层可以使用缓存产品或者自建哈希表来进行构建
  • 自建哈希表有两种形式, 分别是数组加链表的结构, 以及数组加二叉树的结构.
  • 而且若一级缓存不够还可以加二级缓存

1.3. 哈希表的内存结构

1.3.1. 数组加链表形式的哈希表

数组链表

  • 在数组中每个元素存放的是一条链表
  • 当查找数据的时候会根据查找的 ID 通过散列函数进行计算
  • 然后找出目标可能会在哪一条链表中出现, 再去确定是否存在

2. 哈希表的应用实例

2.1. 谷歌的上机题

  • 题目
    有一个公司, 当有新的员工来报到时, 要求将该员工的信息加入,
    员工信息包括 id, 名字, 性别, 年龄, 住址等,
    当输入该员工的 id 时要查找到该员工的所有信息.

  • 要求

    • 不使用数据库, 速度越快越好 (即要求使用哈希表)
    • 添加时, 保证按照 id 从低到高插入

2.2. 思路分析

思路分析

  • 如上图所示, 用哈希表来管理雇员信息需要构建三个类
2.2.1. 链表类

构建链表类, 主要用来实现存放每个链表对应的头结点和增删查改结点的方法

2.2.2. 结点类

构建结点类, 主要用于实现每个结点内存放雇员的 id, 姓名等信息

2.2.3. 哈希表类

构建哈希表类, 主要用于实现链表数组用于存放每个链表的头结点,
以及实现调用增删查改结点和构建散列函数使 id 对应到相应链表的方法


2.3. 代码实现

  • 实现细节看注释
2.3.1. 结点类
package com.leo9.dc21.hash_table;

//定义员工结点类, 用来存放员工信息
//同时链表是单链表, 所以只需要定义指向下一结点
public class EmpNode {
    public int id;
    public String name;
    public EmpNode next_emp;

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

2.3.2. 链表类
package com.leo9.dc21.hash_table;

//定义员工链表类, 存放每个链表的头结点, 和实现链表增删查改的方法
public class EmpLinkList {
    //头结点, 指向当前链表的第一个员工, 因此头结点是有效结点
    private EmpNode head_node;

    //定义添加新员工结点的方法
    //假定添加员工时, id是自增长的, 即id总是从小到大
    //因此直接将新结点添加到链表尾部即可
    public void addEmployer(EmpNode new_node){
        //先判断头结点是否为空
        if(head_node == null){
            head_node = new_node;
            return;
        }

        //如果不是当前链表的第一个员工, 使用辅助指针帮助定位
        EmpNode cur_node = head_node;
        while(true){
            if(cur_node.next_emp != null){
                cur_node = cur_node.next_emp;
            }else {
                cur_node.next_emp = new_node;
                break;
            }
        }
    }

    //遍历链表的雇员信息
    public void viewList(int no){
        //判断头结点是否为空, 如果为空则链表为空
        if(head_node == null){
            System.out.printf("the list[%d] is empty\n", no);
            return;
        }
        System.out.printf("the list[%d] ", no);
        EmpNode cur_node = head_node;
        while(true){
            //输出结点信息
            System.out.printf("=> [id = %d, name = %s]\t", cur_node.id, cur_node.name);
            //判断当前结点下一位是否为空, 为空则跳出循环
            if(cur_node.next_emp == null){
                break;
            }
            //后移一位
            cur_node = cur_node.next_emp;
        }
        System.out.println();
    }

    //根据id查找员工
    //如果找到就返回员工相应信息, 如果没找到就返回null
    public EmpNode searchEmpById(int id){
        //先判断当前链表是否为空
        if(head_node == null){
            //System.out.println("this list is empty");
            return null;
        }

        //遍历链表寻找是否存在员工
        EmpNode cur_node = head_node;
        while(true){
            //找到则跳出循环, 然后返回该员工结点.
            if(cur_node.id == id){
                break;
            }
            //全表遍历完仍找不到则返回空
            if(cur_node.next_emp == null){
                //System.out.println("this employer is not existed");
                return null;
            }
            cur_node = cur_node.next_emp;
        }
        return cur_node;
    }
}

2.3.3. 哈希表类
package com.leo9.dc21.hash_table;

//定义HashTable 管理多条链表
public class EmpHashTable {
    private EmpLinkList[] empLinkListsArray;
    //定义一个尺寸表示该哈希表中存放有多少条链表
    private int size;

    //构造器, 指定哈希表有多少条链表, 并进行初始化
    public EmpHashTable(int size){
        this.size = size;
        //根据尺寸构建哈希表
        empLinkListsArray = new EmpLinkList[size];
        //初始化哈希表内的每一条链表
        for (int i = 0; i < size; i++) {
            empLinkListsArray[i] = new EmpLinkList();
        }
    }

    //编写散列函数, 使用取模法进行编写
    public int hashFun(int id){
        return id % size;
    }

    //添加员工
    public void add(EmpNode emp_node){
        //根据员工的id, 得到该员工应当添加到哪条链表
        int empLinkListNO = hashFun(emp_node.id);
        //将员工结点添加到对应的链表中
        empLinkListsArray[empLinkListNO].addEmployer(emp_node);
    }

    //遍历哈希表的所有链表
    public void viewList(){
        for(int i = 0; i < size; i++){
            empLinkListsArray[i].viewList(i);
        }
    }

    //查找员工
    public void searchEmpById(int id){
        EmpNode node = empLinkListsArray[hashFun(id)].searchEmpById(id);
        if(node != null){
            System.out.printf("the employer[id = %d] is existed in list[%d], the info is [id = %d, name = %s]\n", id, hashFun(id), node.id, node.name);
        }
        else {
            System.out.printf("the employer[id = %d] is not existed\n", id);
        }
    }
}

2.3.4. 测试类
package com.leo9.dc21.hash_table;

public class TestDemoHT {
    public static void main(String[] args) {
        EmpHashTable emp_table = new EmpHashTable(5);
        emp_table.add(new EmpNode(1,"leo"));
        emp_table.add(new EmpNode(6,"ken"));
        emp_table.viewList();
        emp_table.searchEmpById(6);
        emp_table.searchEmpById(7);
    }
}


2.4. 测试结果

testresult

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值