hash函數進階二

hash函數進階二

1.理論篇

hash可以說是一種數據結構,類似於數組,鏈表;也可以說是一種算法過程,用於將數據散列,便於訪問。

如何使用好hash,主要體現在以下三點:

(1)生成hashcode的算法

(2)散列算法

(3)衝突處理策略

1.1.生成hashcode的算法

對於不同的對象,需要對應不同的關鍵字,因此算法需要儘量滿足不同對象的hashcode值不同。

譬如,現有最多僅有3個字符的全字母字符串待獲取hashcode,以下兩種算法效果就完全不同:

unsigned int gethashcode(const char *str)
{
	unsigned int hashcode = 0;
	
	while(*str)
	{
		hashcode = hashcode + *str++;
	}
	
	return (hashcode & 0x7FFFFFFF);
}
unsigned int gethashcode(const char *str)
{
	unsigned int hashcode = 0;
	
	while(*str)
	{
		hashcode = 24 * hashcode + *str++;
	}
	
	return (hashcode & 0x7FFFFFFF);
}
第一種生成的hashcode重複概率很大(如字符串"ab"和"ba"),因此不是一個好的算法。

1.2.散列算法

好的hashcode算法,會使得不同的對象有不同的hashcode,而散列就是通過hashcode,將對象存放在特定的位置;因此散列算法需要儘量使得不同的hashcode得到的位置不同,即衝突越少越好。

譬如,有如下兩種實現算法:

int getindex(int hashcode, int tablelength)
{
	return (hashcode % tablelength);
}
int getindex(int hashcode, int a, int b)
{
	return (a * hashcode + b);
}
第一種方法是除留餘數法:關鍵點在於除數的選取,一般選擇除數的原則是小於或等於表長的最大質數

第二種方法是直接尋址法:簡單,不常用,可用於特定應用中,如輸入對象是1980到2000年段的大量數據,統計每個出生年有多少人。

  • hashcode算法:hashcode = value - 1980
  • 散列算法:index = hashcode

1.3.衝突解決策略

即使能夠保證hashcode值不同,對於大量的數據需要存放在長度遠小於數據量的情況,必然會出現散列衝突的情況,因此需要設計好的衝突解決方法,以提高效率。

常用的策略有:

  • 開放定址法:衝突時,利用散列結果再散列,如:f(key) = (f(key) + di) % len;(di值可任選)
  • 鏈地址法:衝突時,在衝突的位置利用鏈表鏈接起來。
bool putvalue(int hashcode, int value)
{
	int index = 0, mark = 0;
	
	index = getindex(hashcode, len);
	while((array[index] != -1) && (mark < len))
	{
		index = getindex(index + 1, len);
		mark++;
	}
	
	if(mark != len)
	{
		array[index] = value;
		return true;
	}
	else
	{
		return false;
	}
}
bool putvalue(int hashcode, int value)
{
	int index = 0;
	struct Node *pnode = NULL, *node = NULL;
	
	index = getindex(hashcode, len);
	pnode = array[index];
	while(pnode->next != NULL)
	{
		pnode = pnode->next;
	}
	
	/*malloc node and set value*/
	/*.......*/
	pnode->next = node;
	
	return true;
}

2.實例篇

我們以JDK和C++STL中的hash實現爲例子,分析hash的實現過程。

2.1JDK中的hashtable應用

(1)存儲目標對象數組及初始化

/**
     * The hash table data.
     */
    private transient Entry<?,?>[] table;
public Hashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

thresdhold是一個門限值,當達到該值後,在加入對象會加大長度,重做table。

(2)對象及hashcode算法

對象:<k key, V value>;使用key作爲關鍵字,通過hashcode算法散列到table,而後將<key, value>對象存儲。

默認的hashcode算法是native類型,無法查看到源代碼,但可以參考string對象的覆寫實現:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

:因對於相同值得string對象,其hashcode需要一樣,因此藉助輔助變量hash來判斷是否通過string(str)繼承而來。

(3)添加:散列算法、衝突策略

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }
private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

散列算法:採用的是除留餘數法。

衝突策略:鏈接法。

(4)查詢、獲取

public synchronized boolean containsKey(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return true;
            }
        }
        return false;
    }
@SuppressWarnings("unchecked")
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

2.2 C++ STL中的hashtable應用

未完待續。。。

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