哈希表

一、哈希表概述

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

其实把哈希表看做是字典来理解哈希表就很容易明白了,我们通过关键码即可快速定位关键值。显而易见哈希表有一个很大的优点就是查找数据的速度快

如下所示就是一个典型的哈希表:

哈希表_第1张图片

哈希表是数组和链表的结合体,上图的哈希表左边是一个数组,右边是链表,即数组中的每个元素都是一个链表。一个新的结点具体添加到哪个链表中是由映射关系来决定的。同样,我们如果想要查找某个结点,只需要通对结点的关键码进行映射关系运算,计算出在数组的第几个元素(即哪个链表),然后遍历链表比对关键码即可得出结果。

从典型的哈希表结构中,我们可以得出这样的结论:本质上哈希表是一个元素为链表的数组

二、哈希表的代码实现

下面以 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;
}

本案例中要实现一个哈希表,需要三个类:一个类作为结点类存放雇员的个人信息、一个类作为链表类,用于记录相同余数的雇员结点、一个类作为哈希表类。通过哈希表类可以实现对任意结点(也即员工)进行增删改查,而增删改查的具体操作也就是对结点的具体操作,则是要通过链表类来实现。

【代码实现】

本案例完整的哈希表代码实现如下:

public class No1_HashTable {
     
    public static void main(String[] args) {
     
        Scanner input = new Scanner(System.in);
        MyHashTable hashTable = new MyHashTable(6);	// 创建哈希表

        while (true){
     
            System.out.println();
            System.out.println("(add)添加雇员");
            System.out.println("(ser)查找雇员");
            System.out.println("(lis)显示雇员");
            System.out.println("(del)删除雇员");
            System.out.println("(ext)退出");
            System.out.print("请输入选项:");

            String choice = input.nextLine();
            switch (choice){
     
                case "add":	// 添加
                    System.out.print("请输入ID:");
                    int id = Integer.parseInt(input.nextLine());
                    System.out.print("请输入姓名:");
                    String name = input.nextLine();
                    hashTable.addEmp(new Employee(id, name));
                    break;
                case "ser":	// 查找
                    System.out.print("请输入要查找ID:");
                    hashTable.findById(Integer.parseInt(input.nextLine()));
                    break;
                case "lis":	// 显示
                    hashTable.list();
                    break;
                case "del":	// 删除
                    System.out.print("请输入要删除ID:");
                    hashTable.deleteById(Integer.parseInt(input.nextLine()));
                    break;
                case "ext":	// 退出
                    break;
                    default:
                        System.out.println("输入有误!");
            }
        }
    }
}

/**
 * 定义一个哈希表
 */
class MyHashTable{
     
    private int size;
    private EmployeeLinkedList[] empList;

    public MyHashTable(int maxSize){
     
        size = maxSize;
        empList = new EmployeeLinkedList[maxSize];
        for (int i=0; i<maxSize; i++){
        // 数组中存放的不是基本类型时,必须初始化数组
            empList[i] = new EmployeeLinkedList();
        }
    }
    // 添加一个结点到哈希表中
    public void addEmp(Employee emp){
     
        int index = hashFun(emp.id);
        empList[index].addEmp(emp);
    }
    // 显示所有结点
    public void list(){
     
        for (int i=0; i<size; i++){
     
            System.out.printf("第 %d 个链表为:",i);
            empList[i].list();
            System.out.println();
        }
    }
    // 根据 id 查找某个结点
    public void findById(int id){
     
        int index = hashFun(id);
        empList[index].findById(id);
    }
    // 根据 id 删除某个结点
    public void deleteById(int id){
     
        int index = hashFun(id);
        empList[index].deleteById(id);
    }
    // 模拟获取哈希值,该值决定结点要存放到数组的第几个链表
    public int hashFun(int id){
     
        return id % size;
    }
}

/**
 * 定义一个雇员链表
 */
class EmployeeLinkedList{
     
    private Employee head = null;   // 这里的头结点是有意义的,是一个真实的雇员

    // 添加结点
    public void addEmp(Employee employee){
     
        if (head == null){
           // 判断链表是否为空
            head = employee;
            return;
        }
        Employee cur = head;    // 辅助指针
        while(cur.next != null){
        // 找到最后一个结点
            cur = cur.next;
        }
        cur.next = employee;
        employee.next = null;
    }
    // 通过 id 查找结点
    public void findById(int id){
     
        if (head == null){
       // 判断链表是否为空
            System.out.printf("链表为空, ID 为 %d 的结点不存在!", id);
            return;
        }
        Employee cur = head;    // 第一个结点不能动,要用辅助指针
        while (cur != null){
     
            if (cur.id == id){
     
                System.out.printf("找到了!该结点信息为:ID = %d,NAME = %s", id, head.name);
                return;
            }
            cur = cur.next;
        }
        System.out.println("该结点不存在!");
    }
    // 通过 id 删除结点
    public void deleteById(int id){
     
        if (head == null){
     
            System.out.println("链表为空,无法删除!");
            return;
        }
        // 因为 head 结点也是一个有效结点,他没有前驱,所以要单独判断
        if (head.id == id){
      // 如果第一个结点是目标结点
            head = head.next;
            return;
        }
        // head 之后的节点都是有前驱结点的结点
        Employee cur = head;    // 辅助指针
        while (true){
     
            if (cur.next == null){
       // 如果判断到了最后一个结点还是没有找到
                System.out.println("该结点不存在!");
                return;
            }
            if (cur.next.id == id){
     
                cur.next = cur.next.next;
                return;
            }
            cur = cur.next;
        }
    }
    // 显示所有结点
    public void list(){
     
        if (head == null){
     
            System.out.print("链表为空!");
            return;
        }
        Employee cur = head; // 辅助指针
        while (cur != null){
     
            System.out.printf(" =>ID:%d,NAME:%s", cur.id, cur.name);
            cur = cur.next;
        }
    }
}

/**
 * 定义一个雇员结点
 */
class Employee{
     
    public int id;
    public String name;
    public Employee next;

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

你可能感兴趣的:(算法与数据结构,哈希表,链表,数据结构,java)