算法8-基于拉链法、线性探测法的散列表

各位读者,晚上好。

这里分享下基于拉链法、线性探测法实现的散列表。

本博客代码示例均来自:算法 Algorithmes Forth Edition
[美] Robert Sedgewick Kevin Wayne 著 谢路云译

一、拉链法

package com.cmh.algorithm;

import edu.princeton.cs.algs4.SequentialSearchST;

import java.util.Arrays;

/**
 * 基于拉链法的散列表
 *
 * @author Author:起舞的日子
 * Date:  2020/4/16 下午11:04
 * <p>
 *
 */
public class SeparateChainingHashST<Key, Value> {
    /**
     * 键值对总数
     */
    private int N;
    /**
     * 散列表的大小
     */
    private int M;
    /**
     * 存放链表对象的数组
     */
    private SequentialSearchST<Key, Value>[] st;

    public SeparateChainingHashST() {
        this(997);
    }

    public SeparateChainingHashST(int M) {
        /*
        创建M条链表
         */
        this.M = M;
        st = new SequentialSearchST[M];
        for (int i = 0; i < M; i++) {
            st[i] = new SequentialSearchST<>();
        }
    }

    private int hash(Key key) {
        return (key.hashCode() & 0x7fffffff) % M;
    }

    public Value get(Key key) {
        return st[hash(key)].get(key);
    }

    public void put(Key key, Value val) {
        st[hash(key)].put(key, val);
    }

    public Iterable<Key> keys() {
        return (Iterable<Key>) Arrays.stream(st)
                .map(e -> e.keys());
    }

    /**
     * 总结:
     * 1、散列表是算法在时间和空间上做出权衡的经典例子;散列函数能够将键转化为数组的一个索引;
     * 2、散列查找需要处理碰撞冲突,这里介绍2种解决碰撞的办法:拉链法、线性探测法
     * 3、可将各种数据类型散列:
     *      整数-除留余数法
     *      浮点数-把键表示为二进制再使用除留余数法
     *      字符串-
     *      组合键
     *      java.hashCode()
     *      自定义hashCode()方法
     * 4、软缓存:每次计算散列值很耗时,就把计算的值缓存起来,下次直接用;
     * 5、为避免链表过长,动态调整链表数组大小,无论符号表中有多少键值对都能保证链表较短:
     *      二项分布
     *      泊松分布
     *
     */
}

二、线性探测法


package com.cmh.algorithm;

/**
 * 基于线性探测法的散列表
 * <p>
 * Author:起舞的日子
 * Date:  2020/4/16 下午11:29
 */
public class LinearProbingHashST<Key, Value> {
    /**
     * 符号表中的键值对总数
     */
    private int N;
    /**
     * 线性探测表的大小
     */
    private int M = 16;
    /**
     * 并行数组
     */
    private Key[] keys;

    private Value[] vals;

    public LinearProbingHashST() {
        keys = (Key[]) new Object[M];
        vals = (Value[]) new Object[M];
    }

    public LinearProbingHashST(int cap) {
        M = cap;
    }

    private int hash(Key key) {
        return (key.hashCode() & 0x7fffffff) % M;
    }

    private void resize() {

    }

    private void resize(int cap) {
        LinearProbingHashST<Key, Value> t;
        t = new LinearProbingHashST<>(cap);
        for (int i = 0; i < M; i++) {
            if (keys[i] != null) {
                t.put(keys[i], vals[i]);
            }
            keys = t.keys;
            vals = t.vals;
            M = t.M;
        }
    }

    public void put(Key key, Value val) {
        /**
         * 扩容,为什么扩2倍?
         * 因为这样可以避免键簇形成
         */
        if (N > M / 2) {
            resize(2 * M);
        }
        int i;
        for (i = hash(key); keys[i] != null; i = (i + 1) % M) {
            if (keys[i].equals(key)) {
                vals[i] = val;
                return;
            }
            keys[i] = key;
            vals[i] = val;
            N++;
        }
    }

    public Value get(Key key) {
        for (int i = hash(key); keys[i] != null; i = (i + 1) % M) {
            if (keys[i].equals(key)) {
                return vals[i];
            }
        }
        return null;
    }

    public boolean contains(Key key) {
        for (int i = hash(key); keys[i] != null; i = (i + 1) % M) {
            if (keys[i].equals(key)) {
                return true;
            }
        }
        return false;
    }

    public void delete(Key key) {
        if (!contains(key)) {
            return;
        }
        int i = hash(key);
        while (!key.equals(keys[i])) {
            i = (i + 1) % M;
        }
        keys[i] = null;
        vals[i] = null;
        i = (i + 1) % M;
        while (keys[i] != null) {
            Key keyToRedo = keys[i];
            Value valToRedo = vals[i];
            keys[i] = null;
            vals[i] = null;
            N--;
            put(keyToRedo, valToRedo);
            i = (i + 1) % M;
        }
        N--;
        if (N > 0 && N == M / 8) {
            resize(M / 2);
        }
    }

    /**
     * 总结:
     * 1、 用大小为M的数组保存N个键值对,其中M>N.依靠数组中空位解决碰撞冲突。——开放地址散列表
     * 2、开放地址散列表中最简单的方法是:线性探测法
     * 3、用散列函数找到键所在的索引,检查其中的键和被查找的键是否相同,如果不同继续查找;
     * 4、探测:检查一个数组位置是否含有被查找的键的操作
     * 5、键簇-元素插入数组后形成一组连续的条目,如何避免?调整数组大小
     * 6、均摊分析  动态调整数组大小使散列表长度加倍的插入操作需要大量探测
     *
     */
}

三、源码地址

https://github.com/cmhhcm/my2020.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值