什么是哈希表?
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
记录的存储位置=f(关键字)
这里的对应关系f称为散列函数,又称为哈希(Hash函数),采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。(或者:把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。) 而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。
数组的特点是:寻址容易,插入和删除困难;
而链表的特点是:寻址困难,插入和删除容易。
那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为“链表的数组”,如图:
左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。
下面是使用go模拟哈希表代码的简单实现:
package HashMp
import (
"MyHashMap/LinkedNodes"
"fmt"
)
//定义数组的全局变量
var arr [16] *LinkedNodes.Nodes
//创建16个顶层节点,放到数组中
func CreateArry (){
var ar = [16]*LinkedNodes.Nodes{}
for i := 0; i < 16 ; i++ {
ar[i] = LinkedNodes.CreateArryNode("顶层节点","顶层节点")
}
//赋值给全局变量
arr = ar
//fmt.Println(ar)
}
//向数组中添加键值对
func AddKVToArr(k,v string){
//先计算出要添加的数据存储到哪个下角标中,这里调用从网上找的算法
var corner = HashCode(k)
var head *LinkedNodes.Nodes = arr[corner]
//调用添加方法
LinkedNodes.AddChilddNode(k,v,head)
}
//获取数据
func GetValue(k string) string{
//先判断是哪个下标存储
var corner = HashCode(k)
//获取头节点
var head *LinkedNodes.Nodes = arr[corner]
//通过头节点遍历
for {
if head.Data.K == k {
fmt.Println(head.Data.V)
break
}else {
head = head.NextNode
}
}
return ""
}
//将key转换成数组下标的散列算法,范围16之间
func HashCode(key string) int{
var index int = 0
index = int(key[0])
for k:= 0; k< len(key) ; k++ {
index *= (1103515245 + int(key[k]))
}
index >>= 27
index &= 16 - 1
return index
}
package LinkedNodes
import "fmt"
//申明全局变量,保存头节点
var heads *Nodes //头节点 为了遍历使用
var currs *Nodes //当前节点
//定义结构存储,存储数组每个下标所包含的单独的map数据
type MP struct {
K string
V string
}
//创建结构体,用以申明每个节点
type Nodes struct {
Data MP // 数据信息
NextNode *Nodes //下一个节点的地址
}
//创建头节点
func CreateArryNode(k,v string) *Nodes{
//创建Node头节点
var node *Nodes = new(Nodes)
//封装数据
node.Data.V = v
node.Data.K = k
//指定下一个节点地址,因为还没添加所以是nil
node.NextNode = nil
//第一次创建头节点
heads = node
currs = node
return node
}
//向指定的节点中添加数据 第二个参数:指定哪一个节点
func AddChilddNode (k,v string,currs *Nodes) *Nodes{
var newNode *Nodes = new(Nodes)
//添加信息
newNode.Data.K = k
newNode.Data.V = v
newNode.NextNode = nil
//挂接节点
currs.NextNode = newNode
currs = newNode
//fmt.Println(curr)
return newNode
}
//遍历指定的节点链表
func ShowNode(n *Nodes){
var node = n
for {
if node.NextNode != nil{
fmt.Println(node.Data)
node = node.NextNode
}else {
fmt.Println(node.Data)
break
}
}
}
//计算节点个数
func NodesCount ()int{
var n = heads
var flag int //临时存储节点个数变量
for {
if n.NextNode != nil{
flag+=1
//fmt.Println(n.data)
n = n.NextNode
}else {
flag+=1
//fmt.Println(n.data)
break
}
}
//fmt.Println("节点个数是:",flag)
return flag
}
测试:
package main
import "MyHashMap/HashMp"
//程序入口,主执行
func main () {
//创建数组,添加顶层节点
HashMp.CreateArry()
//随机向数组的每个下标添加子节点
HashMp.AddKVToArr("abc","世界")
HashMp.AddKVToArr("def","和平")
HashMp.GetValue("abc")
}