哈希算法 数据结构_实现哈希表构造和查找算法

哈希算法 数据结构_实现哈希表构造和查找算法一、什么是哈希表1.概述哈希表(Hashtable,也叫散列表),是根据关键码值(Keyvalue)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、什么是哈希表

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();
            }
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170809.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • seo关键词快速排名流量有多大_seo站内优化技巧

    seo关键词快速排名流量有多大_seo站内优化技巧SEO的一个重要工作就是,通过优化关键词的方式,将网站做到搜索页的第一页,甚至第一页的第一名的位置。比如,你们公司是做鲜花业务,那么用户搜索“玫瑰”的时候,第一眼就能搜到你的网站。…

    2022年9月16日
    0
  • css自动换行属性与保留空白属性冲突_css换行样式

    css自动换行属性与保留空白属性冲突_css换行样式word-break属性规定自动换行的处理方法。提示:通过使用word-break属性,可以让浏览器实现在任意位置的换行。所有主流浏览器都支持word-break属性。语法:word-break:normal|break-all|keep-all;normal使用浏览器默认的换行规则。break-all允许在单词内换行。keep-all只能在半角空格或连字符处换行。word-break:break-all所有的都换行,右侧换行没有空隙。word-wrap属性允许

    2022年10月30日
    0
  • vue-axios使用_vue post请求

    vue-axios使用_vue post请求什么是axiosAxios是一个基于promise的HTTP库,可以用在浏览器和node.js中。主要的作用:axios主要是用于向后台发起请求的,还有在请求中做更多是可控功能。a

    2022年7月30日
    8
  • UE4/UE5 代理使用介绍[通俗易懂]

    UE4/UE5 代理使用介绍[通俗易懂]原创文章,转载请注明出处。UE4有一套代理机制,整理了一下做个介绍。也请大家做补充。有了代理,方便我们做代码设计,减轻耦合。文章里面的代码下载链接:代理单播代理二级目录三级目录多播代理二级目录三级目录单播代理二级目录三级目录多播代理二级目录三级目录…

    2022年9月28日
    0
  • kitti数据集介绍_cifar10数据集下载

    kitti数据集介绍_cifar10数据集下载KITTI数据集下载及解析版本更新时间更新内容作者1V1.0xxx完成主体内容W.Xiao2文章目录KITTIDataset1简介1.1数据采集平台1.2坐标系2数据解析2.1image文件2.2velodyne文件2.3calib文件2.4label文件3KITTI可视…

    2022年10月10日
    0
  • 显示隐藏高德地图点标注的文本标签「建议收藏」

    显示隐藏高德地图点标注的文本标签「建议收藏」@[显示隐藏高德地图点标注的文本标签]效果如图欢迎使用Markdown编辑器你好!这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Markdown编辑器,可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。新的改变我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你…

    2022年5月14日
    111

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号