《算法》3.4散列表

1散列表

用算术操作将键转化为数组的索引来访问数组中的键值对。

散列表的查找算法分两步:

  • 用散列函数将键转化为数组的索引
  • 处理碰撞冲突(常用的两种方法)
    • 拉链法
    • 线性探测法

使用散列表:

  • 可以实现在一般应用中拥有(均摊后)常数级别的查找和插入操作的符号表。
  • 但是散列表不支持有序性操作。

1.1散列函数

将键转化为数组的索引。

严格来说:对于每种类型的键我们都需要一个与之对应的散列函数。

  • 正整数:除留余数法。(k对数组大小M取余)
  • 浮点数:将键转化为二进制再用除留余数法。
  • 字符串:将字符串当做大整数。
  • 组合键:将各个键的hash值组合起来。

java的约定:

  • 如果需要为自定义的数据类型定义散列函数,就需要同时重写hashCode()equals()方法。

1.2软缓存

  • 第一次调用hashCode()方法,计算对象的散列值。
  • 但之后调用hashCode()方法,直接返回hash变量的值。

一个优秀的散列函数应该满足以下三个条件:

  • 一致性:等价的键必然产生相同的散列值
  • 高效性:计算简便
  • 均匀性:所有的键均匀分布

2基于拉链法的散列表

/**
 * @Author: AZhu
 * @Date: 2021/3/7 23:50
 */
public class SeparateChainingHashST<Key, Value> {
    
    private int N;//键值对总数
    private int M;//散列表的大小
    private SequentialSearchST<Key, Value>[] st;//存放链表对象的数组

    public SeparateChainingHashST() {
        this(997);
    }

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

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

    /**
     * 查找
     * @param key
     * @return
     */
    public Value get(Key key) {
        return (Value) st[hash(key)].get(key);
    }

    /**
     * 插入
     * @param key
     * @param value
     */
    public void put(Key key, Value value) {
        st[hash(key)].put(key, value);
    }
    
}

3基于线性探测法的散列表

/**
 * 基于线性探测法的散列表
 * @Author: AZhu
 * @Date: 2021/3/8 0:03
 */
public class LinearProbingHashST<Key, Value> {

    private int N;//键值对总数
    private int M = 16;//线性探测表的大小
    private Key[] keys;
    private Value[] values;

    public LinearProbingHashST() {
        keys = (Key[]) new Object[M];
        values = (Value[]) new Object[M];
    }
    
    public LinearProbingHashST(int m) {
        this.M = m;
        keys = (Key[]) new Object[M];
        values = (Value[]) new Object[M];
    }

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

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

    /**
     * 插入
     * @param key
     * @param value
     */
    public void put(Key key, Value value) {
        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)) {
                values[i] = value;
                return;
            }
        }
        keys[i] = key;
        values[i] = value;
        N++;
    }

    /**
     * 查找
     *
     * @param key
     * @return
     */
    public Value get(Key key) {
        for (int i = hash(key); keys[i] != null; i = (i + 1) % M) {
            if (keys[i].equals(key)) {
                return values[i];
            }
        }
        return null;
    }
    
}

4内存使用

符号表的内存使用:

方法N个元素所需的内存
基于拉链法的散列表~48N+32M
基于线性探测法的散列表32N和128N之间
各种二叉查找树~56N
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值