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

  • 相关阅读:
    PointToPointNetDevice doesn't support TapBridgeHelper
    NS3系列—10———NS3 NodeContainer
    NS3系列—9———NS3 IP首部校验和
    NS3系列—8———NS3编译运行
    【习题 7-6 UVA
    【Good Bye 2017 C】 New Year and Curling
    【Good Bye 2017 B】 New Year and Buggy Bot
    【Good Bye 2017 A】New Year and Counting Cards
    【Educational Codeforces Round 35 D】Inversion Counting
    【Educational Codeforces Round 35 C】Two Cakes
  • 原文地址:https://www.cnblogs.com/0ffff/p/11240684.html
Copyright © 2011-2022 走看看