哈希数据结构的增强

1. 背景

哈希作为一种常用的数据结构,key-value 的读写都很有着很高的性能,时间复杂度 O ( 1 ) O(1) O(1)。但也存在着一些限制,比如:

  • 关于哈希的遍历都是无序的,所以每次遍历的结果都不一样。
  • 怎么做到随机返回哈希中的一对 key-value,要求时间复杂度 O ( 1 ) O(1) O(1),而且是均匀的。

现在我们就针对这 2 个问题讨论下怎么设计。

2. 链表加强哈希,实现遍历有序

思路:

  • 继续使用哈希数据结构存储 key-value,毕竟这是最高效的
  • 如何保证有序:使用双向链表
    • 添加 key-value 时,将 key-value 添加在链表的头部,当然也可能是移动到链表头部。
    • 删除 key-value 时,将 key-value 从链表中删除。
    • 遍历时,从链表的头部开始直到链表的尾部,因为链表的遍历是有序的,所以整个遍历结果也是有序的。
Golang 复制代码
type Node[K comparable, T any] struct {

    key K

    value T

    next *Node[K, T]

    prev *Node[K, T]

}


type LinkedList[K comparable, T any] interface {

    MoveToHead(node Node[K, T])

    AddToHead(node Node[K, T])

    DeleteNode(node Node[K, T])

    Next() Node[K, T]

    HasNext() bool

}



type HashSet[K comparable, T any] struct {

    linkedList LinkedList[K, T]

    keyNode map[K]*Node[K, T]

}

  


func (h *HashSet[K, T]) Get(key K) (T, error) {

    var value T

    node, ok := h.keyNode[key]

    if !ok {

        return value, errors.New("key not found")

    }

    return node.value, nil

}


func (h *HashSet[K, T]) Set(key K, value T) error {

    node, ok := h.keyNode[key]

    if ok {

        node.value = value

        h.linkedList.MoveToHead(*node)

        return nil

    }

    node = &Node[K, T]{key: key, value: value, prev: nil, next: nil}

    h.keyNode[key] = node

    h.linkedList.AddToHead(*node)

    return nil

}

 

func (h *HashSet[K, T]) Delete(key K) error {

    node, ok := h.keyNode[key]

    if !ok {

        return errors.New("key not found")

    }

    delete(h.keyNode, key)

    h.linkedList.DeleteNode(*node)

    return nil

}


type procFunc[K comparable, T any] func(key K, value T)
  

func (h *HashSet[K, T]) iterator(proc procFunc[K, T]) {

    for h.linkedList.HasNext() {

        node := h.linkedList.Next()

        proc(node.key, node.value)

    }

}

时间复杂度并没有增加,读写依旧是 O ( 1 ) O(1) O(1),空间复杂度比普通哈希数据结构翻倍。

3. 数组加强哈希,实现 O ( 1 ) O(1) O(1) 复杂度随机返回一对 key-value

思路:

  • 标准数组可以实现 O(1) 复杂度在随机生成一个 [0, size) 的索引。

  • 基于拉链法实现的哈希,虽然本质使用了一个数组,但每一个数组都都是一个指向头节点的链表。不知道数组的长度,也不知道每一个链表的长度。必然需要先遍历才能实现返回一对随机的 key-value,时间复杂度不满足。

  • 基于线性探测法,可能存在空洞,每一对 key-value 返回的概率不等。

    • 向左或向右查找,找到第一个非空元素。时间复杂度不满足,不是均匀随机的,沿固定方向查找会导致某一侧元素被选中的概率增大。
    • 多次随机------遇到空洞就再随机生成一个索引,这样做是均匀的,但是时间复杂度不满足。
  • 基于标准哈希 + 数组实现

    • 哈希的 value 是数组的索引。
    • 添加一对 key-value 就在数组后面添加一个元素。
    • 删除一对 key-value 需要在数据中删除对应的元素,但我们可以简化操作,将最后一个元素替换到删除的索引,然后将数据的长度剪 1。
goland 复制代码
type array[T any] []T


func (a array[T]) pop() {

    size := len(a)

    if size == 0 {

        return

    }

    newArray := make([]T, size-1)

    copy(newArray, a[0:size-1])

    a = newArray

}


type HashSet[K comparable, T any] struct {

    values array[T]

    indexes map[K]int

}


func (h *HashSet[K, T]) Get(key K) (T, error) {

    var value T

    index, ok := h.indexes[key]

    if !ok {

        return value, errors.New("key not found")

    }

    value = h.values[index]

    return value, nil

}


func (h *HashSet[K, T]) Set(key K, value T) {

    index, ok := h.indexes[key]

    if ok {

        h.values[index] = value

    return

    }


    index = len(h.values)

    h.values = append(h.values, value)

    h.indexes[key] = index

}


func (h *HashSet[K, T]) Delete(key K) error {

    index, ok := h.indexes[key]

    if !ok {

        return errors.New("key not found")

    }

  
    lastIndex := len(h.indexes) - 1

    // 交换 value

    h.values[index], h.values[lastIndex] = h.values[lastIndex], h.values[index]

    // 删除最后一个元素。可优化,删除大量元素后,再缩容

    h.values.pop()

    // 删除 key

    delete(h.indexes, key)

    return nil

}


func (h *HashSet[K, T]) Random() T {

    size := len(h.indexes)

    index := rand.Intn(size)

    return h.values[index]

}
相关推荐
叩码以求索1 小时前
浅谈:前序遍历反转法求解N叉树的后序遍历
算法
北风toto2 小时前
中缀、前缀、后缀表达式
算法·软件设计师
听取WA声一片(无恶意)2 小时前
CSP-J/CSP-S 深度优先搜索(DFS)完全讲义
c++·算法·深度优先
码流怪侠3 小时前
2026年8月GitHub热榜深度拆解:Agent Skills席卷开源圈,一个“技能包“收割5万星
算法·程序员·github
hold?fish:palm3 小时前
30 两两交换链表中的节点
数据结构·算法·链表
Nil2083 小时前
leetcode 98验证二叉搜索树
算法·leetcode·职场和发展
不正经学生3 小时前
C语言结构体:自定义数据类型,让变量打包出行
java·c语言·开发语言·数据结构·算法
cxr8284 小时前
数学的本质:关系、模式与不变量的结构世界
人工智能·算法·机器学习
初夏睡觉5 小时前
原创题目 - 空壳恋爱
c++·算法·dp·oi·信息学·原创题目·值得挑战的题目
Buke..5 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(下):OLLVM 混淆还原 sign 算法
java·javascript·爬虫·python·算法