Go 实现哈希表 (拉链法+开放寻址法)

659 阅读3分钟

哈希表,是根据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
   }
}