算法(5)哈希表

1.0 問題描述

實現數據結構:哈希表。swift

2.0 問題分析

  1. 哈希表能夠看做咱們常常使用的字典(swift)或對象(js),可讓一個key&value對一一對應,能夠快速根據key找到value。
  2. 哈希表內部使用數組實現,咱們須要將不論任何類型的key計算出與之一一對應的數字來,數字的大小介於0到數組尺寸之間,這樣,咱們能夠把value直接存儲到數組的對應位置。
  3. 經過key計算出惟一數字的過程,叫作哈希函數。同一個key每次經過hash函數生成的數字都是相同的。
  4. 因爲不一樣的key可能會生成相同的數字,這種狀況叫作哈希衝突。因爲衝突不可避免,因此咱們有多種辦法來處理衝突。
  5. 處理衝突的方法有:
  • 開放定址法:若是數字相同,則令數字加1,查看數組中是否有值,直到找到空位
  • 鏈表法:數組每一個位置帶一個鏈表,鏈表中存儲衝突的數值
  • 再次散列:屢次調用散列函數,直到不衝突
  • 公共溢出區:將衝突的值放到另一個數組中,一旦衝突,則去新數組查找。
  1. 使用較多的方法是開放定址法。
  2. 爲了提高性能,咱們須要選擇更好的哈希函數,還須要保持較低的填充因子,填充因子=已存儲的數量/當前數組總數,咱們須要作2件事情。
  • 讓哈希函數的衝突更少
  • 填充因子大於0.75,即將數組擴充至當前尺寸的2倍

3.0 代碼實現

3.1使用swift實現

/* * 哈希表Key須要遵照的協議 * 用於生成hash值,即hash函數 */
public protocol HashedKey {
    var hashValue: UInt32 { get }
}


/* * 哈希表內部元素表示 */
fileprivate struct HashElement<Key, Value>{
    var key: Key;
    var value: Value;
}


/* * 哈希表 */
public class Hash<Key, Value> where Key: HashedKey, Key: Equatable{
    ///內部數組
    private var _storeArr:[HashElement<Key, Value>?];
    ///當前元素數量
    private var _storeCount = 0;
    ///最小元素數量
    private let MINSIZE = 1;
    
    /* * 構造方法 */
    public init() {
        _storeArr = Array<HashElement<Key, Value>?>.init(repeating: nil, count: MINSIZE);
    }
    
    /** * 將內部數組的尺寸擴充爲2倍大小 */
    private func _expand(){
        let oldArr = _storeArr;
        _storeArr = Array<HashElement<Key, Value>?>.init(repeating: nil, count: _storeArr.count * 2);
        _storeCount = 0;
        for case let e? in oldArr {
            set(k: e.key, v: e.value);
        }
    }
    
    /** * 插入數值 * @param {Key} - k 插入的key * @param {Value} - v 插入的value */
    public func set(k: Key, v: Value){
        if let i = self._index(k: k){
            _storeArr[i]?.value = v;
            return;
        }
        let capacity = _storeArr.count;
        let hashValue = k.hashValue;
        var idx = Int(hashValue) % capacity;
        while _storeArr[idx] != nil {
            idx =  (idx + 1) % capacity;
        }
        _storeArr[idx] = HashElement.init(key: k, value: v);
        _storeCount += 1;
        
        if _storeCount >= capacity * 3 / 4 {
            _expand();
        }
    }
    
    /** * 查找參數k對應的數組下標值,用於判斷元素是否存在 * @param {Key} - k 傳入key值 * @return {Int?} - 返回數組下標,若key不存在,則返回nil */
    private func _index(k: Key) -> Int?{
        let capacity = _storeArr.count;
        var idx = Int(k.hashValue) % capacity;
        var count = 0;
        while _storeArr[idx] != nil && _storeArr[idx]!.key != k && count < capacity {
            idx = (idx + 1) % capacity;
            count += 1;
        }
        if _storeArr[idx] != nil && count < capacity {
            return idx;
        }else{
            return nil;
        }
    }
    
    /** * 獲取數值 * @param {Key} - k 想要獲取數值的key * @return {Value?} - 返回的value */
    public func get(k: Key) -> Value?{
        if let i = self._index(k: k){
            return _storeArr[i]?.value;
        }else{
            return nil;
        }
    }
    
    /** * 支持以下操做: * hash[key] = value * let v = hash[key] */
    public subscript(k: Key)->Value?{
        set{
            if case let v? = newValue {
                set(k: k, v: v)
            }
        }
        get{
            return get(k: k);
        }
    }
}
複製代碼

3.2使用js實現

function Hash(hashFunction) {
    this._elements = new Array(1);
    this._elementsCount = 0;
    this._hashFunc = hashFunction;
}


Hash.prototype._expand = function(){
    let capacity = this._elements.length;
    let oldElements = this._elements;
    this._elements = new Array(capacity * 2);
    oldElements.forEach(element => {
        this.set(element.key, element.value);
    });
}


Hash.prototype.set = function(k, v){
    let index = this._index(k);
    if(index != undefined && this._elements[index] != v){
        this._elements[index] = v;
        return;
    }
    let capacity = this._elements.length;
    let hashValue = this._hashFunc(k) % capacity;
    while (this._elements[hashValue]) {
        hashValue = (hashValue + 1) % capacity;
    }
    this._elements[hashValue] = {key:k, value:v};
    this._elementsCount++;


    if(this._elementsCount >= capacity * 3 / 4){
        this._expand();
    }
}


Hash.prototype._index = function(k){
    let capacity = this._elements.length;
    let hashValue = this._hashFunc(k) % capacity;
    let count = 0;
    while(this._elements[hashValue] && this._elements[hashValue].key != k && count < capacity){
        hashValue = (hashValue + 1) % capacity;
        count++;
    }
    if(this._elements[hashValue] && count < capacity){
        return hashValue;
    }
}


Hash.prototype.get = function(k){
    let index = this._index(k);
    if(index != undefined) {
        return this._elements[index];
    }
}
複製代碼

4.0 複雜度分析

  1. 插入值
  • 咱們經過哈希函數計算一個數組下標,只有這一次計算。
  • 下標可能有衝突,衝突的數量是常數級。這一點咱們經過良好的哈希函數,和較低的填充因子來保證。
  • 當填充因子達到上限,須要建立新的數組並copy全部數值到新數組中。這一步操做須要O(n)的時間,可是把這一步均攤到以前每個set函數中,咱們會發現至關於每一個set的時間乘以2,也是常數級別。
  • 綜上所述,hash表插入的時間複雜度爲O(1)。
  1. 獲取值
  • 獲取值的過程相似於插入,也是計算一次哈希函數。
  • 若是有衝突,則根據規則繼續操做,經過上面的分析,咱們知道,這一步也是常數級別的時間複雜度。
  • 綜上所述,hash表獲取值的時間複雜度也是O(1)。
相關文章
相關標籤/搜索