zoukankan      html  css  js  c++  java
  • 拉链法和线性探测法

    散列函数

    1. 正整数
      除留余数法,选择大小为素数M的数组,对于任意正整数k ,计算k除以M的余数。
      如果M不是素数,我们可能无法利用键中包含的所有信息,这可能导致我们无法均匀地散列散列值

    2. 浮点数
      第一,如果键是0-1的实数,我们可以将它乘 M 并四舍五入得到一个0~M-1 之间的索引,有缺陷,高位起的作用更大,最低位对散列值得结果没影响。
      第二,将键表示为二进制,然后试用版除留余数法。

    3. 字符串
      基本原理也是除留余数法,Horner方法
      int hash = 0;
      for(int i = 0 ; i<s.length(); i++){
      hash = (R * hash +s.charAt(i)) % M;
      }
      //R代表进制

    4. 组合键
      eg ,年月日
      int hash = (((day * R +month) % M) * R +year) % M;

    5. Java约定
      第一,所有数据类型都集成了一个能返回一个32bit整数的hashCode()方法。
      第二,每一种数据类型的hashCode()都必须和equals()一致
      第三,a.equals(b) 为true a.hashCode()= b.hashCode(); a,b的hashCode相同,并不一样是一个对象,需要用equal判断是否为同一对象。

    6. hashCode()的返回值转化为索引值
      hashCode()返回32位整数,而我们需要的是 0~M-1索引。转化方法如下

    private int hash(Key x){
        return (x.hashCode() & 0x7fffffff) % M;
    }
    //x.hashCode() & 0xfffffff将符号位屏蔽,然后除留余数法计算。
    
    
    1. hashCode()
    public class Transaction{
        private final String who;
        private final Date when;
        private final double amount;
        
        public int hashCode(){
            int hash  = 17;
            hash = 31 * hash + who.hashCode();
            hash = 31 * hash + when.hashCode();
           hash = 31 * hash + ammount.hashCode();
           return hash;
        }
    }
    
    1. 软缓存
      如果散列值得计算很耗时,那么我们或许可以将每个键的散列值保存起来,即在每个键中使用一个hash变量保存它的hashCode()返回值
    2. 优秀的散列方法的三个条件
      第一,一致性 等价的键必然产生相等的散列值
      第二,高效性,计算简便
      第三,均匀地散列所有键

    基于拉链法的散列表

    定义:碰撞处理是指处理两个或多个键的散列值相同的情况。一种直接的办法是将大小为M的数组中的每个元素指向一条链表,链表中每个结点都存储了
    散列值为该元素索引的键值对。这种方法称为拉链法。
    让M足够大,这样每个链表的长度就越短。
    查找分两步:根据散列值找到链表,然后沿着链表查找相应的键。
    在一张含有M条链表 和N个键的散列表中,未命中查找和插入操作所需比较的次数为 ~N/M

    基于线性探测法的散列表

    另外一种,用大小M的数组保存N个键值对,其中 M > N。我们需要依靠数组中的空位解决碰撞冲突。基于这种策略的所有方法统称为 开放地址散列表
    开放地址散列表最简单的方法叫做 线性探测法
    定义:当碰撞发生时,我们直接检查散列表中的下一个位置。这样的线性探测有三种结果
    第一,命中,该位置的键和被查找的键相同
    第二,未命中,键为空(该位置没有键)
    第三,继续查找,该位置的键和被查找的键不同

    基于线性探测法的散列表:
    插入思路:
    第一:计算key的散列表,找到相应的位置
    第二:查看这个位置的值是否为要插入的key。不是的话比较下一个位置 ,直到当前位置的键值为空。
    第三:如果在遇到空键值前遇到相等的键,则进行更新操作;如果直到遇到空值也没遇到相同的键,在空键值位置插入相应键值。

    public class LinearProbingHashST<Key, Value> {
        private static final int INIT_CAPACITY = 4;
    
        private int n;           // number of key-value pairs in the symbol table
        private int m;           // size of linear probing table
        private Key[] keys;      // the keys
        private Value[] vals;    // the values
    
        public LinearProbingHashST() {
            this(INIT_CAPACITY);
        }
        public LinearProbingHashST(int capacity) {
            m = capacity;
            n = 0;
            keys = (Key[])   new Object[m];
            vals = (Value[]) new Object[m];
        }
    
        private int hash(Key key) {
            return (key.hashCode() & 0x7fffffff) % m;
        }
    
        // resizes the hash table to the given capacity by re-hashing all of the keys
        private void resize(int capacity) {
            LinearProbingHashST<Key, Value> temp = new LinearProbingHashST<Key, Value>(capacity);
            for (int i = 0; i < m; i++) {
                if (keys[i] != null) {
                    temp.put(keys[i], vals[i]);
                }
            }
            keys = temp.keys;
            vals = temp.vals;
            m    = temp.m;
        }
        public void put(Key key, Value val) {
            if (key == null) throw new IllegalArgumentException("first argument to put() is null");
            if (val == null) {
                delete(key);
                return;
            }
            // double table size if 50% full
            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) {
            if (key == null) throw new IllegalArgumentException("argument to get() is null");
            for (int i = hash(key); keys[i] != null; i = (i + 1) % m)
                if (keys[i].equals(key))
                    return vals[i];
            return null;
        }
    }
    

    删除操作
    键簇: 元素在插入数组后集聚而成的一组连续的条目。
    我们需要将簇中被删除键的右侧的所有键重新插入到散列表

        public void delete(Key key) {
            if (key == null) throw new IllegalArgumentException("argument to delete() is null");
            if (!contains(key)) return;
    
            // find position i of key
            int i = hash(key);
            while (!key.equals(keys[i])) {
                i = (i + 1) % m;
            }
    
            // delete key and associated value
            keys[i] = null;
            vals[i] = null;
    
            // rehash all keys in same cluster
            i = (i + 1) % m;
            while (keys[i] != null) {
                // delete keys[i] an vals[i] and reinsert
                Key   keyToRehash = keys[i];
                Value valToRehash = vals[i];
                keys[i] = null;
                vals[i] = null;
                n--;
                put(keyToRehash, valToRehash);
                i = (i + 1) % m;
            }
    
            n--;
    
            // halves size of array if it's 12.5% full or less
            if (n > 0 && n <= m/8) resize(m/2);
    
            assert check();
        }
    

    当 散列表快满的时候查找所需要的探测次数是巨大的,但当使用率 a小于 1/2 是探测的预计次数在1.5~2.5之间。

    调整数组的大小
    第一,基于线性探测法的数组大小调整
    把原表中所有的键重新散列并插入到新表

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

    第二,拉链法的数组大小调整

    内存使用
    除了存储键和值所需要的空间外,我们实现拉链法 SeparateChainingHashST 保存了M个SeparateChainingHashST
    对象和它们的引用。每个对象需要16字节,每个引用需要8字节。另外还有N个对象,每个对象需要24字节以及三个引用。
    在使用率 1/81/2情况下,线性探测使用4N16N个引用。
    方法 N个元素所需的内存
    拉链法 48N + 32M
    线性探测法 32N ~ 128N
    二叉查找树 56N

  • 相关阅读:
    Hibernate实现limit查询报错 :unexpected token: limit near line 1, column 33 [from pojo.Product p order by id limit ?,? ]
    slot_filling
    Perplexity(困惑度)
    图网络
    textrank
    copynet
    PGN
    beam-search的改进
    项目实战:如何构建知识图谱
    实体关系三元组抽取
  • 原文地址:https://www.cnblogs.com/0ffff/p/11240684.html
Copyright © 2011-2022 走看看