算法随笔 — 搜索查找算法 — 哈希表

哈希表原理

哈希表是一种能够快速索引到数据的数据结构,与此类似的是数组,和数组不同的是数组是通过数字下标来索引到数据,而哈希表可以通过任意数据结构来定位。

哈希表原理
哈希表的原理如上图所示,本质上还是需要数组,但是我们可以通过设计哈希函数来将任意数据结构来映射成数组下标。

在学习哈希表时,我们的重点工作是设计一个合适的哈希函数。设计哈希函数有很多方法,并没有一个标准答案,不同场景有不同的最佳实践方式,但是还是有很多范式可以参考的,本文先不讨论这方面。(其实是我现在也没会多少 o( ̄︶ ̄)o)

当然这种设计方式会出现一个问题,就是“哈希冲突”,所谓“哈希冲突”就是哈希函数将超过一个元素映射到同一个位置上,而对待哈希冲突的态度应该是怎么解决而不是避免。

解决哈希冲突方法主流的有以下几种:

  1. 开放定址法(线性探测)
    开放定址法
  2. 再哈希法
    当出现哈希冲突的时候那就再换个哈希函数再哈希一遍就好了,使用这种方法的时候可以多设置几个哈希函数。
    但是还是要考虑最坏情况,就是经过所有的哈希方法之后还是会有冲突,所以还是要用其他哈希方法做兜底。
  3. 建立公共溢出区
    建立公共溢出区
    公共溢出区可以用其他数据结构来维护
  4. 链式地址法(拉链法)
    这个方法是最常用的,原理是在哈希表的基础上,数组存储的是链表
    拉链法

这里提供一种使用拉链法处理冲突的哈希表设计范式,有很多地方可以根据具体场景进行优化

class Node<T> {
   
    val: T
    next: Node<T> | null
    constructor(val: T) {
   
        this.val = val
        this.next = null
    }

    //* 在当前节点后面插入一个节点
    insert(node: Node<T>) {
   
        node.next = this.next
        this.next = node
    }
}

class HashTable<T> {
   
    cnt: number
    data: Array<Node<T> | null>
    constructor(n: number) {
   
        this.data = (new Array(n) as any).fill(null)
        this.cnt = 0
    }

    insert(s: T) {
   
        let ind = this.hash_func(s) % this.data.length
        const node = new Node(s)
        this.cnt++
        if (!this.data[ind]) {
   
            this.data[ind] = node
            return
        }
        let p = this.data[ind] as Node<T>
        while (p.next && p.next.val !== s) p = p.next
        if (p.next === null) {
     // 走到最后还没找到
            p.insert(node)
            if (this.cnt > this.data.length * 3) this.expand()
        }
    }

    find (s: T) {
   
        const ind = this.hash_func(s) % this.data.length
        let p = this.data[ind]
        while (p && p.val !== s) p = p.next
        return p !== null
    }

    private expand() {
   
        //* 开辟新的哈希表
        const n = this.data.length * 2
        const h = new HashTable<T>(n)
        //* 数据迁移
        let p: Node<T> | null
        this.data.forEach(d => {
   
            p = d
            while (p) {
   
                h.insert(p.val)
                p = p.next
            }
        })
        this.data = h.data
    }

    //* 计算哈希值
    private hash_func(s: T): number {
   
        let str = JSON.stringify(s), hash: number = 0, seed = 131
        for (let i = 0; i < str.length; i++) {
   
            hash = hash * seed + str[i].charCodeAt(0)
        }
        return hash & 0x7fffffff
    }
}

//* 测试
const h = new HashTable<string>(5) // string -> number
h.insert('aaa')
h.insert('bbb')
h.insert('ccc')
h.insert('ddd')
h.insert('eee')
h.insert('fff')
h.insert('ggg')
console.log(h.data)
console.log(h.find('ggg'))
</
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值