九, Java实现哈希表(拉链法, 数组+单链表)

九, 哈希表

9.1 哈希表的定义和特点

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

  • 其实把哈希表看做是字典来理解哈希表就很容易明白了,我们通过关键码即可快速定位关键值。显而易见哈希表有一个很大的优点就是查找数据的速度快。
  • 下图就是一个典型的哈希表(数组+链表实现的,使用拉链法解决冲突)

什么是拉链法?

9.2 哈希表的一些实际应用

根据散列表的特点可以想到,散列表比较适合无序、需要快速访问的情况


  1. 用作缓存, 提升数据的查找速度, 减轻数据库的压力

关于哈希表实际应用更加详细的说明

9.3 一个简单哈希表(数组+单链表)的实现

下面以 google 的一个上机题为例来做一个具体实现。

【案例描述】

有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id、名字、…),当输入该员工的 id 时,要求查找到该员工的所有信息。要求不使用数据库,速度越快越好。==> 我们可由此知道, 此题目的是让我们使用哈希表实现对数据的快速查找

【思路分析】
要求不使用数据库,而且是通过员工 id 来查询员工的所有信息,很明显可以使用哈希表来解决。

在本例中,员工的 id 是关键码,我们可以对员工的 id 取余来作为到链表的映射。假设数组的长度是 5,比如 id 取余 5 等于 0 的员工都存放到第一个链表中(如 5、15…)、再比如 id 取余5 等于 2 的都存放到第 3 个链表中(如 2、7、12…)。其实现代码如下:

// 模拟获取哈希值,该值决定结点要存放到数组的第几个链表
public int hashFun(int id){
    return id % size;
}

本案例中要实现一个哈希表,需要三个类:一个类作为结点(EmployeeNode)类存放雇员的个人信息、一个类作为链表类(EmployeeLinkedList),具体实现对结点的增删改查等操作, 一个类作为哈希表类(EmployeeHashTableImp),通过散列函数把链表分配到数组中, 并使用链表类的增删改查方法. 注意: 增删改查的具体操作代码,是要通过链表类来实现

代码结构图如下:


哈希表(数组+单链表)的应用实例代码实现:

  1. 雇员结点类(管理雇员链表的指针域, 初始化和输出雇员信息)
public class EmployeeNode {
    //结点类, 存放雇员的具体信息
    int id;
    String name;
    int salary;
    EmployeeNode next;

