算法(3.4 散列表)

来源:互联网 发布:如何成为黑客 知乎 编辑:程序博客网 时间:2024/06/08 03:51

3.4.1.6  Java 的约定


每种数据类型都需要相应的散列函数,于是Java 令所有数据类型都继承了一个能够返回一个
32 比特整数的hashCode() 方法。每一种数据类型的hashCode()方祛都必须和equals() 方法一
致。也就是说,如果a,equa1s(b) 返回true,那么a.hashCodeO 的返回值必然和b.hashCode()
的返回值相同。相反,如果两个对象的hashCode() 方法的返回值不同,那么我们就知道这两个
对象是不同的。但如果两个对象的hashCode( 方法的返回值相同,这两个对象也有可能不同,
我们还需要用equa1s() 方祛进行判断。请往意,这说明如果你要为自定义的数据类型定义散列函
数,你需要同时重写hashCode() 和equa1sO 两个方法。默认散列丽数会返回对象的内存地址,
但这只适用于很少的情况。Java 为很多常用的数据类型重写了hashCode() 方法(包括String、

Integer.Double,File 和URL)。


3.4.1.7 将hashCode()的返回值转化为一个数组索引


因为我们需要的是数组的索引而不是一个32 位的整数,我们在实现中会将默认的hashCode()方法
和除留余数法结合起来产生一个0 到M-1的整数,方法如下:
private int hash(Key x){
return (x.hashCode() & 0x7fffffff) % M;
}
这段代码会将符号位屏蔽(将一个32 位整数变为一个31位非负整数),然后用除留余数法i
算它除以M 的余数。在使用这样的代码时我们一般会将数组的大小M 取为素数以充分利用原散列(
的所有位。注意: 为了避免混乱,我们在例子中不会使用这种计算方法而是使用表3.4.1所示的1
列值作为替代。

//拉链法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];          for(int i=0;i<st.length;i++){              st[i] = new SequentialSearchST();          }      }            private int hash(Key key){          return (key.hashCode()&0x7fffffff) % M;      }            public Value get(Key key){        return (Value)st[hash(key)].get((int)key);  //        return st[hash(key)].get((int)key);      }            public void put(Key key,Value val){          st[hash(key)].put(key,val);      }  }

// 线性探测class LinearProbingHashST<Key, Value>{private int N;//  符号表中键值对的总数private int M; //  线性探测表的大小private Key[] keys;//  键private Value[] vals; //  值public LinearProbingHashST(){M = 16;keys = (Key[])new Object[16];vals = (Value[])new Object[16];}public LinearProbingHashST(int M){this.M = M;keys = (Key[])new Object[M];vals = (Value[])new Object[M];}private int hash(Key key){          return (key.hashCode()&0x7fffffff) % M;      }private void resize(int cap){LinearProbingHashST<Key, Value> t = new LinearProbingHashST<Key, Value>(cap);for (int i=0; i<M; i++){if (keys[i] != null){t.put(keys[i], vals[i]);}}this.M = cap;this.keys = t.keys;this.vals = t.vals;}public void put(Key key, Value val){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;}//3.4.3.1 删除操作////如何从基于线性探测的散列表中删除一个//键? 仔细想一想,你会发现直接将该键所在的位//置设为null是不行的,因此,我们需要将簇中被删除键的右侧的//所有键重新插入散列表。和拉链祛一样,//开放地址类的散列表的性能也依赖于a=N/M的比值,//但意义有所不同。public void delete(Key key){if (get(key) == null){return;}int i = hash(key);while (!keys[i].equals(key)){i = (i + 1) % M;}keys[i] = null;vals[i] = null;i = (i + 1) % M;while (keys[i] != null){Key k = keys[i];Value v = vals[i];keys[i] = null;vals[i] = null;N--;// 相当于先删除再插入  所以先减少,在插入方法里面会增加put(k, v);;i = (i+1) % M;}N--;if (N > 0 && N == M/8){resize(M/2);}}}


在拉链法中调整散列表大小不是必需的,在线性探测法中调整大小是必需的



原创粉丝点击