学习 Go 语言数据结构:实现哈希表

简介: 哈希表是开发过程中最常使用的一种数据结构,该数据结构不是使用自定义的键来存储 map 中的值,而是对键执行散列函数,以返回数组中一个项目的确切索引。

前言


哈希表是开发过程中最常使用的一种数据结构,该数据结构不是使用自定义的键来存储 map 中的值,而是对键执行散列函数,以返回数组中一个项目的确切索引。


原理


  • 链接法
  • 开放定址法


  1. 创建一个长度等于哈希表中键/值对的预期数量的数组。数组越大,发生碰撞的机会就越低
  2. 创建一个散列函数,它将获取您要添加的键的值并将其转换为数字。此功能越好,碰撞的机会就越低
  3. 取散列函数生成的数字并计算与数组长度的模数。(例如,如果散列为 1234,数组长度为 100,则计算 1234 % 100)。这将是要存储值的数组中的索引。


Go 语言实现


将哈希表表示为 map,实现四个功能:

  • Insert()
  • Search()
  • Delete()
  • Size()


首先,可以为底层数据存储选择一个数组大小(桶数量)。在目前的实现是固定大小的,但在实际的版本将能够在键的数量达到数组的长度时,动态地创建一个更大的数组(Java JDK 7 hashmap 的实现方式)。

const ArraySize = 7 // 哈希表的桶数量


其次,像链表一样,也需要节点用来存储数据定义:

// 桶结构(本质即链表)
type bucket struct {
  head *bucketNode
}
// 桶节点
type bucketNode struct {
  key  string
  next *bucketNode
}

定义哈希表数据结构:

  • 第一个字段 Table 被定义为一个 map,并将一个整数与链表节点(*Node) 关联
  • 第二个字段 Size 为哈希表的长度
// 哈希表结构体
type HashTable struct {
  array [ArraySize]*bucket
}

最终,哈希表能有的链表数量与其桶数量相同。


哈希映射中最重要的事情之一可能是哈希函数。接下来是哈希函数,hashFunction() 函数使用了模运算符:

// 哈希函数
func hash(key string) int {
  sum := 0
  for _, v := range key {
    sum += int(v)
  }
  return sum % ArraySize
}


接下来是插入、查找和删除功能:

// 插入将传入一个 key 参数,并将其添加到哈希表中
func (ht *HashTable) Insert(key string) {
  index := hash(key)
  ht.array[index].insert(key)
}
// 查找将接收一个 key 参数,如果在表中,则返回 true,如果不在,则返回 false
func (ht *HashTable) Search(key string) bool {
  index := hash(key)
  return ht.array[index].search(key)
}
// 删除将接收一个 key 参数,并从哈希表中删除它
func (ht *HashTable) Delete(key string) {
  index := hash(key)
  ht.array[index].delete(key)
}


以下是通过链表的方式实现的查找、插入和删除:

// 链表查找
func (b *bucket) search(k string) bool {
  currNode := b.head
  for currNode != nil {
    if currNode.key == k {
      return true
    }
    currNode = currNode.next
  }
  return false
}
// insert 将输入一个 key ,用 key 创建一个节点,并将该节点放入桶内
func (b *bucket) insert(k string) {
  if !b.search(k) {
    newNode := &bucketNode{key: k}
    newNode.next = b.head
    b.head = newNode
  } else {
    fmt.Print(k, "already exists!")
  }
}
// delete 将接收一个 key ,并从桶中删除该节点
func (b *bucket) delete(k string) {
  if b.head.key == k {
    b.head = b.head.next
    return
  }
  prevNode := b.head
  for prevNode.next != nil {
    if prevNode.next.key == k {
      // 删除
      prevNode.next = prevNode.next.next
      return
    }
    prevNode = prevNode.next
  }
}


main 函数如下:

func main() {
  hashTable := Init()
  list := []string{
    "A",
    "B",
    "C",
    "D",
  }
  for _, v := range list {
    hashTable.Insert(v)
  }
  hashTable.Delete("C")
}

总结


哈希表可以在 O(1) 的时间内访问键和值。哈希表非常适用于字典,或其他需要搜索大量数据的应用中。

相关文章
|
8天前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
12天前
|
程序员 Go 云计算
2023年学习Go语言是否值得?探索Go语言的魅力
2023年学习Go语言是否值得?探索Go语言的魅力
|
2月前
|
存储 消息中间件 缓存
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
62 0
|
11天前
|
搜索推荐 算法 Go
深入探索堆:Go语言中的高效数据结构
深入探索堆:Go语言中的高效数据结构
|
5天前
|
算法 NoSQL 中间件
go语言后端开发学习(六) ——基于雪花算法生成用户ID
本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
go语言后端开发学习(六) ——基于雪花算法生成用户ID
|
6天前
|
JSON 缓存 监控
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
|
17天前
|
Go
Go - 学习 grpc.Dial(target string, opts …DialOption) 的写法
Go - 学习 grpc.Dial(target string, opts …DialOption) 的写法
40 12
|
12天前
|
SQL 关系型数据库 MySQL
「Go开源」goose:深入学习数据库版本管理工具
「Go开源」goose:深入学习数据库版本管理工具
「Go开源」goose:深入学习数据库版本管理工具
|
1月前
|
Cloud Native Java Go
为什么要学习Go语言?
GO logo的核心理念,即简单胜于复杂。使用现代斜体无衬线字体与三条简单的运动线相结合,形成一个类似于快速运动的两个轮子的标记,传达速度和效率。字母的圆形暗示了GO地鼠的眼睛,创造了一个熟悉的形状,让标记和吉祥物很好地搭配在一起。
38 4
|
1月前
|
Dart 算法 JavaScript
C#数据结构与算法入门教程,值得收藏学习!
C#数据结构与算法入门教程,值得收藏学习!