go 哈希表——map的简单实现

哈希表

  • 什么是哈希表?

    • 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
    • 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
  • 那什么是哈希?

    • Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数,这个数称为Hash,常见的MD5,SHA1等
  • 从这里可以看出来,我们常用的map或者说dictionary都属于哈希表里的关系

  • 那么我们就来简单的实现下,简单的map吧

拉链法(链地址法)

  • 在讲解步骤之前,首先说下为什么要用拉链法,而不是其他
    • 首先拉链法的学习只需要数组和链表的知识点即可
    • 其次,拉链法很简单的就解决了哈希表冲突
  • 其特点:
    • 数组的特点是:寻址容易,插入和删除困难。
    • 而链表的特点是:寻址困难,插入和删除容易。
    • 所以,哈希里拉链法就是寻址容易,插入删除简单

实现步骤

  1. //1.首先定义结构体
    type MpNode struct {
     Data Dict    // 定义数据  数据为存放k v 的结构体
     Next *MpNode // 下个节点
    }
    type Dict struct {
     Key   string
     Value interface{}
    }
    
    //2 初始化
    //初始化链表的头节点
    func newNodeHead() *MpNode {
     node := new(MpNode)
     node.Data.Key = "头key"
     node.Data.Value = "头value"
     node.Next = nil
     return node
    }
    
    //链方法
    //3 封装结构体对象方法
    // 封装key value 存放方法
    func (node *MpNode) data(k string, v interface{}) *MpNode {
     if node == nil {
         node = newNodeHead()
     }
     node.Data.Key = k
     node.Data.Value = v
     return node
    }
    
    //4  向该链空节点创建新数据
    func (node *MpNode) add(k string, v interface{}) {
     // 首先判断k是否是该链已有的key  因为根据哈希算法简化后  同样的字符串会在同样的链里存放
     if node.getKey(k) != nil {
         return
     }
     //遍历到尾节点
     for node.Next != nil {
         node = node.Next
     }
     // 创建新的尾节点
     node.Next = node.Next.data(k, v)
    }
    

到这里我们已经初步实现了单链里存储key,value值

  • //5 测试下添加数据是否有问题
    func MpDemo() {
      node := newNodeHead()
      node.add("1", "2")
      node.add("2", 3)
      node.Log()//看下面
      fmt.Println(node.Length())
    }
    
    
    //6 发现这样打印看不太方便 于是写个遍历值
    func (node *MpNode) Log() {
      if node.Next == nil {
          return
      }
      fmt.Println(node.Next.Data)
      node.Next.Log()
    }
    
    // 7计算链表长度 防止数据堆积 为后续优化做准备
    func (node *MpNode) Length() int {
       if node == nil {
          return 0
       }
       i := 0
       for node.Next != nil {
          i++
          node = node.Next
       }
       return i
    }
    
    //8 开始创建数组链表  也就是哈希表
    // 定义数组链表
    var Arr [16]*MpNode
    
    //9 初始化哈希表
    func NewHash() {
       for i := 0; i < 16; i++ {
          Arr[i] = newNodeHead()
       }
    }
    
    //哈希表方法
    //10 往表里存key值
    func SetKey(k string, v interface{}) {
       // 存k先判断存哪里  这里才使用了散列算法计算
       num := hashNum(k)
       Arr[num].add(k, v)
    }
    

    ==这里你会发现,欸,哈希算法怎么写?于是我找了个简单的,并且能手撕的算法==

    //11 获取16以内的散列算法
    func hashNum(key string) int {
       var index int = 0
       index = int(key[0])
       // 询价累积新的数值
       for k := 0; k < len(key); k++ {
          index *= (1103515245 + int(key[k]))
       }
       // 右位移2^27次方  这里可以位移任何数  只要别太大导致直接清空
       index >>= 27
       // 按位&  这里想要求32以内就 32-1即可   也就是说  index跟 1111 进行&  得到15以内的数  跟11111取余则得31以内的数
       index &= 16 - 1
    
       return index
    }
    
    //12 根据key取value
    func (node *MpNode) getKey(k string) interface{} {
       if node.Next == nil {
          return nil
       }
       for node.Next != nil {
          if node.Next.Data.Key == k {
             return node.Next.Data.Value
          } else {
             node = node.Next
          }
       }
       return nil
    }
    //13 根据valve取key  后面发现如果要写这个 就要给每个数组都遍历一遍后  就暂且放弃 等后续优化
    //func (node *MpNode) getValue(v interface{}) string {
    //    if node.Next == nil {
    //        return ""
    //    }
    //    for node.Next != nil {
    //        if node.Next.Data.Value == v {
    //            return node.Next.Data.Key
    //        } else {
    //            node = node.Next
    //        }
    //    }
    //    return ""
    //}
    
    //14 取key值
    func GetKey(k string) interface{} {
       num := hashNum(k)
       return Arr[num].getKey(k)
    }
    

    到目前为止,整个算法结构已经初步完成

