数据结构-哈希表(Java版)

目录

1,什么是哈希表

1.1,如何解决哈希碰撞问题

1.2,哈希表有什么优势呢?

2,链表的实现

2.1,哈希表中的节点类型

2.2,链表的定义

2.2.1,链表的节点定义

2.2.2,向链表末尾添加一个元素

2.2.3,在链表中查找一个元素

2.2.4,在链表中删除一个元素

2.2.5,打印链表

3,哈希表的实现

3.1,定义一个哈希表

3.2,向哈希表中添加一个元素

3.3,获取元素索引

3.4,哈希表的遍历

3.5,在哈希表中查找一个元素

3.6,在哈希表中删除一个元素

4,代码测试


1,什么是哈希表

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

给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

  • 下面总结一下什么是哈希表
    • 哈希表是一种数据结构
    • 哈希表表示了关键码值和记录的映射关系
    • 哈希表可以加快查找速度
    • 任意哈希表,都满足有哈希函数f(key),代入任意key值都可以获取包含该key值的记录在表中的地址
  • 语言太抽象,我们可以通过下面这张图了解到底什么是哈希表

看上面的图很好理解,数组就是我们的哈希表实现,在一个典型的哈希表实现中,我们将数组总长度设为模数,将key值直接对其取模,所得的值为数组下标,也就是我们的元素存储在哈希表中的位置下标,但是入过数组中这个索引出有元素,那么其他元素如果映射到这里的话就会发生冲突,该如何存储元素呢?这就是我接下来要讲的冲突处理方法。

1.1,如何解决哈希碰撞问题

常用的解决方案有散列法和拉链法散列法又分为开放寻址法和再散列法等,此处不做展开。java中使用的实现为拉链法,即:在每个冲突处构建链表,将所有冲突值链入链表,如同拉链一般一个元素扣一个元素,故名拉链法。

需要注意的是,如果遭到恶意哈希碰撞攻击,拉链法会导致哈希表退化为链表,即所有元素都被存储在同一个节点的链表中,此时哈希表的查找速度=链表遍历查找速度=O(n)。

1.2,哈希表有什么优势呢?

通过前面的概念了解,哈希表的优点呼之欲出:通过关键值计算直接获取目标位置,对于海量数据中的精确查找有非常惊人的速度提升,理论上即使有无限的数据量,一个实现良好的哈希表依旧可以保持O(1)的查找速度,而O(n)的普通列表此时已经无法正常执行查找操作(实际上不可能,受到JVM可用内存限制,机器内存限制等)。

2,链表的实现

2.1,哈希表中的节点类型

class HashNode{
    public int id;
    public HashNode next;//指向下一个节点

    public HashNode(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "HashNode{" +
                "id=" + id +
                '}';
    }
}

2.2,链表的定义

2.2.1,链表的节点定义

class HashLinked {
    //    头指针,指向第一个元素,也就是说链表没有头结点
//    什么叫做没有头指针,也就是说head指针永远指向第一个元素节点
    private HashNode head;//默认是空的
}

2.2.2,向链表末尾添加一个元素

/**
     * 向链表中添加一个节点,默认使用尾插法
     * @param hashNode 需要添加的节点
     */
    public void add(HashNode hashNode) {
        if(head == null){
//            说明添加的是第一个节点
            head=hashNode;//链表没有头结点
            return;
        }
//        否则就添加到链表的末尾
        HashNode curNode=head;
//        第一种写法
        while (curNode.next != null){
            curNode=curNode.next;
        }
        curNode.next=hashNode;
//        第二种写法,错误写法,因为curNode为空时,其下一个指针域为null,会包空指针异常
//        要理解什么是空指针异常,当当前节点为null的时候,其next域会报空指针异常,但是
//        只要最后一个节点不是空,即使其next为null,也不会报空指针异常
//        也就是说只有null会报空指针异常
//        while (curNode != null){
//            curNode=curNode.next;//指向下一个节点后,下一个节点是null,所以回报空指针异常
//            if(curNode == null)
//                curNode.next=hashNode;
//        }

    }

2.2.3,在链表中查找一个元素

 /**
     * 在链表中的查找操作
     * @param id 元素的id值
     * @return 查找成功就返回元素,否则就返回null
     */
    public HashNode find(int id){
        if(this.head == null){
            return null;//如果当前的链表是空,就返回null
        }else {
//            也就是当前的链表不是空的,遍历链表查找元素
            HashNode curNode=head;
            while (curNode!= null){
                if(curNode.getId() == id)
                    return curNode;
                else
                {
                    curNode=curNode.next;
                }
            }
        }
        //退出循环,说明没有找到该元素,直接返回null
        return null;
    }

