算法(5)哈希表

1.0 问题描述

实现数据结构:哈希表。

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)。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 如需转载, 请咨询作者, 并且注明出处.有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: ...
    coderwhy阅读 11,373评论 19 121
  • 一.概念 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可...
    lfp901020阅读 2,945评论 0 2
  • 哈希表定义 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结...
    n油炸小朋友阅读 4,835评论 0 22
  • 哈希表:即散列存储结构。散列法存储的基本思想:建立记录关键码字与其存储位置的对应关系,或者说,由关键码的值决定数据...
    linbj阅读 6,249评论 1 5
  • 有两个字典,分别存有 100 条数据和 10000 条数据,如果用一个不存在的 key 去查找数据,在哪个字典中速...
    和风细羽阅读 2,339评论 0 5