• [go学习笔记.第十八章.数据结构] 1.基本介绍,稀疏数组,队列(数组实现),链表


    一.基本介绍

    1.数据结构(算法)的介绍

    (1).数据结构是一门研究算法的学科,自从有了编程语言也就有了数据结构,学好数据结构可以编写出更加漂亮,更加有效率的代码

    (2).要学习好数据结构就要多多考虑如何将生活中遇到的问题用程序去实现解决

    (3).程序= 数据结构 + 算法

    2.数据结构和算法的关系

    • 算法是程序的灵魂,为什么有些网站能够在高并发,在海量吞吐情况下依然坚如磐石,大家可能会说:网站使用了服务器群集技术、数据库读写分离和缓存技术(比如 redis等),那么如果再深入的问一句,这些优化技术又是怎样被那些天才的技术高手设计出来的呢?
    • 大家请思考一个问题,是什么让不同的人写出的代码从功能看是一样的,但从效率上却有天壤之别,比如:我是做服务器的,环境是UNIX ,功能是要支持上千万人同时在线,并保证数据传输的稳定,在服务器上线前,做内侧,一切 OK.可上线后,服务器就支撑不住了,公司的CTO对我的代码进行优化,再次上线,坚如磐石,那一瞬间,我认识到程序是有灵魂的,就是算法.如果你不想永远都是代码工人,那就花时间来研究下算法

    3.案例

    (1).试写出用单链表表示的字符串类及字符串结点类的定义,并依次实现它的构造函数、以及计算串长度、串赋值、判断两串相等、求子串、两串连接、求子串在串中位置等7个成员函数

    代码如下:

    1. func main() {
    2. var str string = "go, golang, hello world"
    3. str = strings.Replace(str, "go", "c"-1)
    4. fmt.Println(str)
    5. }

    (2).五子棋:如何判断游戏的输赢,并可以完成存盘退出和继续上局的功能

    (3).约瑟夫问题(丢手帕问题)

            设编号为 1 , 2 , … n 的 n 个人围坐一圈,约定编号为 k ( 1 <= k  <= n)的人从 1 开始报数,数到m的那个人出列,它的下一位又从 1 开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列.

    提示:

            用一个不带头结点的循环链表来处理josephu问题,先构成一个有 n 个结点的单循环链表,然后由 k 结点起从 1 开始计数,计到m时,对应结点从链表中删除.然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除,算法结束.

    (4).邮差问题

            战争时期,胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在有六个邮差,从G点出发,需要分别把邮件分别送到 A, B, C , D, E, F 六个村庄

            各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里

    问:

            如何计算出G村庄到 其它各个村庄的最短距离?如果从其它点出发到各个点的最短距离又是多少?

    (5).最短路径问题

            首先是迷宫的表示。如下图,起始位置是左上角黄色位置,重点位置是右下角黄色位置。在这个二维矩阵里,0表示道路通畅,可走;1表示有障碍物,不可走。最终计算出来的结果如右下角所示,从左上角0开始,每走一步,累加一次,这样就可以显示出整条路径的先后顺序。要走最短路径,只要从终点位置,不断递减1寻找上一步的位置直到回到起始位置即可.

    (6).汉诺塔

    假设这个游戏中有 3 个柱子,即 A、B 和 C,需要移动的是方块,其中一个柱子上已经有 N 个有序的方块,最大的在底部,方块按顺序越来越小。另外 2 个是空柱子。

    基本条件:

    • 一次只能移动一颗方块
    • 小方块一定要在大方块上面
    • 最终目标是将柱子上的所有方块移到另一根柱子上

    (7).八皇后问题

    1. 首先定义一个8*8的棋盘
    2. 我们有八个皇后在手里,目的是把八个都放在棋盘中
    3. 位于皇后的水平和垂直方向的棋格不能有其他皇后
    4. 位于皇后的斜对角线上的棋格不能有其他皇后
    5. 解出能将八个皇后都放在棋盘中的摆法

    二.稀疏数组

    1.需求

    编写五子棋程序,存盘退出和续上盘功能

    分析:

           上图中,二维数组中很多数都是默认值(0),因此纪录了很多没有意义的数据,故使用稀疏数组来进行处理 

    2.基本介绍

    当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组

    稀疏数组的处理方法是:

    (1).记录数组一共有几行几列,有多少个不同的值

    (2).把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

     

     3.应用实例

    (1).使用稀疏数组,来保留类似前面的二维数组棋盘、地图等等)

    (2).把稀疏数组存盘,并且可以从新恢复原来的二维数组数
    (3).整休思路分析

    (4).代码实现

      

    代码如下:

    1. package main
    2. import(
    3. "fmt"
    4. "os"
    5. "bufio"
    6. "io"
    7. "strings"
    8. "strconv"
    9. )
    10. type ValNode struct {
    11. row int
    12. col int
    13. val int
    14. }
    15. //编写五子棋程序, 有存盘退出和续上盘的功能
    16. func main() {
    17. //1.先创建一个原始数组
    18. var chessMap [11][11]int
    19. chessMap[1][2] = 1 //黑子
    20. chessMap[2][3] = 2 //蓝子
    21. //2.输出原始数组看看
    22. for _, v := range chessMap {
    23. for _, v2 := range v {
    24. fmt.Printf("%d\t", v2)
    25. }
    26. fmt.Println()
    27. }
    28. //3.转换成稀疏数组
    29. //思路:
    30. //1).遍历chessMap,如果发现有一个元素不为零,创建一个node结构体
    31. //2).将其放入对应的切片即可
    32. //定义切片
    33. var spaseArr []ValNode
    34. //标准的一个稀疏数组应该还有一个记录元素的二维数组的规模(行和列,默认值)
    35. //创建一个ValNode节点
    36. valNode := ValNode {
    37. row: 11,
    38. col: 11,
    39. val: 0,
    40. }
    41. //追加到spaseArr切片中
    42. spaseArr = append(spaseArr, valNode)
    43. //循环chessMap,把不为零的元素放到spaseArr中
    44. for i, v := range chessMap {
    45. for j, v2 := range v {
    46. if v2 != 0 {
    47. //创建一个ValNode 值节点
    48. valNode = ValNode{
    49. row: i,
    50. col: j,
    51. val: v2,
    52. }
    53. spaseArr = append(spaseArr, valNode)
    54. }
    55. }
    56. }
    57. //输出稀疏数组
    58. fmt.Println("当前的稀疏数组为:")
    59. for i, valNode := range spaseArr {
    60. fmt.Printf("%d: %d %d %d\n", i, valNode.row, valNode.col, valNode.val)
    61. }
    62. //将稀疏数组存盘
    63. //创建一个新文件
    64. //打开一个文件 "f:/www/test2.txt"
    65. filePath := "f:/www/chessMap.data"
    66. file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)
    67. if err != nil {
    68. fmt.Printf("open file err =%v\n", err)
    69. }
    70. //及时关闭file
    71. defer file.Close()
    72. var content string
    73. //准备写入
    74. writer := bufio.NewWriter(file)
    75. for _, valNode := range spaseArr {
    76. content = fmt.Sprintf("%d %d %d\n", valNode.row, valNode.col, valNode.val)
    77. writer.WriteString(content)
    78. }
    79. writer.Flush()
    80. //把存盘的稀疏数组恢复到原始数组
    81. //创建一个原始数组
    82. var chessMap2 [11][11]int
    83. //打开文件,遍历每一行数据
    84. file, err = os.Open(filePath)
    85. if err != nil {
    86. fmt.Println("os.Open file fail, err = ", err)
    87. }
    88. defer file.Close()
    89. //创建一个Reader
    90. reader := bufio.NewReader(file)
    91. //循环读取reader里的内容
    92. //文件中第一行数据过滤掉
    93. i := 1
    94. for {
    95. str, err := reader.ReadString('\n')
    96. if err == io.EOF { //读到文件末尾就退出
    97. break
    98. }
    99. if i == 1 {
    100. i++
    101. continue
    102. }
    103. //通过空格切割字符串,并转换成数组
    104. arr := strings.Fields(string(str))
    105. row, err := strconv.ParseInt(arr[0], 10, 64)
    106. col, err := strconv.ParseInt(arr[1], 10, 64)
    107. val, err := strconv.ParseInt(arr[2], 10, 64)
    108. chessMap2[row][col] = int(val)
    109. }
    110. //打印稀疏数组
    111. for _, v := range chessMap2 {
    112. for _, v2 := range v {
    113. fmt.Printf("%d\t", v2)
    114. }
    115. fmt.Println()
    116. }
    117. }

    三.队列(数组实现)

    1.队列的应用场景

    银行排队案例:

    2.队列介绍

    队列是一个有序列表,可以用数组或是链表来实现.遵循先入先出的原则。即:先存入队列的数据,要先取出,后存入的要后取出示意图,使用数组模拟队列示意图如下:

    3.数组模拟队列

    队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下:其中 maxSize 是该队列的最大容量

    因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标, front 会随着数据输出而改变,而rear则是随着数据输入而改变,如图所示:

    将数据存入队列时称为"addqueue", addqueue 的处理需要有两个步骤

    (1).将尾指针往后移: rear + 1 , front ==real [空]

    (2).若尾指引 rear 小于等于队列的最大下标 MaxSize -1, 则将数据存入 rear 所指的数组元素,否则无法存入数据

    1. package main
    2. import (
    3. "fmt"
    4. "errors"
    5. "os"
    6. )
    7. //定义一个结构体,存放队列相关数据
    8. type Queue struct {
    9. maxSize int //队列最大数
    10. array [5]int // 数组=>模拟队列
    11. front int //指向队列首部
    12. rear int //指向队列尾部
    13. }
    14. //添加队列数据
    15. func (this *Queue) AddQueue(val int) (err error) {
    16. //判断队列是否已满
    17. if this.maxSize -1 == this.rear { // rear是队列尾部(含最后一个元素)
    18. return errors.New("queue full")
    19. }
    20. this.rear++ //rear后移
    21. this.array[this.rear] = val
    22. return
    23. }
    24. //显示队列:找到队首,然后遍历
    25. func (this *Queue) ShowQueue () {
    26. fmt.Println("队列当前情况:")
    27. //this.front是不包含队首的元素
    28. for i := this.front + 1; i <= this.rear; i++ {
    29. fmt.Printf("arrar[%d]=%d\n", i, this.array[i])
    30. }
    31. }
    32. //从队列中取出数据
    33. func (this *Queue) GetQueue() (val int, err error) {
    34. //先判断队列是否为空
    35. if this.rear == this.front { //队列为空了
    36. return -1, errors.New("queue empty")
    37. }
    38. this.front++
    39. val = this.array[this.front]
    40. return val, err
    41. }
    42. func main() {
    43. //先创建一个队列
    44. queue := &Queue {
    45. maxSize: 5,
    46. front: -1,
    47. rear: -1,
    48. }
    49. var key string
    50. var val int
    51. for {
    52. fmt.Println("1. 输入add,表示添加数据到队列")
    53. fmt.Println("2. 输入get,表示从队列获取数据")
    54. fmt.Println("3. 输入show,表示显示队列")
    55. fmt.Println("4. 输入exit,表示退出队列")
    56. fmt.Scanln(&key)
    57. switch key {
    58. case "add":
    59. fmt.Println("输入要入队的对数:")
    60. fmt.Scanln(&val)
    61. err := queue.AddQueue(val)
    62. if err != nil {
    63. fmt.Println(err.Error())
    64. } else {
    65. fmt.Println("加入队列成功")
    66. }
    67. case "get":
    68. val, err := queue.GetQueue()
    69. if err != nil {
    70. fmt.Println(err.Error())
    71. } else {
    72. fmt.Printf("从队列中取出的数为:%d\n", val)
    73. }
    74. case "show":
    75. queue.ShowQueue()
    76. case "exit":
    77. os.Exit(0)
    78. default:
    79. fmt.Println("输入有误,请重新输入")
    80. }
    81. }
    82. }

     对上面代码的小节和说明:

    (1).上面代码实现了基本队列结构,但是役有有效的利用数组空间

    (2).请思考:如何使用数组实现一个环形的队列

    4.环形数组队列 

    对前面的数组模拟队列的优化,充分利用数组,因此将数组看做是一个环形的( 通过取模的方式来实现即可)

    提醒:

    (1).尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意(tail+1)%maxSize == head(满)

    (2).tail == head (空)

    分析思路:

    (1).什么时候表示队列满? (tail+1)%maxSize == head

    (2).什么时候表示队列空? tail == head

    (3).初始化时,tail == 0,head == 0

    (4).怎么统计该队列有多少个元素? (tail + maxSize - head) % maxSize

    代码如下:

    1. package main
    2. import(
    3. "fmt"
    4. "errors"
    5. "os"
    6. )
    7. //定义一个结构体管理环形队列
    8. type CircleQueue struct{
    9. maxSize int //队列最大值:5
    10. array [5]int //使用数组 =>队列
    11. head int //指向队列队首:0
    12. tail int //指向队列队尾:0
    13. }
    14. //入队列 AddQueue(push)
    15. func (this *CircleQueue) Push(val int) (err error) {
    16. //判断环形队列是否满了
    17. if this.IsFull() {
    18. return errors.New("queue full")
    19. }
    20. //this.tail在队列尾部,但是不包含最后一个元素
    21. this.array[this.tail] = val //把值给尾部
    22. this.tail = (this.tail + 1) % this.maxSize
    23. return
    24. }
    25. //出队列 GetQueue(pop)
    26. func (this *CircleQueue) Pop() (val int, err error) {
    27. //判断环形队列是否为空
    28. if this.IsEmpty() {
    29. return 0, errors.New("queue empty")
    30. }
    31. //取出:head是指向队首,并且含队首元素
    32. val = this.array[this.head]
    33. this.head = (this.head + 1) % this.maxSize
    34. return val, err
    35. }
    36. //判断环形队列是否满了
    37. func (this *CircleQueue) IsFull() bool {
    38. return (this.tail + 1) % this.maxSize == this.head
    39. }
    40. //判断环形队列是否为空
    41. func (this *CircleQueue) IsEmpty() bool {
    42. return this.tail == this.head
    43. }
    44. //取出环形队列有多少个元素
    45. func (this *CircleQueue) Size() int {
    46. //这是个关键的点
    47. return (this.tail + this.maxSize - this.head) % this.maxSize
    48. }
    49. //显示队列
    50. func (this *CircleQueue) ListQueue() {
    51. fmt.Println("环形队列情况如下:")
    52. //取出当前队列有多少个元素
    53. size := this.Size()
    54. if size == 0 {
    55. fmt.Println("队列为空")
    56. }
    57. //定义一个临时变量,指向head
    58. tempHead := this.head
    59. for i := 0; i < size; i++ {
    60. fmt.Printf("array[%d] = %d\n", tempHead, this.array[tempHead])
    61. tempHead = (tempHead + 1) % this.maxSize
    62. }
    63. }
    64. func main() {
    65. //先创建一个队列
    66. queue := &CircleQueue {
    67. maxSize: 5,
    68. head: 0,
    69. tail: 0,
    70. }
    71. var key string
    72. var val int
    73. for {
    74. fmt.Println("1. 输入push,表示添加数据到队列")
    75. fmt.Println("2. 输入pop,表示从队列获取数据")
    76. fmt.Println("3. 输入list,表示显示队列")
    77. fmt.Println("4. 输入exit,表示退出队列")
    78. fmt.Scanln(&key)
    79. switch key {
    80. case "push":
    81. fmt.Println("输入要入队的对数:")
    82. fmt.Scanln(&val)
    83. err := queue.Push(val)
    84. if err != nil {
    85. fmt.Println(err.Error())
    86. } else {
    87. fmt.Println("加入队列成功")
    88. }
    89. case "pop":
    90. val, err := queue.Pop()
    91. if err != nil {
    92. fmt.Println(err.Error())
    93. } else {
    94. fmt.Printf("从队列中取出的数为:%d\n", val)
    95. }
    96. case "list":
    97. queue.ListQueue()
    98. case "exit":
    99. os.Exit(0)
    100. default:
    101. fmt.Println("输入有误,请重新输入")
    102. }
    103. }
    104. }

    四.链表

    1.链表的介绍

    链表是有序的列表,它在内存中的存储如下

    2.单链表的介绍

    示意图如下:

    说明: 一般来说,为了比较好的对单链表进行增删改查的操作,都会给其设置一个头结点,头结点的作用主要是用来标识链表头,本身这个结点不存放数据

    3.单链表的应用实例

    使用带 head 头的单向链表实现\:水浒英雄排行榜管理

    完成对英雄人物的增删改查操作

    在添加英雄时,直接添加到链表的尾部

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. //定义一个HeroNode结构体
    6. type HeroNode struct {
    7. no int
    8. name string
    9. nickname string
    10. next *HeroNode //表示指向下一个结点
    11. }
    12. //给链表插入一个结点
    13. //编写第一种插入方式:在单链表的最后插入
    14. func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    15. //1.先找到链表最后这个结点
    16. //2.创建一个辅助结点
    17. temp := head
    18. for {
    19. if temp.next == nil { //表示找到了最后
    20. break
    21. }
    22. temp = temp.next //让temp不断指向下一个结点
    23. }
    24. //当for结束时,temp一定是找到了最后一个节点的
    25. //3.将newHeroNode加入道最后
    26. temp.next = newHeroNode
    27. }
    28. //编写第二种插入方式: 根据no的编号从小到大插入
    29. func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    30. //1.先找适当的结点
    31. //2.创建一个辅助结点
    32. temp := head
    33. flag := true
    34. //让插入结点的no和temp的下一个结点的no比较
    35. for {
    36. if temp.next == nil { //表示到了最后的链表
    37. break
    38. } else if temp.next.no > newHeroNode.no {
    39. //说明newHeroNode就应该插入到temp后面
    40. break
    41. } else if temp.next.no == newHeroNode.no {
    42. //说明链表中已经有这个no,就不允许插入了
    43. flag = false
    44. break
    45. }
    46. temp = temp.next //让temp不断指向下一个结点
    47. }
    48. if !flag {
    49. fmt.Println("已存在no=", newHeroNode.no)
    50. return
    51. } else {
    52. newHeroNode.next = temp.next
    53. temp.next = newHeroNode
    54. }
    55. }
    56. //显示链表的所有结点信息
    57. func ListHeroNode(head *HeroNode) {
    58. //1.创建一个辅助结点
    59. temp := head
    60. //先判断该链表是不是空的链表
    61. if temp.next == nil {
    62. fmt.Println("空的链表")
    63. return
    64. }
    65. //2.遍历链表
    66. for {
    67. fmt.Printf("[%d, %s, %s] ==> ", temp.next.no, temp.next.name, temp.next.nickname)
    68. //判断链表是否最后
    69. temp = temp.next
    70. if temp.next == nil {
    71. break
    72. }
    73. }
    74. }
    75. //删除一个结点
    76. func DelHeroNode(head *HeroNode, id int) {
    77. temp := head
    78. flag := false
    79. //找到要删除的结点的no,和temp的下一个结点的no比较
    80. for {
    81. if temp.next == nil {//说明到了链表的最后
    82. break
    83. } else if temp.next.no == id {
    84. //说明找到了
    85. flag = true
    86. break
    87. }
    88. temp = temp.next
    89. }
    90. if flag {//找到了,就行删除操作
    91. temp.next = temp.next.next // 这样目标结点就会成为一个垃圾结点,被系统回收
    92. } else {
    93. fmt.Println("要删除的结点id不存在")
    94. }
    95. }
    96. func main() {
    97. //1.先创建一个头结点
    98. head := &HeroNode{}
    99. //2.创建一个新的结点
    100. head1 := &HeroNode{
    101. no: 1,
    102. name: "宋江",
    103. nickname: "及时雨",
    104. }
    105. head2 := &HeroNode{
    106. no: 2,
    107. name: "陆静怡",
    108. nickname: "玉骑铃",
    109. }
    110. head3 := &HeroNode{
    111. no: 3,
    112. name: "零充",
    113. nickname: "包子头",
    114. }
    115. //3.加入
    116. //第一种方法
    117. // InsertHeroNode(head, head1)
    118. // InsertHeroNode(head, head3)
    119. // InsertHeroNode(head, head2)
    120. //第二种方法
    121. InsertHeroNode2(head, head1)
    122. InsertHeroNode2(head, head3)
    123. InsertHeroNode2(head, head2)
    124. //4.列表
    125. ListHeroNode(head)
    126. //5.删除
    127. fmt.Println()
    128. DelHeroNode(head, 2)
    129. ListHeroNode(head)
    130. }

    4.双向链表的应用实例

    使用带 head 头的双向链表实现:水浒英雄排行榜管理

    单向链表的缺点分析:

    (1).单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找

    (2).单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面单链表删除时节点,总是找到 temp 的下一个节点来删除

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. //定义一个HeroNode结构体
    6. type HeroNode struct {
    7. no int
    8. name string
    9. nickname string
    10. pre *HeroNode //表示指向上一个结点
    11. next *HeroNode //表示指向下一个结点
    12. }
    13. //给链表插入一个结点
    14. //编写第一种插入方式:在链表的最后插入
    15. func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    16. //1.先找到链表最后这个结点
    17. //2.创建一个辅助结点
    18. temp := head
    19. for {
    20. if temp.next == nil { //表示找到了最后
    21. break
    22. }
    23. temp = temp.next //让temp不断指向下一个结点
    24. }
    25. //当for结束时,temp一定是找到了最后一个节点的
    26. //3.将newHeroNOde加入道最后
    27. temp.next = newHeroNode
    28. //4.再将temp指向newHeroNode.pre
    29. newHeroNode.pre = temp
    30. }
    31. //编写第二种插入方式: 根据no的编号从小到大插入
    32. func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    33. //1.先找适当的结点
    34. //2.创建一个辅助结点
    35. temp := head
    36. flag := true
    37. //让插入结点的no和temp的下一个结点的no比较
    38. for {
    39. if temp.next == nil { //表示到了最后的链表
    40. break
    41. } else if temp.next.no > newHeroNode.no {
    42. //说明newHeroNode就应该插入到temp后面
    43. break
    44. } else if temp.next.no == newHeroNode.no {
    45. //说明链表中已经有这个no,就不允许插入了
    46. flag = false
    47. break
    48. }
    49. temp = temp.next //让temp不断指向下一个结点
    50. }
    51. if !flag {
    52. fmt.Println("已存在no=", newHeroNode.no)
    53. return
    54. } else {
    55. newHeroNode.next = temp.next
    56. newHeroNode.pre = temp
    57. if temp.next != nil {
    58. temp.next.pre = newHeroNode
    59. }
    60. temp.next = newHeroNode
    61. }
    62. }
    63. //显示链表的所有信息:顺序方式
    64. func ListHeroNode(head *HeroNode) {
    65. //1.创建一个辅助结点
    66. temp := head
    67. //先判断该链表是不是空的链表
    68. if temp.next == nil {
    69. fmt.Println("空的链表")
    70. return
    71. }
    72. //2.遍历链表
    73. for {
    74. fmt.Printf("[%d, %s, %s] ==> ", temp.next.no, temp.next.name, temp.next.nickname)
    75. //判断链表是否最后
    76. temp = temp.next
    77. if temp.next == nil {
    78. break
    79. }
    80. }
    81. }
    82. //显示链表的所有信息:逆序方式
    83. func ListHeroNode2(head *HeroNode) {
    84. //1.创建一个辅助结点
    85. temp := head
    86. //先判断该链表是不是空的链表
    87. if temp.next == nil {
    88. fmt.Println("空的链表")
    89. return
    90. }
    91. //2.让temp定位到双向链表的最后结点
    92. for {
    93. if temp.next == nil {
    94. break
    95. }
    96. temp = temp.next
    97. }
    98. //2.遍历链表
    99. for {
    100. fmt.Printf("[%d, %s, %s] <== ", temp.no, temp.name, temp.nickname)
    101. //判断链表是否到head
    102. temp = temp.pre
    103. if temp.pre == nil {
    104. break
    105. }
    106. }
    107. }
    108. //删除一个结点
    109. func DelHeroNode(head *HeroNode, id int) {
    110. temp := head
    111. flag := false
    112. //找到要删除的结点的no,和temp的下一个结点的no比较
    113. for {
    114. if temp.next == nil {//说明到了链表的最后
    115. break
    116. } else if temp.next.no == id {
    117. //说明找到了
    118. flag = true
    119. break
    120. }
    121. temp = temp.next
    122. }
    123. if flag {//找到了,就行删除操作
    124. temp.next = temp.next.next // 这样目标结点就会成为一个垃圾结点,被系统回收
    125. if temp.next != nil { //当下一个结点存在时,才操作
    126. temp.next.pre = temp
    127. }
    128. } else {
    129. fmt.Println("要删除的结点id不存在")
    130. }
    131. }
    132. func main() {
    133. //1.先创建一个头结点
    134. head := &HeroNode{}
    135. //2.创建一个新的结点
    136. head1 := &HeroNode{
    137. no: 1,
    138. name: "宋江",
    139. nickname: "及时雨",
    140. }
    141. head2 := &HeroNode{
    142. no: 2,
    143. name: "陆静怡",
    144. nickname: "玉骑铃",
    145. }
    146. head3 := &HeroNode{
    147. no: 3,
    148. name: "零充",
    149. nickname: "包子头",
    150. }
    151. //3.加入
    152. //第一种方法
    153. InsertHeroNode(head, head1)
    154. InsertHeroNode(head, head2)
    155. InsertHeroNode(head, head3)
    156. //第二种方法
    157. // InsertHeroNode2(head, head1)
    158. // InsertHeroNode2(head, head3)
    159. // InsertHeroNode2(head, head2)
    160. // //4.列表
    161. //顺序
    162. ListHeroNode(head)
    163. fmt.Println()
    164. //逆序
    165. ListHeroNode2(head)
    166. // //5.删除
    167. // fmt.Println()
    168. DelHeroNode(head, 2)
    169. ListHeroNode(head)
    170. }

    5.单向环形链表的应用场景

    josephu 问题:

    设编号为1, 2 ,... n 的n个人围坐一圈, 约定编号为 k (1<=k<=n )的人从 1 开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列

    提示:

    用一个不带头结点的循环链表来处理josephu问题,先构成一个有 n 个结点的单循环链表,然后由 k 结点起从1开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除,算法结束

    6.环形单向链表的介绍

    7.环形单向链表应用案例

    完成对单向环形链表的添加结点,删除结点和显示

    删除一个环形单向琏表的思路如下:

    1.先让 temp 指向 head

    2.让 helper 指向环形链表最后

    3.让temp 和要删除的id进行比较,如果相同,则同helper完成删除(这里必须考虑如果删除的就是头结点)

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. //定义一个CatNode结构体
    6. type CatNode struct {
    7. no int //猫的编号
    8. name string //名字
    9. next *CatNode //下一个结点
    10. }
    11. //插入环形链表
    12. func InsertCatNode(head *CatNode, newCatNode *CatNode) {
    13. //判断是不是添加第一只猫
    14. if head.next == nil {
    15. head.no = newCatNode.no
    16. head.name = newCatNode.name
    17. head.next = head // 构成一个环形
    18. fmt.Println(newCatNode, "加入到环形的链表")
    19. return
    20. }
    21. //先定义一个临时的变量,找到环形的最后结点
    22. temp := head
    23. for {
    24. if temp.next == head {
    25. break
    26. }
    27. temp = temp.next
    28. }
    29. //加入到链表
    30. temp.next = newCatNode
    31. newCatNode.next = head
    32. }
    33. //显示环形链表
    34. func ListCircleLink(head *CatNode) {
    35. temp := head
    36. if temp.next == nil {
    37. fmt.Println("空的链表")
    38. return
    39. }
    40. for {
    41. fmt.Printf("猫的信息为:no=%d,name=%s, =>\n", temp.no, temp.name)
    42. if temp.next == head {//说明链表环状查询完毕
    43. break
    44. }
    45. temp = temp.next
    46. }
    47. }
    48. //删除
    49. func DelCircleCatNode(head *CatNode, id int) *CatNode {
    50. temp := head
    51. helper := head
    52. //空链表
    53. if temp.next == nil {
    54. fmt.Println("空链表")
    55. return head
    56. }
    57. //如果只有一个结点
    58. if temp.next == head {
    59. temp.next = nil
    60. return head
    61. }
    62. //将helper定位到环形链表最后
    63. for {
    64. if helper.next == head {
    65. break
    66. }
    67. helper = helper.next
    68. }
    69. //如果有两个以及以上的结点
    70. flag := true
    71. for {
    72. if temp.next == head { //说明已经比较到最后一个(最后一个还没比较)
    73. break
    74. }
    75. if temp.no == id { // 找到了
    76. if temp == head { // 说明删除的是头结点
    77. head = temp.next
    78. }
    79. //可以在这里删除
    80. helper.next = temp.next
    81. fmt.Printf("猫:%d已经被删除了\n", id)
    82. flag = false
    83. break
    84. }
    85. temp = temp.next // 移动,目的是为了 "比较"
    86. helper = helper.next // 移动,目的是为了删除结点
    87. }
    88. //这里还要比较一次
    89. if flag { // 如果flag为true,则上面没有删除
    90. if temp.no == id {
    91. helper.next = temp.next
    92. fmt.Printf("猫:%d已经被删除了\n", id)
    93. } else {
    94. fmt.Printf("没有该猫,no=%d\n", id)
    95. }
    96. }
    97. return head
    98. }
    99. func main() {
    100. //初始化一个环形链表的头结点
    101. head := &CatNode{}
    102. //创建一只猫
    103. cat1 := &CatNode{
    104. no: 1,
    105. name: "tom",
    106. }
    107. cat2 := &CatNode{
    108. no: 2,
    109. name: "jack",
    110. }
    111. cat3 := &CatNode{
    112. no: 3,
    113. name: "mary",
    114. }
    115. InsertCatNode(head, cat1)
    116. InsertCatNode(head, cat2)
    117. InsertCatNode(head, cat3)
    118. ListCircleLink(head)
    119. head = DelCircleCatNode(head, 2)
    120. ListCircleLink(head)
    121. }

     创建一个链表模拟队列,实现数据入队列,数据出队列,显示队列

    1. package main
    2. import (
    3. "fmt"
    4. "errors"
    5. "os"
    6. )
    7. //定义一个结构体管理环形队列
    8. type CircleQueue struct {
    9. maxSize int //队列最大值:5
    10. array [5]int //使用数组 =>队列
    11. head int //指向队列队首:0
    12. tail int //指向队列队尾:0
    13. }
    14. //入队列 AddQueue(push)
    15. func (this *CircleQueue) Push(val int) (err error) {
    16. //判断环形队列是否满了
    17. if this.IsFull() {
    18. return errors.New("queue full")
    19. }
    20. //this.tail在队列尾部,但是不包含最后一个元素
    21. this.array[this.tail] = val //把值给尾部
    22. this.tail = (this.tail + 1) % this.maxSize
    23. return
    24. }
    25. //出队列 GetQueue(pop)
    26. func (this *CircleQueue) Pop() (val int, err error) {
    27. //判断环形队列是否为空
    28. if this.IsEmpty() {
    29. return 0, errors.New("queue empty")
    30. }
    31. //取出:head是指向队首,并且含队首元素
    32. val = this.array[this.head]
    33. this.head = (this.head + 1) % this.maxSize
    34. return val, err
    35. }
    36. //判断环形队列是否满了
    37. func (this *CircleQueue) IsFull() bool {
    38. return (this.tail+1)%this.maxSize == this.head
    39. }
    40. //判断环形队列是否为空
    41. func (this *CircleQueue) IsEmpty() bool {
    42. return this.tail == this.head
    43. }
    44. //取出环形队列有多少个元素
    45. func (this *CircleQueue) Size() int {
    46. //这是个关键的点
    47. return (this.tail + this.maxSize - this.head) % this.maxSize
    48. }
    49. //显示队列
    50. func (this *CircleQueue) ListQueue() {
    51. fmt.Println("环形队列情况如下:")
    52. //取出当前队列有多少个元素
    53. size := this.Size()
    54. if size == 0 {
    55. fmt.Println("队列为空")
    56. }
    57. //定义一个临时变量,指向head
    58. tempHead := this.head
    59. for i := 0; i < size; i++ {
    60. fmt.Printf("array[%d] = %d\n", tempHead, this.array[tempHead])
    61. tempHead = (tempHead + 1) % this.maxSize
    62. }
    63. }
    64. func main() {
    65. //先创建一个队列
    66. queue := &CircleQueue{
    67. maxSize: 5,
    68. head: 0,
    69. tail: 0,
    70. }
    71. var key string
    72. var val int
    73. for {
    74. fmt.Println("1. 输入push,表示添加数据到队列")
    75. fmt.Println("2. 输入pop,表示从队列获取数据")
    76. fmt.Println("3. 输入list,表示显示队列")
    77. fmt.Println("4. 输入exit,表示退出队列")
    78. fmt.Scanln(&key)
    79. switch key {
    80. case "push":
    81. fmt.Println("输入要入队的对数:")
    82. fmt.Scanln(&val)
    83. err := queue.Push(val)
    84. if err != nil {
    85. fmt.Println(err.Error())
    86. } else {
    87. fmt.Println("加入队列成功")
    88. }
    89. case "pop":
    90. val, err := queue.Pop()
    91. if err != nil {
    92. fmt.Println(err.Error())
    93. } else {
    94. fmt.Printf("从队列中取出的数为:%d\n", val)
    95. }
    96. case "list":
    97. queue.ListQueue()
    98. case "exit":
    99. os.Exit(0)
    100. default:
    101. fmt.Println("输入有误,请重新输入")
    102. }
    103. }
    104. }

    [上一节][go学习笔记.第十七章.redis的使用] 1.redis的使用

    [下一节][go学习笔记.第十八章.数据结构] 2.约瑟夫问题,排序,栈,递归,哈希表,二叉树的三种遍历方式 

  • 相关阅读:
    影单:分享一下最近在看的一些电影
    Java之转换流的详细解析
    【7z密码】7z压缩包密码忘记了,怎么办?i
    零信任架构在企业中的应用
    Go中的有限状态机FSM的详细介绍
    2、CSS基础
    这些嵌入式知识助你秋招,也助你进阶
    Java学习之super关键字
    git命令
    分享一个python无人超市管理系统django项目实战源码调试 lw 开题
  • 原文地址:https://blog.csdn.net/zhoupenghui168/article/details/128061769