2.2.4,在链表中删除一个元素

 /**
     * 在链表中删除一个元素
     * @param id 需要删除元素的id
     * @return 如果删除成功就返回true,否则就直接返回false
     */
    public boolean delete(int id){
        if(this.head == null){
            return false;
        }else {
//            查找要删除的节点
        HashNode curNode=find(id);
        HashNode preNode=head;
        if(curNode != null){
            if(preNode.getId() == id){
                head=head.next;
                return true;
            }else if(preNode.next != null){
                while (preNode.next != curNode){
                    preNode=preNode.next;
                }
                preNode.next=curNode.next;
                return true;
            }else {
                return false;
            }

        }else {
//            如果链表中没有要删除的元素,就直接退出
            return false;
        }
        }
    }

2.2.5,打印链表

//    打印链表
    public void print(int i){
        if(this.head == null){
//            说明当前的莲表是空链表
            System.out.println("第"+(i)+"条链表是空的!");
        }
        else {
            HashNode curNode=head;
            System.out.print("第"+(i)+"条链表的元素是:");
            while (curNode != null){
                System.out.print(curNode.getId()+" ");
                curNode=curNode.next;
            }
            System.out.println();
        }
    }

3,哈希表的实现

3.1,定义一个哈希表

//现在创建我们的哈希表,用来管理多条链表
class HashTable{
    private HashLinked []HashArrayList;
    private int size;//标示哈希表的大小,也就是链表的条数
//    构造函数,初始化数组的大小

    public HashTable(int size) {
        this.size=size;
//        这里只是对整条数组进行初始化
        HashArrayList = new HashLinked[size];
//        在这里对每一条链表进行初始化,这里非常重要
//        因为数组中每一个元素都是一个链表,所以每一个链表都要初始化
        for(int j=0;j<this.size;j++){
            HashArrayList[j]=new HashLinked();
        }
    }
}

3.2,向哈希表中添加一个元素

/**
     * 向哈希表中添加一个元素
     * @param hashNode 添加的元素节点
     */
    public void addEle(HashNode hashNode){
//获取元素存储的哈希值
        int getLinkedNum=getHash(hashNode.id);
//        把此节点添加到对应的链表中
//        在这里会报空指针异常,因为只是创建数组,被默认初始化为null,所以报错
//        解决方法,分别初始化每一条链表
        HashArrayList[getLinkedNum].add(hashNode);
    }

3.3,获取元素索引

 /**
     * 编写一个哈希函数,此函数可以根据id获取其存储的位置
     * @param id 元素存储的id
     * @return 返回元素在哈希表中的存储位置索引
     */
    public int getHash(int id){
        return id%this.size;
    }

3.4,哈希表的遍历

/**
     * 哈希表的遍历操作
     */
    public void printHashTable(){
        for(int i=0;i<this.size;i++){
            HashArrayList[i].print(i);
        }
    }

3.5,在哈希表中查找一个元素

/**
     * 哈希表的查找算法
     * @param id 根据id查找元素的值
     * @return 如果查找到,就返回元素,否则就返回空
     */
    public HashNode findEleById(int id){
//        首先要根据id值去获取元素的hash码
        int EleHash=getHash(id);
//        根据hash去查找看元素在那一条链表上面
        return HashArrayList[EleHash].find(id);
}

3.6,在哈希表中删除一个元素

 /**
     * 在哈希表中删除一个元素
     * @param id 需要删除元素的di
     * @return 如果删除成功,就返回true,否则就返回false
     */
    public boolean deleteEleInHashTable(int id){
        int hash=getHash(id);
        return HashArrayList[hash].delete(id);
}

4,代码测试

public class HashTableDemo {
    public static void main(String[] args) {
        HashTable hashTable=new HashTable(5);
        hashTable.addEle(new HashNode(7));
        hashTable.addEle(new HashNode(58));
        hashTable.addEle(new HashNode(98));
        hashTable.addEle(new HashNode(41));
        hashTable.addEle(new HashNode(25));
        hashTable.addEle(new HashNode(36));
        hashTable.addEle(new HashNode(55));
        hashTable.addEle(new HashNode(78));
        hashTable.printHashTable();
        ArrayList arrayList=new ArrayList();
//        在哈希表里面查找操作
        HashNode hashNode=hashTable.findEleById(6666);
        System.out.println(hashNode);
//        从哈希表中删除一个元素
        boolean b=hashTable.deleteEleInHashTable(78);
       System.out.println(b);
        hashTable.printHashTable();

    }

}
  • 测试结果


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值