我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
1、基本介绍
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。
也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
上图来自网络(侵删)
👇接下来,让我们来一个创意实践,来帮助理解散列表的特点以及用法
2、需求描述
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址.),当输入该员工的 id 时,要求查到该员工的所有信息。以此需求设计一个简易雇员系统。
要求:
-
不使用数据库,尽量节省内存,速度越快越好(散列到不同的链表里去)=>哈希表(散列)
-
添加时,保证按照 id 从低到高插入
-
👉系统操作方式:通过终端键盘输入指令控制增删改查操作,以及退出操作
要求不使用数据库,节省内存且速度够快,这就需要一丝创意了,具体思路请看下文分析👇
3、思路分析
-
使用链表来实现哈希表,该链表不带表头(即:链表的第一个结点就存放雇员信息)
-
定义雇员、雇员链和hash table结构体
-
给结构体实现增删改查等方法
-
终端指令接收处理
注意:使用链表实现哈希表的过程中,要注意雇员信息插入到雇员链中的位置,应该是有序的(这里即是按照id升序)
3.1、拆解示意图
4、代码实现
// 哈希表
/*
有一个公司,当有新的员工来报道时,
要求将该员工的信息加入(id,性别,年龄,住址.),
当输入该员工的 id 时,要求查到该员工的所有信息。
*/
package main
import (
"errors"
"fmt"
"os"
)
// 定义雇员
type Emp struct {
Id int
Name string
Next *Emp
}
// 显示 emp 信息
func (emp *Emp) ShowEmpInfo() {
fmt.Printf("链表: %v 内找到雇员,id=%v,name=%v \n", emp.Id%7, emp.Id, emp.Name)
}
// 定义雇员链, 不带表头
type EmpLink struct {
Head *Emp
}
// 添加员工的方法, id从小到大
func (el *EmpLink) Insert(emp *Emp) {
// 定义辅助指针
cur := el.Head
var pre *Emp = nil
// 当前是空链表
if cur == nil {
el.Head = emp
return
}
// 链首: 插入的元素要放到链表首
if cur.Id > emp.Id {
emp.Next = cur
el.Head = emp
return
}
// 中间+最后:不是空链,找到适当的位置插入, 要么 cur 前, 要么后
for {
if cur != nil {
if cur.Id >= emp.Id {
break
}
} else {
// 找到最后, cur == nil
break
}
pre = cur
cur = cur.Next
}
emp.Next = cur
pre.Next = emp
/* if cur != nil {
// 利用 pre 将 emp 插入到 cur 前面
emp.Next = cur
pre.Next = emp
} else {
emp.Next = cur
pre.Next = emp // 这个写不写无所谓, cur就是nil
} */
}
// 根据 id 查找 emp
func (el *EmpLink) FindById(id int) (emp *Emp) {
cur := el.Head
for {
if cur != nil && cur.Id == id {
return cur
} else if cur == nil {
return nil
}
cur = cur.Next
}
}
// 根据 id 删除 emp
func (el *EmpLink) DelById(id int) (err error) {
// emp := el.FindById(id)
// if emp == nil {
// fmt.Printf("要删除的雇员id: %v 不存在\n", id)
// return errors.New("id不存在")
// }
cur := el.Head
var pre *Emp = nil
fmt.Println("删除", id, cur)
// 空链表
if cur == nil {
fmt.Println("空链表")
return errors.New("空链表")
}
// 将删除的就是头节点
if cur.Id == id {
el.Head = nil
return
}
// 要删除的不是头节点
for {
if cur != nil && cur.Id == id {
pre.Next = cur.Next
return
} else if cur == nil {
fmt.Printf("要删除的雇员id: %v 不存在\n", id)
return errors.New("id不存在")
}
pre = cur
cur = cur.Next
}
}
// 定义 hash table, 链表数组
type HashTable struct {
LinkArr [7]EmpLink
}
// 编写 哈希表 添加方法
func (ht *HashTable) Insert(emp *Emp) {
// 1. 使用散列函数,确定添加到哪个链表里
linkNo := ht.HashFun(emp.Id)
// 2. 添加到对应的链表
ht.LinkArr[linkNo].Insert(emp)
}
// 编写 哈希表 散列函数 (映射函数)
// 这里只展示按 id 散列, 没有二级链表(二级链表需要取模两次)
func (ht *HashTable) HashFun(id int) int {
return id % 7
}
// 显示当前链表的 emp
func (el *EmpLink) ShowEmp(no int) {
if el.Head == nil {
fmt.Printf("链表: %v 为空\n", no)
return
}
cur := el.Head
fmt.Println("链表: ", no)
for {
if cur != nil {
fmt.Printf("emp[id]=%v, emp[name]=%v => ", cur.Id, cur.Name)
} else {
break
}
cur = cur.Next
}
fmt.Println()
}
// 显示所有 emp
func (ht *HashTable) ShowAll() {
for i := 0; i < len(ht.LinkArr); i++ {
ht.LinkArr[i].ShowEmp(i)
}
}
// 编写查找 emp 方法
func (ht *HashTable) FindEmpById(id int) *Emp {
// 1. 使用散列函数,确定在哪个链表里
linkNo := ht.HashFun(id)
return ht.LinkArr[linkNo].FindById(id)
}
// 编写删除 emp 方法
func (ht *HashTable) DelEmpById(id int) error {
// 1. 使用散列函数,确定在哪个链表里
linkNo := ht.HashFun(id)
return ht.LinkArr[linkNo].DelById(id)
}
func main() {
var key int
var id int
var name string
hashTable := &HashTable{}
for {
fmt.Println("*********** 雇员系统菜单 ***********")
fmt.Println("*********** 1. 添加雇员 ***********")
fmt.Println("*********** 2. 显示雇员 ***********")
fmt.Println("*********** 3. 查找雇员 ***********")
fmt.Println("*********** 4. 删除雇员 ***********")
fmt.Println("*********** 5. 退出 ***********")
fmt.Println("请输入(1-5):")
fmt.Scanln(&key)
switch key {
case 1:
fmt.Println("请输入雇员id")
fmt.Scanln(&id)
fmt.Println("请输入雇员name")
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.FindEmpById(id)
if emp == nil {
fmt.Printf("没有找到id:%v \n", id)
} else {
fmt.Println("找到了: ")
emp.ShowEmpInfo()
}
case 4:
fmt.Println("请输入要删除的雇员id")
fmt.Scanln(&id)
err := hashTable.DelEmpById(id)
if err != nil {
fmt.Printf("id: %v 删除失败, %v\n", id, err)
} else {
fmt.Printf("id: %v 已删除\n", id)
}
case 5:
os.Exit(0)
default:
fmt.Println("输入有误,请重新输入")
}
}
}
// 散列效果:
/*
链表: 0
emp[id]=14, emp[name]=wwwww =>
链表: 1
emp[id]=1, emp[name]=qqq => emp[id]=8, emp[name]=cccccc => emp[id]=15, emp[name]=ddd =>
链表: 2 为空
链表: 3 为空
链表: 4 为空
链表: 5
emp[id]=12, emp[name]=fffff =>
链表: 6 为空
*********** 雇员系统菜单 ***********
*********** 1. 添加雇员 ***********
*********** 2. 显示雇员 ***********
*********** 3. 找到雇员 ***********
*********** 4. 退出 ***********
请输入(1-4):
*/
5、效果展示
6、创意总结
抛开数据库,单纯运用哈希+链表的形式,实现一个雇员系统的CRUD操作。
要善于灵活运用数据结构。
一方面数据通过数据结构来直观的区分,另一方面同样的数据在前端的表现上也可以是多种多样的。这样从数据和表现形式两个维度就可以组合出很多的视觉层的效果,让人非常期待🎉🎉
🎈🎈🎈
🎊 关注我,你会发现一个踏实努力的宝藏前端,持续更文,让我们一起学习,共同成长😊
🎉 喜欢的小伙伴请点赞关注收藏哟
✨ 欢迎大家转发评论
🧨 蟹蟹😊
我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!