学习 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) 的时间内访问键和值。哈希表非常适用于字典,或其他需要搜索大量数据的应用中。

相关文章
|
1月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
178 86
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
285 0
|
3月前
|
存储 监控 算法
公司员工泄密防护体系中跳表数据结构及其 Go 语言算法的应用研究
在数字化办公中,企业面临员工泄密风险。本文探讨使用跳表(Skip List)数据结构优化泄密防护系统,提升敏感数据监测效率。跳表以其高效的动态数据处理能力,为企业信息安全管理提供了可靠技术支持。
76 0
|
4月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
131 10
|
JSON JavaScript Go
Go 语言学习指南:变量、循环、函数、数据类型、Web 框架等全面解析
掌握 Go 语言的常见概念,如变量、循环、条件语句、函数、数据类型等等。深入了解 Go 基础知识的好起点是查阅 Go 官方文档
1052 2
|
自然语言处理 Java Go
Go语言学习之函数
Go语言学习之函数
64 0