前言
这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天,其实第五届青训营已经正式开课n多天了,笔记不断更新中,都是我听完课之后的总结和平时自己的学习积累,分享出来给有需要的朋友。
本文内容
本文将涉及到数据结构稀疏数组、循环队列和链表增删改查的基本使用。
Go语言基础
1.数据结构
(1)稀疏数组
问一个问题,如果现在要让你通过编码的方式,让你将这盘棋局保存起来,你会怎么做呢?
面对行列数据的保存,我相信大多人第一时间都会想到用二维数组进行保存。但最其并不是最好的方案,因为如果棋盘上就几个黑子和白子,大部分都是空的,我们使用二维数组把这样没有价值的数据起来,不但会浪费存储空间的大小,如果写入到磁盘中,还会增大 IO 的读写量,影响性能,这就是用普通二维数组来表示棋盘数据的不足之处。
这时候稀疏数组就出场了,以下是完整代码
type ValNode struct {
row int
col int
val int
}
func main() {
//1.先创建一个原始数组
var chessMap [11][11]int
chessMap[1][2] = 1 //黑子
chessMap[2][3] = 2 //蓝子
//2.输出看看原始数组
for _, v := range chessMap {
for _, v2 := range v {
fmt.Printf("%d\t", v2)
}
fmt.Println()
}
//3.转成系数数组
// 思路
// (1) 遍历chessMap,如果我们发现有一个元素不为0,创建一个node结构体
// (2) 将其放入到其对应的切片即可
var sparseArr []ValNode
valNode := ValNode{
row: 11,
col: 11,
val: 0,
}
sparseArr = append(sparseArr, valNode)
for i, v := range chessMap {
for j, v2 := range v {
if v2 != 0 {
//创建一个ValNode值结点
valNode := ValNode{
row: i,
col: j,
val: v2,
}
sparseArr = append(sparseArr, valNode)
}
}
}
//输出稀疏数组
fmt.Println("======当前的稀疏数组是=====")
for i, v := range sparseArr {
fmt.Printf("%d %d %d %d\n", i, v.row, v.col, v.val)
}
//1.先创建一个原始数组
var chessMap2 [11][11]int
for i, v := range sparseArr {
if i != 0 {
chessMap2[v.row][v.col] = v.val
}
}
/*
输出结果:(第一列可以省略)
======当前的稀疏数组是=====
0 11 11 0
1 1 2 1
2 2 3 2
第一行 11 11 0记录着行数、列数、和初始值
第二行开始 记录具体值 1 2 1 代表第一行第2列值是1
*/
//2.输出看看还原后的数组
fmt.Println("======还原后的数组=====")
for _, v := range chessMap2 {
for _, v2 := range v {
fmt.Printf("%d\t", v2)
}
fmt.Println()
}
}
(2)数组模拟队列
完整案例
// 使用一个结构体管理队列
type Queue struct {
maxsize int
array [5]int //数组->模拟队列
front int //表示指向队列首
rear int //表示指向队列的尾部
}
// 添加数据到队列
func (this *Queue) AddQueue(val int) (err error) {
//先判断队列是否已满
if this.rear == this.maxsize-1 { //rear是队尾(含最后元素)
return errors.New("queue full")
}
this.rear++ //往后移
this.array[this.rear] = val
return
}
// 从队列取出数据
func (this *Queue) GetQueue() (int, error) {
//先判断队列是否为空
if this.rear == this.front { //front不含对首元素
return -1, errors.New("queue null") //对空
}
this.front++
val := this.array[this.front] //从队首出,队尾进
return val, nil
}
// 显示队列数据 从对首遍历到队尾
func (this *Queue) ShowQueue() {
for i := this.front + 1; i <= this.rear; i++ {
fmt.Printf("array[%d]=%d\t", i, this.array[i])
}
fmt.Println()
}
func main() {
queue := &Queue{
maxsize: 5,
front: -1,
rear: -1,
}
var key string
var val int
for {
fmt.Println("1.输入add 表示添加数据到队列")
fmt.Println("2.输入get 表示从队列获取数据")
fmt.Println("3.输入show 表示显示队列")
fmt.Println("4.输入exit 表示退出队列")
fmt.Print("输入你的选项:")
fmt.Scanln(&key)
switch key {
case "add":
fmt.Print("输出你要入队列的数:")
fmt.Scanln(&val)
err := queue.AddQueue(val)
if err == nil {
fmt.Println("success,加入队列成功!")
} else {
fmt.Println(err.Error())
}
case "get":
val, err := queue.GetQueue()
if err != nil {
fmt.Println("队列为空,去除失败")
} else {
fmt.Println("取出了一个数", val)
}
case "show":
queue.ShowQueue()
case "exit":
os.Exit(0)
}
}
}
(3)循环队列相关用法
因为顺序队列的假溢出的缺点,我们可以使用循环队列(逻辑上的环状空间)
原理:当队首指针Q.front=MaxSize-1后,再前进一个位置就自动到0,
可以通过除法取余运算(%)来实现
提醒(real front等价于tail head)
(1)尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定
(2)判断队列满:(real+1)%maxsize=front
(3)判断队列空:real=front
(4)初始化队列:real=0,front=0
(5)统计队列元素个数:(real-front+maxsize) % maxsize
完整代码:
package main
import (
"errors"
"fmt"
"os"
)
// 使用一个结构体管理循环队列
type CircleQueue struct {
maxSize int //5
array [5]int //数组
head int //指向队列队首
tail int //指向队尾
}
// 入队列AddQueue(push) GetQueue(pop)
func (q *CircleQueue) Push(val int) (err error) {
if q.IsFull() {
return errors.New("queue full")
}
//分析出q.tail 在队列尾部,但是包含最后的元素
q.array[q.tail] = val
q.tail = (q.tail + 1) % q.maxSize
return
}
// 出队列
func (q *CircleQueue) Pop() (val int, err error) {
if q.IsEmpty() {
return 0, errors.New("queue empty")
}
//取出,head是指向队首,并且包含队首元素
val = q.array[q.head]
q.head = (q.head + 1) % q.maxSize
return
}
// 判断环形队列为满
func (q *CircleQueue) IsFull() bool {
return (q.tail+1)%q.maxSize == q.head
}
// 判断环形队列为空
func (q *CircleQueue) IsEmpty() bool {
return q.tail == q.head
}
// 去除环形队列有多少个元素
func (q *CircleQueue) Size() int {
//这是一个关键的算法
return (q.tail + q.maxSize - q.head) % q.maxSize
}
// 显示队列
func (q *CircleQueue) ListQueue() {
fmt.Println("环形队列情况如下:")
//取出当前队列中有多少个元素
size := q.Size()
if size == 0 {
fmt.Println("队列为空")
}
//设计一个辅助的变量,指向head
tempHead := q.head
//因为
for i := 0; i < size; i++ {
fmt.Printf("arr[%d]=%d\t", tempHead, q.array[tempHead])
tempHead = (tempHead + 1) % q.maxSize
}
fmt.Println()
}
func main() {
//初始化环形队列
queue := &CircleQueue{
maxSize: 5,
head: 0,
tail: 0,
}
var key string
var val int
for {
fmt.Println("1.输入add 表示添加数据到队列")
fmt.Println("2.输入get 表示从队列获取数据")
fmt.Println("3.输入show 表示显示队列")
fmt.Println("4.输入exit 表示退出队列")
fmt.Print("输入你的选项:")
fmt.Scanln(&key)
switch key {
case "add":
fmt.Print("输出你要入队列的数:")
fmt.Scanln(&val)
err := queue.Push(val)
if err == nil {
fmt.Println("success,加入队列成功!")
} else {
fmt.Println(err.Error())
}
case "get":
val, err := queue.Pop()
if err != nil {
fmt.Println("队列为空,去除失败")
} else {
fmt.Println("取出了一个数", val)
}
case "show":
queue.ListQueue()
case "exit":
os.Exit(0)
}
}
}
(4)单向链表和双向链表操作
(1)插入节点
1.先定义一个HeroNode结构体
type HeroNode struct {
no int
name string
nickname string
// pre *HeroNode //双向链表的前结点
next *HeroNode //这个表示指向下一个结点
}
2.给链表插入一个结点[直接在尾部插入]
// 编写第一种插入方法,在单链表的最后加入
func InserHeroNode(head *HeroNode, newHeroNode *HeroNode) {
//思路
//1.先找到该链表的最后这个结点
//2.创建一个辅助结点
temp := head //辅助结点
for {
if temp.next == nil {
break
}
temp = temp.next //让temp不断指向下一个结点
}
//3.将newHeroNode插入到最后
temp.next = newHeroNode
}
3.给链表插入一个结点[根据id顺序插入]
思路 1.先找到该链表的最后这个结点 2.创建一个辅助结点
// 编写第二种插入方法,在单链表的最后加入
func InserHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
temp := head
flag := true
//让插入的结点的no序号和temp的下一个结点的no序号比较
for {
if temp.next == nil { //说明到链表的最后
break
} else if temp.next.no > newHeroNode.no {
//说明newHeroNode就应该插入到temp后面
break
} else if temp.next.no == newHeroNode.no {
//说明我们链表中已经有这个no序号了,不插入
flag = false
break
}
temp = temp.next //让temp不断指向下一个结点
}
if !flag {
fmt.Println("对不起,已经存在该序号", newHeroNode.no)
return
} else {
newHeroNode.next = temp.next
temp.next = newHeroNode
}
}
(2)修改节点
思路
1.先找到要删除的这个结点
2.创建一个辅助结点
func AlterHeroNode(head *HeroNode, newHeroNode *HeroNode) {
temp := head
flag := false
for {
if temp.next == nil {
return
} else if newHeroNode.no == temp.next.no {
flag = true //找到要删除的结点赋值true
break
}
temp = temp.next //让temp不断指向下一个结点
}
if flag {
newHeroNode.next = temp.next.next //让新结点指向被修改节点的下一个结点
temp.next = newHeroNode //让被修改的结点的前一个结点指向新结点
}
}
(3)删除结点
思路 1.先找到要删除的这个结点 2.创建一个辅助结点
// 删除节点
func DeleteHeroNode(head *HeroNode, id int) {
temp := head
flag := false
for {
if temp.next == nil {
return
} else if id == temp.next.no {
flag = true //找到要删除的结点赋值true
break
}
temp = temp.next //让temp不断指向下一个结点
}
if flag {
temp.next = temp.next.next //让temp的下一个结点指向删除节点的下一个结点
}
}
(4)显示链表的所有信息
func ListHeroNode(head *HeroNode) {
//1.创建一个辅助结点
temp := head
//2.判断是否为空链表
if temp.next == nil {
fmt.Println("链表是空的")
return
}
//3.遍历链表
for {
fmt.Printf("[%d,%v,%v]==>", temp.next.no, temp.next.name, temp.next.nickname)
temp = temp.next
if temp.next == nil {
break
}
}
}
(5)完整代码
package main
import (
"fmt"
)
/*
《数据结构-单向链表和双向链表操作》
*/
type HeroNode struct {
no int
name string
nickname string
// pre *HeroNode //双向链表的前结点
next *HeroNode //这个表示指向下一个结点
}
// 编写第一种插入方法,在单链表的最后加入
func InserHeroNode(head *HeroNode, newHeroNode *HeroNode) {
//思路
//1.先找到该链表的最后这个结点
//2.创建一个辅助结点
temp := head //辅助结点
for {
if temp.next == nil {
break
}
temp = temp.next //让temp不断指向下一个结点
}
//3.将newHeroNode插入到最后
temp.next = newHeroNode
}
// 编写第二种插入方法,在单链表的最后加入
func InserHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
temp := head
flag := true
//让插入的结点的no序号和temp的下一个结点的no序号比较
for {
if temp.next == nil { //说明到链表的最后
break
} else if temp.next.no > newHeroNode.no {
//说明newHeroNode就应该插入到temp后面
break
} else if temp.next.no == newHeroNode.no {
//说明我们链表中已经有这个no序号了,不插入
flag = false
break
}
temp = temp.next //让temp不断指向下一个结点
}
if !flag {
fmt.Println("对不起,已经存在该序号", newHeroNode.no)
return
} else {
newHeroNode.next = temp.next
temp.next = newHeroNode
}
}
// 修改节点
func AlterHeroNode(head *HeroNode, newHeroNode *HeroNode) {
temp := head
flag := false
for {
if temp.next == nil {
return
} else if newHeroNode.no == temp.next.no {
flag = true //找到要删除的结点赋值true
break
}
temp = temp.next //让temp不断指向下一个结点
}
if flag {
newHeroNode.next = temp.next.next //让新结点指向被修改节点的下一个结点
temp.next = newHeroNode //让被修改的结点的前一个结点指向新结点
}
}
// 删除节点
func DeleteHeroNode(head *HeroNode, id int) {
temp := head
flag := false
for {
if temp.next == nil {
return
} else if id == temp.next.no {
flag = true //找到要删除的结点赋值true
break
}
temp = temp.next //让temp不断指向下一个结点
}
if flag {
temp.next = temp.next.next //让temp的下一个结点指向删除节点的下一个结点
}
}
// 显示链表的所有信息
func ListHeroNode(head *HeroNode) {
//1.创建一个辅助结点
temp := head
//2.判断是否为空链表
if temp.next == nil {
fmt.Println("链表是空的")
return
}
//3.遍历链表
for {
fmt.Printf("[%d,%v,%v]==>", temp.next.no, temp.next.name, temp.next.nickname)
temp = temp.next
if temp.next == nil {
break
}
}
}
func main() {
//1.先创建一个头结点
head := &HeroNode{}
//2.创建一个新的HeroNode
hero1 := &HeroNode{
no: 1,
name: "猪八戒",
nickname: "天蓬元帅",
}
hero2 := &HeroNode{
no: 2,
name: "孙悟空",
nickname: "齐天大圣",
}
hero3 := &HeroNode{
no: 3,
name: "小玉",
nickname: "玉面狐狸",
}
hero4 := &HeroNode{
no: 2,
name: "唐僧",
nickname: "金蝉子",
}
fmt.Printf("原来的链表:")
ListHeroNode(head)
//3.加入
//[InserHeroNode 只在尾部插入 按插入顺序显示]
//[InserHeroNode2 从小到大排序]
InserHeroNode2(head, hero1)
InserHeroNode2(head, hero3)
InserHeroNode2(head, hero2)
//4.显示
ListHeroNode(head)
fmt.Printf("\n\n修改节点后的链表:\n")
//修改节点
AlterHeroNode(head, hero4) //修改第2个节点
ListHeroNode(head)
//删除节点
fmt.Printf("\n\n删除节点后的链表:\n")
DeleteHeroNode(head, 2) //删除第二个hero
ListHeroNode(head)
//双向链表中,插入时先创建新结点的联系,再修改原结点直接的联系
/*
a->b->新结点(newHeroNode)->d
插入示例:
newHeroNode.next = temp.next
newHeroNode.pre = temp
temp.next.pre = newHeroNode
temp.next = newHeroNode
a->b->删除结点(deleteHeroNode)->d
删除示例:
temp.next=temp.next.next
if temp.next !=nil{
temp.next.pre=temp
}
*/
}
总结
本文主要为您介绍了数据结构的相关基础知识。
写在最后
本文是我的日常学习笔记,如果哪里有写错,麻烦请指出来,感谢。这里我也推荐大家多写笔记,写笔记是一个很好的习惯,可以帮助我们更好的吸收和理解学习的新知识,新的一年大家一起加油!