    //构造方法和toString()
    public EmployeeNode(int id, String name, int salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "EmployeeNode{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}

  1. 雇员链表类(操纵链表结点, 具体实现对链表结点的添加, 查找,删除, 遍历操作)
public class EmployeeLinkedList {
    //链表操作结点, 实现对雇员的添加, 查找, 删除,
    //头结点
    EmployeeNode head = null;

    //添加雇员
    public void add(EmployeeNode node){
        //头结点为空, 直接添加
        if(head == null){
            head = node;
            return;
        }

        //尾插法实现链表新结点的添加
        //临时结点
        EmployeeNode temp = head;

        while(true){
            //跳出循环条件
            if( temp.next==null){
               break;
            }
            temp = temp.next;
        }
        temp.next = node;
        ///通过debug我们可以知道, 如果不把链表最后一个结点的指针域置空\
        //会进入node.next=node的无限循环
        node.next = null;
    }
    //查找结点
    public EmployeeNode findById(int id){
        if(head == null){
            System.out.println("链表为空, 查找失败");
            return null;
        }
        EmployeeNode temp = head;
        while(true){
            //找到了, 返回
            if(temp.id == id){
                break;
            }

            temp = temp.next;
            //遍历到末尾都没找到
            if(temp == null){
                break;
            }
        }
        return temp;
    }
    //删除结点
    public void delById(int id){
        //判空
        if(head == null){
            System.out.println("链表为空, 删除失败");
            return;
        }
        //1. 删除的结点为头结点
        if(head.id == id){
            head = head.next;
            System.out.printf("id=%d的结点删除成功! \n ",id);
            return;
        }
        //2. 删除的结点非头结点
        //临时结点
        EmployeeNode temp = head;
        while(true){
            //删除结点必须知道被删除结点的前驱结点, 所以我们使用 temp.next查找要删除的结点
            if(temp.next == null){
                System.out.println("id="+id+"的结点不存在或已经被删除!");
                break;
            }
            if(temp.next.id == id){

                temp.next = temp.next.next;
                System.out.printf("id=%d的结点删除成功! \n ",id);
                break;
            }
            temp = temp.next;

        }
    }

    //遍历结点
    public void list(){
        if(head == null){
            System.out.print("链表为空");
            return;
        }
        //临时结点
        EmployeeNode temp = head;
        while(true){
            if(temp == null) break;
            System.out.printf("\t"+temp + "===>");
            temp = temp.next;

        }
    }
}
  1. 哈希表的操作类(借助数组管理多条链表, 通过散列函数去使用链表类提供的添加, 查找, 删除, 遍历操作)
public class EmployeeHashTableImp {
    //对链表的增删改查和散列到数组
    EmployeeLinkedList[] hashArr; //存储链表的哈希数组
    private static int maxsize;


    public EmployeeHashTableImp(int maxsize){
        this.maxsize = maxsize;
        hashArr = new EmployeeLinkedList[maxsize];
        /注意:
        初始化数组, 因为数组的内容为自定义类型,
        // 所以必须用自定义类型初始化每一个index
        for(int i=0; i< maxsize; i++){
            hashArr[i] = new EmployeeLinkedList();
        }
    }
    //散列函数
    public static int hashFun(int id){
        return id%maxsize;
    }
    //添加结点到链表
    public void add(EmployeeNode node){
        //找到链表在数组中的索引
        int index = hashFun(node.id);
        //添加结点到链表
        hashArr[index].add(node);
    }
    //查询雇员信息
    /**
     * 1. 需要雇员id, 根据id计算散列值
     * 2. 根据散列值找到链表
     * 3. 使用链表的查询
     */
    public void findById(int id){
        //根据散列函数找到链表的位置
        int index = hashFun(id);
        EmployeeNode employee = hashArr[index].findById(id);
        if(employee != null){
            System.out.println("找到了 id="+id+"的具体信息为: "+employee);
        }else{
            System.out.println("未找到id="+id+"的信息");
        }
    }
    //删除雇员信息
    public void delById(int id){
        //找到链表在数组的索引
        int index = hashFun(id);
        hashArr[index].delById(id);
    }
    //打印所有雇员的信息
    public void list(){
        for(int i=0; i<hashArr.length; i++){
            System.out.println("第"+i+"号链表的信息为: ");
            hashArr[i].list();
            System.out.println();
        }
    }
    //测试方法
    public static void main(String[] args) {
        EmployeeHashTableImp hashtable = new EmployeeHashTableImp(7);

        //雇员结点
         EmployeeNode node1 = new  EmployeeNode(1,"xiaowang",22442);
         EmployeeNode node2 = new  EmployeeNode(2, "xiaogou", 3545);
         EmployeeNode node3 = new  EmployeeNode(3, "lisi", 46464);
         EmployeeNode node4 = new  EmployeeNode(4, "mazi",3553);
         EmployeeNode node5 = new  EmployeeNode(5, "zhangsan",5757);
         EmployeeNode node6 = new  EmployeeNode(6, "qingwa",253535);
         EmployeeNode node7 = new  EmployeeNode(7, "sanbing",4646);
         EmployeeNode node8 = new  EmployeeNode(8, "xiaop",24255);
         EmployeeNode node9 = new  EmployeeNode(9, "duzi",4646);
         EmployeeNode node10 = new  EmployeeNode(10, "wangba",464635);

        ///添加 到哈希表
        hashtable. add(node1);
        hashtable. add(node2);
        hashtable. add(node3);
        hashtable. add(node4);
        hashtable. add(node5);
        hashtable. add(node6);
        hashtable. add(node7);
        hashtable. add(node8);
        hashtable. add(node9);
        hashtable. add(node10);

        //打印出整体的哈希表
        hashtable.list();
        System.out.println();
        System.out.println("============================");

        //查询雇员信息
        System.out.println("1. 查询雇员 id=3的详细信息");
        hashtable.findById(3);
        System.out.println("查询雇员 id=7的详细信息");
        hashtable.findById(7);
        hashtable.findById(15);
        hashtable.findById(8);

        //删除雇员信息
        System.out.println("================");
        System.out.println("2. 删除雇员id=1的信息");
        hashtable.delById(1);
        System.out.println("删除雇员id=5的信息");
        hashtable.delById(5);
        System.out.println("删除雇员id=9的信息");
        hashtable.delById(9);
        System.out.println("删除雇员id=9的信息");
        hashtable.delById(9);
        System.out.println("删除雇员id=2的信息");
        hashtable.delById(2);
        System.out.println("删除雇员id=7的信息");
        hashtable.delById(7);

        //打印雇员信息
        System.out.println("================");
        hashtable.list();
    }
}

代码运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

posted @ 2022-05-26 20:31  青松城  阅读(150)  评论(0编辑  收藏  举报