运行这个map吧!

  • 首先整个算法在考虑封装等各种因素,对外只包含3个方法

    • NewHash 创建一个哈希表
    • SetKey 存键值对
    • GetKey 取键值对
  • 附上全代码

  • package data
    
    import "fmt"
    
    //1.首先定义结构体
    type MpNode struct {
       Data Dict    // 定义数据  数据为存放k v 的结构体
       Next *MpNode // 下个节点
    }
    type Dict struct {
       Key   string
       Value interface{}
    }
    
    //8 开始创建数组链表  也就是哈希表
    // 定义数组链表
    var Arr [16]*MpNode
    
    //2 初始化
    //初始化链表的头节点
    func newNodeHead() *MpNode {
       node := new(MpNode)
       node.Data.Key = "头key"
       node.Data.Value = "头value"
       node.Next = nil
       return node
    }
    
    //9 初始化哈希表
    func NewHash() {
       for i := 0; i < 16; i++ {
          Arr[i] = newNodeHead()
       }
    }
    
    //链方法
    //3 封装结构体对象方法
    // 封装key value 存放方法
    func (node *MpNode) data(k string, v interface{}) *MpNode {
       if node == nil {
          node = newNodeHead()
       }
       node.Data.Key = k
       node.Data.Value = v
       return node
    }
    
    //12 根据key取value
    func (node *MpNode) getKey(k string) interface{} {
       if node.Next == nil {
          return nil
       }
       for node.Next != nil {
          if node.Next.Data.Key == k {
             return node.Next.Data.Value
          } else {
             node = node.Next
          }
       }
       return nil
    }
    
    //13 根据valve取key  后面发现如果要写这个 就要给每个数组都遍历一遍后  就暂且放弃 等后续优化
    //func (node *MpNode) getValue(v interface{}) string {
    // if node.Next == nil {
    //    return ""
    // }
    // for node.Next != nil {
    //    if node.Next.Data.Value == v {
    //       return node.Next.Data.Key
    //    } else {
    //       node = node.Next
    //    }
    // }
    // return ""
    //}
    
    //4  向该链空节点创建新数据
    func (node *MpNode) add(k string, v interface{}) {
       // 首先判断k是否是该链已有的key  因为根据哈希算法简化后  同样的字符串会在同样的链里存放
       if node.getKey(k) != nil {
          return
       }
       //遍历到尾节点
       for node.Next != nil {
          node = node.Next
       }
       // 创建新的尾节点
       node.Next = node.Next.data(k, v)
    }
    
    //6 发现这样打印看不太方便 于是写个遍历值
    func (node *MpNode) Log() {
       if node.Next == nil {
          return
       }
       fmt.Println(node.Next.Data)
       node.Next.Log()
    }
    
    // 7计算链表长度 防止数据堆积 为后续优化做准备
    func (node *MpNode) Length() int {
       if node == nil {
          return 0
       }
       i := 0
       for node.Next != nil {
          i++
          node = node.Next
       }
       return i
    }
    
    //哈希表方法
    //10 往表里存key值
    func SetKey(k string, v interface{}) {
       // 存k先判断存哪里  这里才使用了散列算法计算
       num := hashNum(k)
       Arr[num].add(k, v)
    }
    //14 取key值
    func GetKey(k string) interface{} {
       num := hashNum(k)
       return Arr[num].getKey(k)
    }
    
    //11 获取16以内的散列算法
    func hashNum(key string) int {
       var index int = 0
       index = int(key[0])
       // 询价累积新的数值
       for k := 0; k < len(key); k++ {
          index *= (1103515245 + int(key[k]))
       }
       // 右位移2^27次方  这里可以位移任何数  只要别太大导致直接清空
       index >>= 27
       // 按位&  这里想要求32以内就 32-1即可   也就是说  index跟 1111 进行&  得到15以内的数  跟11111取余则得31以内的数
       index &= 16 - 1
    
       return index
    }
    
    //5 测试下添加数据是否有问题
    func MpDemo() {
       //node := newNodeHead()
       //node.add("1", "2")
       //node.add("2", 3)
       //node.Log()
       //fmt.Println(node.Length())
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容