散列 - Java 实现

泪湿孤枕 提交于 2020-02-03 06:35:24

此处使用分离链接法来解决冲突,即将散列到同一个桶的所有元素都保存到一个链表中。

在这里插入图片描述
对象必须实现 hashCode() 方法(计算哈希值)和 equals() 方法(判断是否已经存在该元素)。

除了链表,也可以使用其他的数据结构来解决冲突,如,二叉搜索树、另一个散列表 …

但是,我们期望如果散列表足够大且散列函数可以将元素分布均匀,那么所有的链表都应该是短的,此时使用简单的链表就足以。

装填因子

定义装填因子 λ=/\lambda = 元素总数 / 哈希表桶数 ,即每个冲突链表的平均长度为 λ\lambda

  • 在一次失败的查找中,要探查的节点数平均为 λ\lambda :先根据哈希值定位到一个哈希桶,然后再遍历对应的冲突链表(平均含 λ\lambda 个节点)。

  • 在一次成功的查找中,要探查的节点数平均为 λ/2+1\lambda / 2 + 1:先根据哈希值定位到一个哈希桶,然后再遍历对应的冲突链表,平均来看,有一半的“其他”节点被检查到,接着就是目标节点。

由此来看,散列表的大小并不重要,装填因子才是重要的!

再散列

就是重新构建一个散列表,然后再重新插入每一个元素至新的散列表中。

其运行时间为 O(N) 。

在 rehash 之前,必然已存在 N 次插入操作,因此,插入操作的时间复杂度在均摊意义上为 O(1) 。

import java.util.LinkedList;
import java.util.List;

public class SeparateChainingHashTable<T> {
    private static final int DEFAULT_TABLE_SIZE = 101;	// 哈希表默认大小
    private List<T> [] lists;		// 链表:解决冲突
    private int size;				// 当前元素个数
    private float loadFactor;		// 装填因子 = 元素总数 / 哈希表桶数,即,size / lists.length

    public SeparateChainingHashTable() {
        this(DEFAULT_TABLE_SIZE, 1.0f);
    }

    public SeparateChainingHashTable(int size, float loadFactor) {
        lists = new LinkedList[nextPrime(size)];
        for (int i = 0; i < lists.length; i++) {
            lists[i] = new LinkedList<>();
        }
        this.loadFactor = loadFactor;
    }

	// 计算元素的哈希值
    private int hash(T e) {
        int key = (e.hashCode() + lists.length) % lists.length;
        return key;
    }

	// 大于等于 value 的最小素数
    private int nextPrime(int value) {
        while (!isPrime(value)) {
            if (value >= Integer.MAX_VALUE) {
                break;
            }
            value++;
        }
        return value;
    }

    private boolean isPrime(int value) {
        int last = (int) Math.sqrt((double) value);
        for (int i = 2; i <= last; i++) {
            if (value % i == 0) {
                return false;
            }
        }
        return true;
    }

	// 是否包含元素 e
    public boolean contains(T e) {
        List<T> list = lists[hash(e)];
        return list.contains(e);
    }

	// 插入元素 e:成功时返回 true
    public boolean insert(T e) {
        List<T> list = lists[hash(e)];

        if (list.contains(e)) {
            return false;
        }

        list.add(0, e);
        size++;

        // 是否需要 rehash
        if ((int) (size * loadFactor) > lists.length) {
            rehash();
        }

        return true;
    }

	// 重哈希:桶数大致变为之前的两倍
    private void rehash() {
        List<T> [] oldLists = lists;
        lists = new LinkedList[nextPrime(lists.length << 1)];
        for (int i = 0; i < lists.length; i++) {
            lists[i] = new LinkedList<>();
        }

		// 重新插入:耗时
        for (int i = 0; i < oldLists.length; i++) {
            for (T e : oldLists[i]) {
                insert(e);
            }
        }
    }

	// 删除元素 e
    public void remove(T e) {
        List<T> list = lists[hash(e)];
        if (list.contains(e)) {
            list.remove(e);
            size--;
        }
    }

	// 根据 e 的哈希值获取对应的元素
    public T get(T e) {
        List<T> list = lists[hash(e)];
        for (int i = 0; i < list.size(); i++) {
            if (e.equals(list.get(i))) {
                return list.get(i);
            }
        }
        return null;
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!