Java哈希表及链式哈希表的实现

哈希表也称为散列表,是用来存储群体对象的集合类结构。

什么是哈希表

数组和向量都可以存储对象,但对象的存储位置是随机的,也就是说对象本身与其存储位置之间没有必然的联系。当要查找一个对象时,只能以某种顺序(如顺序查找或二分查找)与各个元素进行比较,当数组或向量中的元素数量很多时,查找的效率会明显的降低。

一种有效的存储方式,是不与其他元素进行比较,一次存取便能得到所需要的记录。这就需要在对象的存储位置和对象的关键属性(设为 k)之间建立一个特定的对应关系(设为 f),使每个对象与一个唯一的存储位置相对应。在查找时,只要根据待查对象的关键属性 k 计算f(k)的值即可。如果此对象在集合中,则必定在存储位置 f(k)上,因此不需要与集合中的其他元素进行比较。称这种对应关系 f 为哈希(hash)方法,按照这种思想建立的表为哈希表。

Java 使用哈希表类(Hashtable)来实现哈希表,以下是与哈希表相关的一些概念:

  • 容量(Capacity):Hashtable 的容量不是固定的,随对象的加入其容量也可以自动增长。

  • 关键字(Key):每个存储的对象都需要有一个关键字,key 可以是对象本身,也可以是对象的一部分(如某个属性)。要求在一个 Hashtable 中的所有关键字都是唯一的。

  • 哈希码(Hash Code):若要将对象存储到 Hashtable 上,就需要将其关键字 key 映射到一个整型数据,成为 key 的哈希码。

  • 项(Item):Hashtable 中的每一项都有两个域,分别是关键字域 key 和值域 value(存储的对象)。Key 和 value 都可以是任意的 Object 类型的对象,但不能为空。

  • 装填因子(Load Factor):装填因子表示为哈希表的装满程度,其值等于元素数比上哈希表的长度。

链式哈希表的描述

链式哈希表从根本上说是由一组链表构成。每个链表都可以看做是一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。插入元素时,首先将其键传入一个哈希函数(该过程称为哈希键),函数通过散列的方式告知元素属于哪个“桶”,然后在相应的链表头插入元素。查找或删除元素时,用同们的方式先找到元素的“桶”,然后遍历相应的链表,直到发现我们想要的元素。因为每个“桶”都是一个链表,所以链式哈希表并不限制包含元素的个数。然而,如果表变得太大,它的性能将会降低。

冲突问题:两个不同的键映射到同一个位置

链式哈希表的实现

链式哈希表的操作与属性有:初始化、添加元素、删除元素、查找key值、获取哈希表中key值的个数。
链式哈希表的实现

/**
 * 描述: 实现链式哈希表结构
 *
 * 哈希函数:除留余数  data % bucket_size =
 * 扩容的时候,按原来空间大小的2倍+1扩容就可以
 *
 */
public class LinkHashTable<K extends Comparable<K>,V> {

    // 哈希桶
    private Entry<K,V>[] table;
    // 装载因子 0.75
    private double loadFactor;
    // 记录已经占用的桶的数量
    private int usedBucketSize;

    /**
     * 哈希表初始化
     */
    public LinkHashTable(){
        this.table = new Entry[3];
        this.loadFactor = 0.75;
        this.usedBucketSize = 0;
    }

    /**
     * 给哈希表增加元素
     * @param key
     * @param value
     */
    public void put(K key, V value){
        double lf = usedBucketSize*1.0 / this.table.length;
        if(lf > this.loadFactor){
            expand();
        }

        int idx = key.hashCode() % this.table.length;
        if(this.table[idx] == null){
             // 桶为空
            this.table[idx] = new Entry<>(key, value, null);
            this.usedBucketSize++;
        } else {
            // 桶已经有元素, 防止key被重复插入
            Entry<K,V> cur = this.table[idx];
            while(cur != null){
                if(cur.key.compareTo(key) == 0){
                    // key值已经存在
                    cur.value = value;
                    break;
                }
                cur = cur.next;
            }

            // cur == null表示上面的while循环没有找见key值相等的节点
            if(cur == null){
                this.table[idx] = new Entry<>(key, value, this.table[idx]);
            }
        }
    }

    /**
     * 在哈希表中查询key是否存在,如果key存在,返回它对应的value值,
     * 否则返回null
     * @param key
     * @return
     */
    public V get(K key){
        int idx = key.hashCode() % this.table.length;
        if(this.table[idx] == null){
            return null;
        } else{
            Entry<K,V> cur = this.table[idx];
            while(cur != null){
                if(cur.key.compareTo(key) == 0){
                    return cur.value;
                }
                cur = cur.next;
            }
            return null;
        }
    }

    /**
     * 删除哈希表中key值为参数指定的节点
     * @param key
     */
    public void remove(K key){
        int idx = key.hashCode() % this.table.length;
        if(this.table[idx] == null){
            return;
        } else {
            /**
             * 桶不为空,需要遍历桶中的链表节点
             */
            Entry<K,V> pre = null;
            Entry<K,V> cur = this.table[idx];
            while(cur != null){
                if(cur.key.compareTo(key) == 0){
                    if(pre == null){
                        // 删除的是桶中的第一个节点
                        this.table[idx] = cur.next;
                    } else {
                        // 删除的不是桶中的第一个节点
                        pre.next = cur.next;
                    }

                    // 桶的元素被删除完了
                    if(this.table[idx] == null){
                        this.usedBucketSize--;
                    }
                    return;
                }
                pre = cur;
                cur = cur.next;
            }
        }
    }

    /**
     * 哈希表的扩容函数
     */
    private void expand(){
        Entry<K,V>[] oldTable = this.table;
        this.usedBucketSize = 0;
        this.table = new Entry[2*oldTable.length];

        for (int i = 0; i < oldTable.length; i++) {
            Entry<K,V> cur = oldTable[i];
            while(cur != null){
                this.put(cur.key, cur.value);
                cur = cur.next;
            }
        }
    }

    /**
     * 链式哈希表中节点的类型
     * @param <K,V>
     */
    static class Entry<K extends Comparable<K>,V>{
        K key;  // student id
        V value; // student
        Entry<K,V> next;

        public Entry(K key, V value, Entry<K, V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public static void main(String[] args) {
        LinkHashTable<Integer, String> ht
                = new LinkHashTable<>();
        /*ht.put(100, "张宇");
        ht.put(103, "张璐");
        ht.put(105, "冯超");

        System.out.println(ht.get(104));*/

        Random rd = new Random();
        int[] arr = new int[1000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = rd.nextInt(60000);
        }

        // key: 数字   值:数字出现的次数
        LinkHashTable<Integer, Integer> ht1
                = new LinkHashTable<>();
        for (int i = 0; i < arr.length; i++) { // O(n)
            Integer val = ht1.get(arr[i]); // O(1)
            if(val == null){
                ht1.put(arr[i], 1);
            } else {
                ht1.put(arr[i], val+1); // O(1)
            }
        }
    }

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值