• 数据结构与算法(十):哈希表


    一、什么是哈希表

    1.概述

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

    通俗的理解一下:

    • 如果我们有n个元素要存储,那我们就用l个内存单元来存储他们
    • 然后我们有一个哈希函数f(x),我们把元素n用函数计算得到哈希值,也就是f(n)
    • f(n)就是存储元素n的那个内存单位的位置,也就是元素在l中的下标

    2.为什么哈希表查询速度快

    理解了哈希表的基本思路,我们也就不难理解为什么哈希表查询效率高了:

    由于每个元素都能通过哈希函数直接计算获得地址,所以查找消耗时间非常少。

    举个例子:

    我们有哈希函数f(n)=n%3,现有元素{1,2,3},我们使用哈希函数分别获得其哈希值,并把哈希值作为下标存入一个数组,

    也就是放f(1)=1,f(2)=2,f(3)=0,如果使用传统线性查找,需要遍历四次,而使用哈希函数计算并查找,只需要一步就能找到,

    可以看得出,理想情况下,哪怕数列再长,找到某个元素都只需要一步。

    3.哈希冲突

    按照上文的例子,数列{1,2,3}通过哈希函数f(n)=n%3可以计算出哈希值,但是如果出现两个元素的哈希值相同就会出现哈希冲突,

    比如f(1)和f(4)都会算出1,这个时候显然不可能上上面一样通过一个一维数组直接存储。

    对此我们有两种方法,即开放地址法和分离链表法:

    • 开放地址法:如果某一哈希值对应的位置已经被占用了,就找另一个没被占用的位置。

      1. 开放地址法容易产生堆积问题;不适于大规模的数据存储
      2. 插入时可能会出现多次冲突的现象,而删除时如果元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂
      3. 结点规模很大时会浪费很多空间

      注:关于开放地址法,具体可以参考这篇文章

    • 分离链表法:将散列表的每一个单元都扩展成为一个链表,相同哈希值的元素会被存储在同一个链表中。

      1. 分离链表法处理冲突简单,且无堆积现象,平均查找长度短
      2. 链表中的结点是动态申请的
      3. 相对开放地址法更加节省空间
      4. 插入与删除结点比较方便

    在jdk8中,使用的就是分离链表法,当哈希冲突超过一点的限制,链表会转为红黑树。

    二、代码实现

    在这里我们实现一个基于分离链表法的哈希表:

    1.节点类

    /**
     * @Author:huang
     * @Date:2020-06-20 10:19
     * @Description:节点
     */
    public class Node {
    
        //节点序号
        int num;
    
        //下一个节点
        Node next;
    
        public Node(int num) {
            this.num = num;
        }
    
        @Override
        public String toString() {
            return "Node{" + "num=" + num + '}';
        }
    }
    

    2.单链表

    /**
     * @Author:黄成兴
     * @Date:2020-06-20 10:19
     * @Description:单链表
     */
    public class SingleLinkList {
    
        private Node head = new Node(0);
    
        public boolean isEmpty() {
            return head.next == null;
        }
    
        /**
         * 添加节点到链表
         * @param node 要插入的节点
         */
        public void add(Node node) {
            Node temp = head;
            //遍历链表
            while (true) {
                if (temp.next == null) {
                    break;
                }
                //不是尾节点就继续遍历下一个节点
                temp = temp.next;
            }
            //将尾节点指向即将插入的新节点
            temp.next = node;
        }
    
        /**
         * 展示链表
         */
        public void show() {
            //判断链表是否为空
            if (isEmpty()) {
                return;
            }
            Node temp = head.next;
            //遍历链表
            while (true) {
                if (temp == null) {
                    break;
                }
                System.out.println(temp.toString());
                temp = temp.next;
            }
        }
    
        /**
         * 根据序号获取节点
         * @param num 要获取的节点序号
         * @return
         */
        public Node get(int num){
            //判断链表是否为空
            if (isEmpty()) {
                return null;
            }
            Node temp = head.next;
            //遍历链表
            while (true) {
                if (temp == null) {
                    return null;
                }
                if (temp.num == num) {
                    return temp;
                }
                temp = temp.next;
            }
        }
    
        /**
         * 修改节点
         * @param node 要更新的节点
         */
        public void update(Node node) {
            Node temp = head;
            //判断链表是否为空
            if (isEmpty()) {
                return;
            }
            //获取要更新的节点序号
            int nodeNum = node.num;
            //遍历链表
            while (true) {
                //如果已经遍历完链表
                if (temp == null) {
                    throw new RuntimeException("编号为" + temp.num + "的节点不存在!");
                }
                //如果找到了该节点
                if (temp.num == nodeNum) {
                    return;
                }
                //继续遍历下一节点
                temp = temp.next;
            }
        }
    
        /**
         * 删除节点
         * @param num 要删除的节点编号
         */
        public void delete(int num) {
            Node temp = head;
            //判断链表是否为空
            if (isEmpty()) {
                return;
            }
            //遍历链表
            while (true) {
                //如果链表到底了
                if (temp.next == null) {
                    return;
                }
                //如果找到了待删除节点的前一个节点
                if (temp.next.num == num) {
                    //判断待删除节点是否为尾节点
                    if (temp.next.next == null){
                        temp.next = null;
                    }else {
                        temp.next = temp.next.next;
                    }
                    return;
                }
                //继续遍历下一节点
                temp = temp.next;
            }
        }
    }
    
    

    3.哈希表

    /**
     * @Author:黄成兴
     * @Date:2020-07-04 11:36
     * @Description:哈希表
     */
    public class HashTable {
    
        //数组长度
        private int size;
        //用于存放数据的数组
        private SingleLinkList[] arr;
    
        public HashTable(int size) {
            this.size = size;
            //初始化数组
            arr = new SingleLinkList[size];
            //初始化链表
            for (int i = 0; i < size; i++) {
                arr[i] = new SingleLinkList();
            }
        }
    
        /**
         * 获取哈希值
         * @param item
         * @return
         */
        public int getHashCode(int item) {
            return item % 2;
        }
    
        /**
         * 插入元素
         * @param item
         */
        public void insert(int item) {
            //获取哈希值
            int hashCode = getHashCode(item);
            //判断哈希值是否超过数组范围
            if (hashCode >= size || hashCode < 0) {
                throw new RuntimeException("哈希值:" + hashCode + "超出初始化长度!");
            }
            //如果该元素在链表中不存在就插入
            if (arr[hashCode].isEmpty() || arr[hashCode].get(item) == null) {
                //插入元素
                arr[hashCode].add(new Node(item));
            }else {
                //否则就更新
                arr[hashCode].update(new Node(item));
            }
    
        }
    
        /**
         * 查找元素
         * @param item
         */
        public Node get(int item) {
            //获取哈希值
            int hashCode = getHashCode(item);
            //判断哈希值是否超过数组范围
            if (hashCode >= size || hashCode < 0) {
                return null;
            }
            //查找元素
            return arr[hashCode].get(item);
        }
    
        /**
         * 删除元素
         * @param item
         */
        public void delete(int item) {
            //获取哈希值
            int hashCode = getHashCode(item);
            //删除元素
            arr[hashCode].delete(item);
        }
    
        /**
         * 展示某个哈希值对应链表的全部数据
         * @param item
         */
        public void show(int item) {
            //获取哈希值
            int hashCode = getHashCode(item);
            arr[hashCode].show();
        }
    
        /**
         * 展示哈希表的所有数据
         */
        public void showAll() {
            for (int i = 0; i < arr.length; i++) {
                //只展示非空链表
                if (!arr[i].isEmpty()) {
                    System.out.println("第"+i+"条链表:");
                    arr[i].show();
                }
            }
        }
    }
    
    
  • 相关阅读:
    Java自学笔记(13):【面向对象】方法覆盖,final关键字,对象转型
    Java自学笔记(12):【面向对象】继承,super关键字,继承下的访问控制权限
    Java自学笔记(11):【面向对象】 package,import,访问控制修饰符,static
    计算机领域会议和期刊
    powershell真香
    C语言博客作业--结构体
    C博客作业--指针
    C语言博客作业--字符数组
    C语言博客作业--一二维数组
    C语言博客作业--数据类型
  • 原文地址:https://www.cnblogs.com/Createsequence/p/13234705.html
Copyright © 2020-2023  润新知