Go语言数据结构与算法—哈希表

1. 概述

哈希表(Hash table),也称为散列表。是根据关键码值而直接进行访问的数据结构。也就是说,它通过关键码值映射到表中一个位置来访问记录,以加快查找的速度。

可以用取快递做一个例子:哈希表相当于快递柜,而关键码值就相当于取件码。通过取件码可以很快的找到自己的快递。

1.1 术语

  • 散列方法:选取某个函数,以该函数按关键字计算元素的存储位置,并按此存放。
  • 散列函数(哈希函数):散列方法中使用的转换函数。
  • 散列表:按散列方法构造的表就是散列表。
  • 冲突:不同关键码映射到同一个散列地址。
  • 同义词:具有相同函数值的多个关键字。

1.2 散列函数的构造

1.2.1 散列函数的特性

  • 压缩性
  • 单向性
  • 确定性
  • 分散性

1.2.2 可选择的方法

  • 直接定址法
  • 数字分析法
  • 平分取中法
  • 折叠法
  • 除留余数法:求关键码key对一个整数的余数,把这个余数当作存储位置
  • 随机数法

1.3 解决冲突问题

1.3.1 可选的方法

  • 开放定址法
  • 链地址法
  • 再散列法
  • 建立一个公共溢出区

1.4 应用场景

  • Java中的Object类
  • 文件秒传
  • HashMap、HashTable
  • Redis集群

2. 雇员信息管理案例

需求:有一个公司,当有新的员工来报道时,要求将该员工的信息加入信息管理系统中,并且当输入该员工的id时,要求查找该员工的所有信息。要求:不使用数据库,尽量节省内存,速度越快越好。

// 员工节点
type Emp struct {
    
    
	Id   int
	Name string
	Next *Emp
}

// 展示员工信息的方法
func (e *Emp) ShowMe() {
    
    
	fmt.Printf("链表%d 找到了雇员%d\n", e.Id%7, e.Id)
}

// 定义员工链表,这里的员工链表不带表头,即第一个节点就存放雇员
type EmpLink struct {
    
    
	Head *Emp
}

// 添加员工,添加时从小到大
func (el *EmpLink) Insert(emp *Emp) {
    
    
	// 循环找到合适的位置,进行添加
	temp := el.Head
	if temp == nil {
    
    
		el.Head = emp
		return
	}
	for {
    
    
		if temp.Next == nil {
    
    
			break
		}
		if temp.Next.Id > emp.Id {
    
    
			break
		}
		temp = temp.Next
	}
	emp.Next = temp.Next
	temp.Next = emp

}

// 通过员工ID查找员工
func (el *EmpLink) FindById(id int) (emp *Emp) {
    
    
	temp := el.Head
	flag := false
	if temp == nil {
    
    
		fmt.Println("the empLink is empty")
		return
	}
	for {
    
    
		// 找到了员工
		if temp.Id == id {
    
    
			flag = true
			break
		}
		// 没找到员工
		if temp.Next == nil {
    
    
			break
		}
		temp = temp.Next
	}

	// 找到员工就返回
	if flag {
    
    
		return temp
	} else {
    
    
		fmt.Println("can`t find the employee")
	}
	return
}

// 显示链表的信息
func (el *EmpLink) ShowLink(no int) {
    
    
	if el.Head == nil {
    
    
		fmt.Printf("链表%d为空\n", no)
		return
	}

	// 遍历当前的链表,并显示数据
	temp := el.Head
	for {
    
    
		if temp != nil {
    
    
			fmt.Printf("链表%d, 雇员id: %d, 名字: %s", no, temp.Id, temp.Name)
			temp = temp.Next
		} else {
    
    
			break
		}

	}
	// 换行处理
	fmt.Println()
}

// 定义hashtable,含有链表数组,数组每个位置存放一条链上的员工信息
type HashTbale struct {
    
    
	LinkArr [7]EmpLink
}

// 添加员工的方法
func (ht *HashTbale) Insert(emp *Emp) {
    
    
	// 使用散列函数,确定将该员工添加到哪个链表
	linkNo := ht.HashFunc(emp.Id)
	// 使用对应的链表添加
	ht.LinkArr[linkNo].Insert(emp)
}

// 显示hashTable的所有员工
func (ht *HashTbale) ShowAll() {
    
    
	for i := 0; i < len(ht.LinkArr); i++ {
    
    
		ht.LinkArr[i].ShowLink(i)
	}
}

// 通过员工ID查询
func (ht *HashTbale) FindById(id int) *Emp {
    
    
	// 确定该雇员所在的链表
	linkNo := ht.HashFunc(id)
	return ht.LinkArr[linkNo].FindById(id)

}

// 编写一个散列方法
func (ht *HashTbale) HashFunc(id int) int {
    
    
	// 得到的值就是对应链表的下标
	return id % 7
}


//---------------测试---------------
func main() {
    
    

	key := ""
	id := 0
	name := ""

	var hashTable HashTbale

	for {
    
    
		fmt.Println("===========================")
		fmt.Println("1. 添加员工")
		fmt.Println("2. 显示员工")
		fmt.Println("3. 查找员工")
		fmt.Println("4. 退出系统")
		fmt.Println("请输入你的选择")
		fmt.Scanln(&key)
		switch key {
    
    
		case "1":
			fmt.Println("请输入员工id: ")
			fmt.Scanln(&id)
			fmt.Println("请输入员工名字: ")
			fmt.Scanln(&name)
			emp := &Emp{
    
    
				Id:   id,
				Name: name,
			}

			hashTable.Insert(emp)
		case "2":
			hashTable.ShowAll()
		case "3":
			fmt.Println("请输入要查找的ID号:")
			fmt.Scanln(&id)
			emp := hashTable.FindById(id)
			if emp != nil {
    
    
				emp.ShowMe()
			}

		case "4":
			os.Exit(0)
		}
	}
}

代码来自尚硅谷的Go语言教程,但是我修改了一些代码,测试的时候也没有发现问题,但是不知道是否完全正确。如果哪位大佬发现了错误,欢迎指出。

完整代码:https://github.com/bigzoro/go_algorithm/tree/main/hash

猜你喜欢

转载自blog.csdn.net/qq_31639829/article/details/120899986
今日推荐