- 前言
- Go 与操作系统
- Go 内部机制
- Go 基本数据类型
- 4 组合类型的使用
- 5 数据结构
- 6 Go package 中不为人知的知识
- 7 反射和接口
- 8 Go UNIX 系统编程
- 08.1 关于 UNIX 进程
- 08.2 flag 包
- 8.2 flag 包
- 08.3 io.Reader 和 io.Writer 接口
- 08.4 bufio 包
- 08.5 读取文本文件
- 08.6 从文件中读取所需的数据量
- 08.7 为什么我们使用二进制格式
- 08.8 读取 CSV 文件
- 08.9 写入文件
- 08.10 从磁盘加载和保存数据
- 08.11 再看strings包
- 08.12 关于bytes包
- 08.13 文件权限
- 08.14 处理 Unix 信号
- 08.15 Unix 管道编程
- 08.16 遍历目录树
- 08.17 使用 ePBF
- 08.18 关于 syscall.PtraceRegs
- 08.19 跟踪系统调用
- 08.20 User ID 和 group ID
- 08.21 其他资源
- 08.22 练习
- 08.23 总结
- 9 并发 Goroutines、Channel 和 Pipeline
- 10 Go 并发-进阶讨论
- 11 代码测试、优化及分析
- 12 Go 网络编程基础
- 13 网络编程 - 构建服务器与客户端
05.4.1 Go 语言实现哈希表
Go 语言实现哈希表
下面将 hashTable.go
中的 Go 语言代码分成五个部分来讲解哈希表。
下面是 hashTable.go
中的第一部分:
package main
import (
"fmt
)
const SIZE = 15
type Node struct {
Value int
Next *Node
}
这一部分是哈希表中节点的定义,毫无疑问,我们使用 Go 的结构体进行了实现。SIZE
常量表示哈希表中桶的数量。
下面的 Go 代码是 hashTable.go
中的第二段:
type HashTable struct {
Table map[int]*Node
Size int
}
func hashFunction(i, size int) int {
return (i % size)
}
在这个代码段中,你可以看到在这个哈希表中使用的哈希函数。hashFunction()
使用了模运算符。这里使用模运算符的主要原因是这个哈希表要处理的是整数值的键。如果你要应对的是字符串或浮点数的键,那么你的哈希函数的逻辑应该与这不同。
真正的哈希表存在一个 HashTable
结构体中,它有两个字段。第二个字段表示哈希表的大小,第一个字段则是连接整数和链表(*Node
)的 map。因此,这个哈希表中链表的数量与桶的数量相等。这也意味着哈希表上每个桶中的节点都会使用链表存储。之后会进一步对链表进行介绍。
hashTable.go
的第三部分如下:
func insert(hash *HashTable, value int) int {
index := hashFunction(value, hash.Size)
element := Node{Value: value, Next: hash.Table[index]}
hash.Table[index] = &element
return index
}
insert()
函数用于向哈希表中插入元素。注意,当前 insert()
的实现不会检查值是否重复。
下面是 hashTable.go
的第四部分:
func traverse(hash *HashTable) {
for k := range hash.Table {
if hash.Table[k] != nil {
t := hash.Table[k]
for t != nil {
fmt.Printf("%d -> ", t.Value)
t = t.Next
}
}
fmt.Println()
}
}
traverse()
函数用于输出哈希表所有的值。这个函数访问哈希表中所有链表,然后依次输出每个链表上存储的值。
hashTable.go
的最后一部分代码如下:
func main() {
table := make(map[int]*Node, SIZE)
hash := &HashTable{Table: table, Size: SIZE}
fmt.Println("Numbder of spaces:", hash.Size)
for i := 0; i < 120; i++ {
insert(hash, i)
}
traverse(hash)
}
在这部分代码中,你将创建一个新的、命名为 hash
的哈希表,它接收一个 table
变量,这个变量是一个保存了哈希表中所有桶的 map。正如你所知道的,这个哈希表的槽是使用链表实现的。我们使用 map 保存哈希表中的链表,而没用切片或数组,这主要是因为切片或数组的键只能是正整数,但 map 的键则几乎可以满足你的所有需求。
执行 hashTable.go
将生成如下输出:
$ go run hashTable.go
108 -> 93 -> 78 -> 63 -> 48 -> 33 -> 18 -> 3 ->
109 -> 94 -> 79 -> 64 -> 49 -> 34 -> 19 -> 4 ->
117 -> 102 -> 87 -> 72 -> 57 -> 42 -> 27 -> 12 ->
119 -> 104 -> 89 -> 74 -> 59 -> 44 -> 29 -> 14 ->
111 -> 96 -> 81 -> 66 -> 51 -> 36 -> 21 -> 6 ->
112 -> 97 -> 82 -> 67 -> 52 -> 37 -> 22 -> 7 ->
116 -> 101 -> 86 -> 71 -> 56 -> 41 -> 26 -> 11 ->
114 -> 99 -> 84 -> 69 -> 54 -> 39 -> 24 -> 9 ->
118 -> 103 -> 88 -> 73 -> 58 -> 43 -> 28 -> 13 ->
105 -> 90 -> 75 -> 60 -> 45 -> 30 -> 15 -> 0 ->
106 -> 91 -> 76 -> 61 -> 46 -> 31 -> 16 -> 1 ->
107 -> 92 -> 77 -> 62 -> 47 -> 32 -> 17 -> 2 ->
110 -> 95 -> 80 -> 65 -> 50 -> 35 -> 20 -> 5 ->
113 -> 98 -> 83 -> 68 -> 53 -> 38 -> 23 -> 8 ->
115 -> 100 -> 85 -> 70 -> 55 -> 40 -> 25 -> 10 ->
这个哈希表已经完美平衡,因为它所需要做的是将连续数放入模运算所计算出的槽中。但是现实世界的问题中可能不会出现这么方便的结果!
在欧几里得除法中,两个自然数 a 和 b 之间的余数可以通过公式 a = bq + r 进行计算,这里的 q 是商,r 是余数。余数的值的范围是 0 到 b-1,这个范围中的值都是模运算可能的结果。
注意,如果多次运行 hashTable.go
,你所得到的那些输出的行的顺序很可能不同,因为 Go 的 map 输出键值对的顺序是完全随机的!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论