哈希表(散列表)——实现简易雇员系统

332 阅读4分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

1、基本介绍

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。

也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

上图来自网络(侵删)

👇接下来,让我们来一个创意实践,来帮助理解散列表的特点以及用法

2、需求描述

有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址.),当输入该员工的 id 时,要求查到该员工的所有信息。以此需求设计一个简易雇员系统

要求

  1. 不使用数据库,尽量节省内存,速度越快越好(散列到不同的链表里去)=>哈希表(散列)

  2. 添加时,保证按照 id 从低到高插入

  3. 👉系统操作方式:通过终端键盘输入指令控制增删改查操作,以及退出操作

要求不使用数据库,节省内存且速度够快,这就需要一丝创意了,具体思路请看下文分析👇

3、思路分析

  1. 使用链表来实现哈希表,该链表不带表头(即:链表的第一个结点就存放雇员信息)

  2. 定义雇员、雇员链和hash table结构体

  3. 给结构体实现增删改查等方法

  4. 终端指令接收处理

注意:使用链表实现哈希表的过程中,要注意雇员信息插入到雇员链中的位置,应该是有序的(这里即是按照id升序)

3.1、拆解示意图

image.png

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、效果展示

image.png

6、创意总结

抛开数据库,单纯运用哈希+链表的形式,实现一个雇员系统的CRUD操作。

善于灵活运用数据结构

一方面数据通过数据结构来直观的区分,另一方面同样的数据在前端的表现上也可以是多种多样的。这样从数据和表现形式两个维度就可以组合出很多的视觉层的效果,让人非常期待🎉🎉


🎈🎈🎈

🎊 关注我,你会发现一个踏实努力的宝藏前端,持续更文,让我们一起学习,共同成长😊

🎉 喜欢的小伙伴请点赞关注收藏

✨ 欢迎大家转发评论

🧨 蟹蟹😊

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!