散列表原理及實現

散列表原理及實現

散列表原理


散列表:使用算術操作將鍵轉化爲數組的索引來訪問數組中的鍵值對, 使用散列表,可以實現常數級別的查找和插入.

使用散列的查找算法主要要解決的兩個問題:


  1. 散列函數的設計(即如何用散列函數將被查找的鍵轉化爲數組的一個索引).
  2. 處理碰撞衝突的過程(即處理兩個或多個鍵的散列值相同的情況).
    PS:處理碰撞衝突的方法主要有拉鍊法線性探測法.

散列函數的設計


實現散列函數的指導思想 :

設計的散列函數能夠均勻並獨立地將所有的鍵散佈於0 ~ M-1之間.(其中M爲存放鍵值的數組的大小)

優秀的散列方法需要滿足三個條件:

  • 一致性 : 等價的鍵必然產生相等的散列值
  • 高效性 : 計算簡便
  • 均勻性 : 均勻地散列所有的鍵

散列方法舉例—hashCode()方法結合除留餘數法

將默認的hashCode()方法與除留餘數法結合起來產生一個0~M-1的整數,一般會將數組的大小M取爲素數以充分利用原散列值的所有位

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

處理碰撞衝突

碰撞處理就是處理兩個或多個鍵的散列值相同的情況

拉鍊法


將大小爲M的數組中的每個元素指向一條鏈表,鏈表的每個節點都存儲了散列值爲該元素的索引的鍵值對.

使用拉鍊法查找鍵值的過程 :

  1. 根據散列值找到對應鏈表
  2. 沿着對應鏈表查找相對應的鍵

拉鍊法實現

1. 拉鍊法基礎方法
public class SeparateChainingHashST<Key extends Comparator<? super Key>,Value> {
    private int N;//鍵值對總數
    private int M;//散列表使用的數組大小
    private SequentialSearchST<Key,Value>[] st;//存放鏈表對象的數組,實現可參照<算法第四版SequentialSearchST.class>

    public SeparateChainingHashST(){
        this(997);
    }
    public SeparateChainingHashST(int M){
        /** 創建M條鏈表 */
        this.M=M;
        st=(SequentialSearchST<Key,Value>[])new SequentialSearchST[M];
        for(int i=0;i<M;i++){
            st[i]=new SequentialSearchST();
        }
    }
 }
2. 拉鍊法中的hash函數實現
    /**
     * hash函數
     * @param key 要插入的鍵
     * @return hash值
     */
    private int hash(Key key){
        return (key.hashCode()&0x7fffffff % M);
    }
3. 拉鍊法的put()和get()方法
    public void put(Key key,Value value){
        st[hash(key)].put(key,value);
    }

    public Value get(Key key){
        return (Value)st[hash(key)].get(key);
    }

線性探測法


開放地址散列表 : 用大小爲M的數組保存N個鍵值對,其中M>N,依靠數組中的空位來解決碰撞衝突(線性探測法是最簡單的開放地址散列表)

線性探測法原理 :
當碰撞發生時(當一個鍵的散列值已經被另外一個不同的鍵佔用),直接檢查散列表的下一個位置(索引值+1)直到遇到相同的鍵或者遇到了空缺的位置
遇到相同的鍵則替換其值,遇到空缺位置則把鍵值寫入即可.

線性探測法實現

1. 線性探測法基礎方法
public class LinearProbingHashST <Key,Value>{
    private int N;//存放的鍵值對的總數
    private int M;//線性檢測表的大小
    private Key[] keys;//鍵
    private Value[] values;//值
    public LinearProbingHashST(){
        keys=(Key[]) new Object[M];
        values=(Value[])new Object[M];
    }
    private LinearProbingHashST(int size){
        this.M=size;
        keys=(Key[]) new Object[M];
        values=(Value[])new Object[M];
    }
    /**
     * resize()方法就是新建一個線性探測表,然後將原表的數據插入
     * @param size 線性探測表的大小
     */
    private void resize(int size){
        LinearProbingHashST newTable=new LinearProbingHashST<Key,Value>(size);
        for(int i=0;i<M;i++){
            if(keys[i]!=null){
                newTable.put(keys[i],values[i]);
            }
        }
        keys=(Key[])newTable.keys;
        values=(Value[])newTable.values;
        M=newTable.M;
    }
}
2. 線性檢測法中的hash函數實現(與拉鍊法相同)
    private int hash(Key key){
        return (key.hashCode()&0x7fffffff)%M;
    }
3. 線性檢測法中的put()和get()方法實現
    public void put(Key key,Value value){
        if(N>=M){
            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++;
    }
    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. 線性檢測法中的delete()方法實現
    public void delete(Key key){
        /** 沒有找到key */
        if(get(key)==null){
            return;
        }
        /** 找到key對應的數組索引位置,並將其鍵與值刪除 */
        int i=hash(key);
        while(!keys[i].equals(key)){
            i=(i+1)%M;
        }
        keys[i]=null;
        values[i]=null;
        /** 將被刪除鍵的右側連續的所有鍵重新插入 */

        while(keys[i]!=null){
            Key keyToRedo=keys[i];
            Value valueToRedo=values[i];
            keys[i]=null;
            values[i]=null;
            N--;
            put(keyToRedo,valueToRedo);
            i=(i+1)%M;
        }
        N--;
        if(N>0&&N==M/8) resize(M/2);
    }

拉鍊法與線性檢測法的性能差距

拉鍊法爲每個鍵值對都分配了一小塊內存,而線性探測法則爲整張表使用了兩個很大的數組,但是兩者的性能差距還是因場景不同而有所變化,最好的方法還是去實踐一下.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章