演算法(四)散列表
阿新 • • 發佈:2018-12-11
雜湊查詢演算法
(一)用雜湊函式將被查詢的鍵轉化為陣列的索引 (二)處理碰撞衝突:拉鍊法和線性探測法 散列表是演算法在時間和空間上作出權衡的經典例子
雜湊函式
特點
(一)易於計算 (二)均勻分佈
基於拉鍊法的散列表
(一) 將大小為M的陣列中的每個元素指向一條連結串列,連結串列中每個節點都儲存了雜湊值為該元素索引的鍵值對 (二) 選擇足夠大的M,使得所有連結串列都儘可能的的高效查詢 (三) 查詢分兩步:(1)根據雜湊值找到對應的連結串列(2)沿著列表順序查詢相應的鍵
實現
package com.lwl.algorithm.hash; import com.lwl.algorithm.linkedlist.Queue; import com.lwl.algorithm.stack.StdIn; import com.lwl.algorithm.stack.StdOut; import com.lwl.algorithm.tree.SequentialSearchST; /** * @author liuweilong * @ClassName: SeparateChainingHashST * @Description: 基於拉鍊法的散列表 * @Version 1.0 **/ public class SeparateChainingHashST<Key, Value> { private static final int INIT_CAPACITY = 4; private int n; // number of key-value pairs private int m; // hash table size private SequentialSearchST<Key, Value>[] st; // array of linked-list symbol tables /** * Initializes an empty symbol table. */ public SeparateChainingHashST() { this(INIT_CAPACITY); } /** * Initializes an empty symbol table with {@code m} chains. * @param m the initial number of chains */ public SeparateChainingHashST(int m) { this.m = m; st = (SequentialSearchST<Key, Value>[]) new SequentialSearchST[m]; for (int i = 0; i < m; i++) st[i] = new SequentialSearchST<Key, Value>(); } // resize the hash table to have the given number of chains, // rehashing all of the keys private void resize(int chains) { SeparateChainingHashST<Key, Value> temp = new SeparateChainingHashST<Key, Value>(chains); for (int i = 0; i < m; i++) { for (Key key : st[i].keys()) { temp.put(key, st[i].get(key)); } } this.m = temp.m; this.n = temp.n; this.st = temp.st; } // hash value between 0 and m-1 private int hash(Key key) { return (key.hashCode() & 0x7fffffff) % m; } /** * Returns the number of key-value pairs in this symbol table. * * @return the number of key-value pairs in this symbol table */ public int size() { return n; } /** * Returns true if this symbol table is empty. * * @return {@code true} if this symbol table is empty; * {@code false} otherwise */ public boolean isEmpty() { return size() == 0; } /** * Returns true if this symbol table contains the specified key. * * @param key the key * @return {@code true} if this symbol table contains {@code key}; * {@code false} otherwise * @throws IllegalArgumentException if {@code key} is {@code null} */ public boolean contains(Key key) { if (key == null) throw new IllegalArgumentException("argument to contains() is null"); return get(key) != null; } /** * Returns the value associated with the specified key in this symbol table. * * @param key the key * @return the value associated with {@code key} in the symbol table; * {@code null} if no such value * @throws IllegalArgumentException if {@code key} is {@code null} */ public Value get(Key key) { if (key == null) throw new IllegalArgumentException("argument to get() is null"); int i = hash(key); return st[i].get(key); } /** * Inserts the specified key-value pair into the symbol table, overwriting the old * value with the new value if the symbol table already contains the specified key. * Deletes the specified key (and its associated value) from this symbol table * if the specified value is {@code null}. * * @param key the key * @param val the value * @throws IllegalArgumentException if {@code key} is {@code null} */ 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 average length of list >= 10 if (n >= 10*m) resize(2*m); int i = hash(key); if (!st[i].contains(key)) n++; st[i].put(key, val); } /** * Removes the specified key and its associated value from this symbol table * (if the key is in this symbol table). * * @param key the key * @throws IllegalArgumentException if {@code key} is {@code null} */ public void delete(Key key) { if (key == null) throw new IllegalArgumentException("argument to delete() is null"); int i = hash(key); if (st[i].contains(key)) n--; st[i].delete(key); // halve table size if average length of list <= 2 if (m > INIT_CAPACITY && n <= 2*m) resize(m/2); } // return keys in symbol table as an Iterable public Iterable<Key> keys() { Queue<Key> queue = new Queue<Key>(); for (int i = 0; i < m; i++) { for (Key key : st[i].keys()) queue.enqueue(key); } return queue; } /** * Unit tests the {@code SeparateChainingHashST} data type. * * @param args the command-line arguments */ public static void main(String[] args) { SeparateChainingHashST<String, Integer> st = new SeparateChainingHashST<String, Integer>(); for (int i = 0; !StdIn.isEmpty(); i++) { String key = StdIn.readString(); st.put(key, i); } // print keys for (String s : st.keys()) StdOut.println(s + " " + st.get(s)); } }
基於線性探測法的散列表
(一)用大小為M的陣列儲存N個鍵值對,M>N,依靠陣列中空缺位解決碰撞衝突,基於這種策略的所有方法統稱為開發地址散列表 (二)開放地址法中最簡單的方法為線性探測法:當碰撞傳送時(一個鍵的雜湊值已經被另一個不同的鍵佔用),直接檢查散列表中的下一個位置(將索引值加1) (三)線性探測會產生三種結果:(1)命中(2)未命中,鍵未空(3)繼續查詢
package com.lwl.algorithm.hash; import com.lwl.algorithm.stack.StdIn; import com.lwl.algorithm.stack.StdOut; /** * @author liuweilong * @ClassName: LinearProbingHashST 基於線性探測的符號表 * @Description: TODO * @Version 1.0 **/ 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 /** * Initializes an empty symbol table. */ public LinearProbingHashST() { this(INIT_CAPACITY); } /** * Initializes an empty symbol table with the specified initial capacity. * * @param capacity the initial capacity */ public LinearProbingHashST(int capacity) { m = capacity; n = 0; keys = (Key[]) new Object[m]; vals = (Value[]) new Object[m]; } /** * Returns the number of key-value pairs in this symbol table. * * @return the number of key-value pairs in this symbol table */ public int size() { return n; } /** * Returns true if this symbol table is empty. * * @return {@code true} if this symbol table is empty; * {@code false} otherwise */ public boolean isEmpty() { return size() == 0; } /** * Returns true if this symbol table contains the specified key. * * @param key the key * @return {@code true} if this symbol table contains {@code key}; * {@code false} otherwise * @throws IllegalArgumentException if {@code key} is {@code null} */ public boolean contains(Key key) { if (key == null) throw new IllegalArgumentException("argument to contains() is null"); return get(key) != null; } // hash function for keys - returns value between 0 and M-1 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; } /** * Inserts the specified key-value pair into the symbol table, overwriting the old * value with the new value if the symbol table already contains the specified key. * Deletes the specified key (and its associated value) from this symbol table * if the specified value is {@code null}. * * @param key the key * @param val the value * @throws IllegalArgumentException if {@code key} is {@code null} */ 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++; } /** * Returns the value associated with the specified key. * @param key the key * @return the value associated with {@code key}; * {@code null} if no such value * @throws IllegalArgumentException if {@code key} is {@code null} */ 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; } /** * Removes the specified key and its associated value from this symbol table * (if the key is in this symbol table). * * @param key the key * @throws IllegalArgumentException if {@code key} is {@code 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(); } /** * Returns all keys in this symbol table as an {@code Iterable}. * To iterate over all of the keys in the symbol table named {@code st}, * use the foreach notation: {@code for (Key key : st.keys())}. * * @return all keys in this symbol table */ public Iterable<Key> keys() { Queue<Key> queue = new Queue<Key>(); for (int i = 0; i < m; i++) if (keys[i] != null) queue.enqueue(keys[i]); return queue; } // integrity check - don't check after each put() because // integrity not maintained during a delete() private boolean check() { // check that hash table is at most 50% full if (m < 2*n) { System.err.println("Hash table size m = " + m + "; array size n = " + n); return false; } // check that each key in table can be found by get() for (int i = 0; i < m; i++) { if (keys[i] == null) continue; else if (get(keys[i]) != vals[i]) { System.err.println("get[" + keys[i] + "] = " + get(keys[i]) + "; vals[i] = " + vals[i]); return false; } } return true; } /** * Unit tests the {@code LinearProbingHashST} data type. * * @param args the command-line arguments */ public static void main(String[] args) { LinearProbingHashST<String, Integer> st = new LinearProbingHashST<String, Integer>(); for (int i = 0; !StdIn.isEmpty(); i++) { String key = StdIn.readString(); st.put(key, i); } // print keys for (String s : st.keys()) StdOut.println(s + " " + st.get(s)); } }