哈希表,是根据key值直接进行数据访问的数据结构。即通过一个hash函数,将key转换成换成数组的索引值,然后将value存储在该数组的索引位置。
hash函数应该具有以下特性:
- 输入参数,其值域范围可以是无穷大的
- 输出结果,其值域范围可以很大,但是一定有穷
- 哈希函数没有任何随机的机制,固定的输入一定是固定的输出
- 输入无穷多但是输出值有限,所以不同的输入可能会有相同的输出(哈希碰撞)
哈希表的实现一般有两种方法:拉链法(又称链接法)和开放寻址法
拉链法
即当有两个不同的key,经过hash函数,被hash到同一个位置的时候,不直接存储在该索引下,而是将该值加到链表中,以免覆盖第一个具有相同hash的key值。链表法对内存的利用率比开放寻址法要高。因为链表结点可以在需要的时候再创建,并不需要像开放寻址法那样事先申请好
type HashTable interface {
Get(int) (interface{}, bool)
Set(int, interface{})
Delete(int)
Traverse()
}
//kv节点
type Node struct {
Key int
Value interface{}
Next *Node
}
type SimpleHash struct {
BasicArr []*Node //底层数组
Size int //元素个数
Len int //数组长度
}
//确保实现了所有接口方法
var _ HashTable = &SimpleHash{}
//构造函数
func NewSimpleHash(len int) *SimpleHash {
return &SimpleHash{
BasicArr: make([]*Node, len),
Size: 0,
Len: len,
}
}
func (h *SimpleHash) get(key int) (*Node, bool) {
exist := false
index := key % h.Len
head := h.BasicArr[index]
//查找
for head != nil {
if head.Key == key {
exist = true
break
}
head = head.Next
}
return head, exist
}
func (h *SimpleHash) Get(key int) (value interface{}, isExist bool) {
node, isExist := h.get(key)
if isExist {
value = node.Value
}
return
}
//设置kv键值对
func (h *SimpleHash) Set(key int, value interface{}) {
node, isExist := h.get(key)
if isExist {
node.Value = value
return
}
//不存在时新建
h.Size++
index := key % h.Len
head := h.BasicArr[index]
newNode := &Node{Key: key, Value: value}
if head != nil {
newNode.Next = head
}
h.BasicArr[index] = newNode
}
func (h *SimpleHash) Delete(key int) {
_, isExist := h.get(key)
//不存在时直接返回
if !isExist {
return
}
h.Size--
index := key % h.Len
head := h.BasicArr[index]
if head.Key == key {
h.BasicArr[index] = head.Next
return
}
for head.Next != nil {
next := head.Next
if next.Key == key {
head.Next = next.Next
return
}
head = head.Next
}
}
func (h *SimpleHash) Traverse() {
for _, head := range h.BasicArr {
traveList(head)
}
}
func traveList(head *Node) {
if head == nil {
return
}
for head != nil {
if head.Next == nil {
fmt.Printf("%v-->%v\n", head.Key, head.Value)
return
}
fmt.Printf("%v-->%v ", head.Key, head.Value)
head = head.Next
}
}
开放寻址法
又称开放定址法,当哈希碰撞发生时,从发生碰撞的那个单元起,按照一定的次序,从哈希表中寻找一个空闲的单元,然后把发生冲突的元素存入到该单元。开放寻址法的好处是不使用指针,直接使用哈希函数算出存取的位置。
不足:开放寻址法解决冲突的散列表,删除数据的时候比较麻烦,需要特殊标记已经删除掉的数据。而且,在开放寻址法中,所有的数据都存储在一个数组中,比起链表法来说,冲突的代价更高。
const N = 200003
const null = math.MaxInt
//底层数组
var buf [N]int
func init() {
for i := 0; i < N; i++ {
buf[i] = null
}
}
//查找位置
func find(key int) int {
k := (key%N + N) % N
for buf[k] != null && buf[k] != key { //说明该位置被占用
k++ //开放寻址法向后一位移动
if k == N { //当移动到最后一位时,循环到第一个位置
k = 0
}
}
// 返回下标,如果x在哈希数组h中,就返回x在h中的位置,
// 如果不在,就返回应该存储的位置
return k
}
func Set(key int) {
k := find(key)
buf[k] = key
}
func Get(key int) (value int, isExist bool) {
i := find(key)
if buf[i] != null {
return buf[i], true
} else {
return null, false
}
}