Go 【数据结构】单链表详解

382 阅读6分钟

Go 【数据结构】单链表详解

作者 :刘俊 Bingo Gophist

链表的定义

链表是由一个个数据节点组成的。它是一个递归结构,要么它是空的,要么它存在一个节点指向另一个节点的引用。

链表的特点

不能随机访问,只能根据链一个一个查找,查找的时间复杂度是 O(n)

  • 链表是以节点方式存储的。
  • 每个节点包含data域,next域。next域指向下一个节点。
  • 链表中的各个节点不一定是连续存储,如图所示:

图片.png

单链表

单链表,就是链表是单向的,像我们上面这个结构一样,可以一直往下找到下一个数据节点,它只有一个方向,它不能往回找。

代码实现的思路

  • 先创建一个节点
type Node struct {
   no       int
   name     string
   next     *Node // 这个表示指向下一个节点
}
  • 给链表加入结点

  • 方法一 在链表的最后加入

  1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
  2. 通过遍历,找到该链表的最后一个结点
  3. 把原先最后一个结点的 next 指向 newNode,即将 newNode 加入到链表的最后
func InsertHeroNode(head *Node, newNode *Node) {
   // 1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
   flag := head
   // 2. 通过遍历,找到该链表的最后一个结点
   for {
      if flag.next == nil { // 表示找到最后
         break
      }
      flag = flag.next // 让temp指向下一个结点,实现循环♻️
   }
   // 3. 把原先最后一个结点的 next 指向 newNode,即将 newNode 加入到链表的最后
   flag.next = newNode

}

  • 方法二 根据 编号从小到大的顺序加入
  1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
  2. 让插入的节点的num,和temp的下一个节点的 no 做比较 如果找到了链表的最后,在链表的尾部插入节点 如果 flag 的序号小于 flag 的下一个结点,就加在 flag 之后, flag 下一个节点之前
func InsertHeroNodeInOrder(head *Node, newNode *Node) {
   // 思路
   // 1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
   flag := head
   // 2. 让插入的节点的num,和temp的下一个节点的 no 做比较
   for {
      if flag.next == nil {
         // 找到了链表的最后,在链表的尾部插入节点
         flag.next = newNode
         return

      } else if flag.next.no > newNode.no {
         // 说明 newNode(第2) 应该插在 flag(第1) 和 flag.next(第3) 之间
         newNode.next = flag.next // newNode(第2) 的 next 是 flag.next(第3)
         flag.next = newNode      // flag(第1) 的 next 是 newNode(第2)
         return

      } else if flag.next.no == newNode.no { // 错误🙅
         // 说明链表中已经有这个no,不允许加入
         fmt.Println("序号已存在,请重新添加!")
         return
      }
      flag = flag.next // 让temp指向下一个结点,实现循环♻️
   }
}

  • 显示链表的所有结点信息
  1. 创建一个辅助结点作为迭代的flag 🚩,从第一个结点开始
  2. 判断该链表是不是一个空链表
  3. 如果不是,遍历这个链表,并显示其中的信息
func ListHeroNode(head *HeroNode) {

   // 1. 创建一个辅助结点[跑龙套,帮忙],从第一个结点开始
   temp := head

   // 2. 先判断该链表是不是一个空链表
   if temp.next == nil {
      fmt.Println("该链表空空如也...")
      return
   }

   // 2. 遍历这个链表
   for {
      fmt.Printf("[%d, %s, %s] -> \n",
         temp.next.no,
         temp.next.name,
         temp.next.nickname,
      )
      // 下一个结点
      temp = temp.next
      // 判断是否存在该结点
      if temp.next == nil {
         break
      }
   }

}

  • 删除链表中的节点
  1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
  2. flag.next 从第一个节点开始遍历
  3. deleteNode 和 flag.next 它们指向的下一个节点相同,代表它们本身相同
  4. 要删除 flag.next,就是要把 flag 指向 flag.next.next
func DeleteNode(head *Node, deleteNode *Node) {
   // 1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
   flag := head
   // flag.next 从第一个节点开始遍历
   for {
      // deleteNode 和 flag.next 它们指向的下一个节点相同,代表它们本身相同。
      // 要删除 flag.next,就是要把 flag 指向 flag.next.next
      if deleteNode.next == flag.next.next {
         flag.next = flag.next.next
         return
      }

      if flag.next == nil {
         return
      } else {
         flag = flag.next
      }
   }

}

  • 运行主函数
func main() {
   // 1. 先创建一个头节点
   head := &Node{}

   // 2. 创建新的节点 Node
   node1 := &Node{
      no:   1,
      name: "宋江",
   }

   node2 := &Node{
      no:   2,
      name: "卢俊义",
   }

   node3 := &Node{
      no:   3,
      name: "林冲",
   }

   // 3. 加入
   InsertHeroNode(head, node1)
   InsertHeroNode(head, node2)
   InsertHeroNode(head, node3)

   // 4. 显示
   ListNode(head)

   // 5. 删除
   DeleteNode(head, node2)

   // 6. 显示删除后的结果
   ListNode(head)

}

在线运行

  • 单链表

go.dev/play/p/TdG2…

  • 带排序的单链表

go.dev/play/p/_UWW…

完整代码

  • 单链表 Single Link
package main

import "fmt"

// 单链表

// 定义一个Node

type Node struct {
   no   int
   name string
   next *Node // 表示指向下一个节点的指针
}

// 给链表插入一个结点

// 编写第二种插入方式,根据 no. 编号从小到大的顺序加入.

func InsertHeroNodeInOrder(head *Node, newNode *Node) {
   // 思路
   // 1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
   flag := head
   // 2. 让插入的节点的num,和temp的下一个节点的 no 做比较
   for {
      if flag.next == nil {
         // 找到了链表的最后,在链表的尾部插入节点
         flag.next = newNode
         return

      } else if flag.next.no > newNode.no {
         // 说明 newNode(第2) 应该插在 flag(第1) 和 flag.next(第3) 之间
         newNode.next = flag.next // newNode(第2) 的 next 是 flag.next(第3)
         flag.next = newNode      // flag(第1) 的 next 是 newNode(第2)
         return

      } else if flag.next.no == newNode.no { // 错误🙅
         // 说明链表中已经有这个no,不允许加入
         fmt.Println("序号已存在,请重新添加!")
         return
      }
      flag = flag.next // 让temp指向下一个结点,实现循环♻️
   }
}

// 显示链表的所有结点信息

func ListNode(head *Node) {

   // 1. 创建一个辅助结点[作为迭代的flag 🚩],从第一个结点开始
   flag := head.next

   // 2. 先判断该链表是不是一个空链表
   if flag == nil {
      fmt.Println("该链表空空如也...")
      return
   }

   // 2. 遍历这个链表
   for {
      fmt.Printf("[%d, %s] ",
         flag.no,
         flag.name,
      )
      // 下一个结点
      flag = flag.next
      // 判断是否存在该结点
      if flag == nil {
         break
      } else {
         fmt.Print("-> ")
      }
   }

}

func main() {
   // 1. 先创建一个头节点
   head := &Node{}

   // 2. 创建新的节点 Node
   node1 := &Node{
      no:   1,
      name: "宋江",
   }

   node2 := &Node{
      no:   4,
      name: "卢俊义",
   }

   node3 := &Node{
      no:   2,
      name: "林冲",
   }

   // 3. 加入
   InsertHeroNodeInOrder(head, node1)
   InsertHeroNodeInOrder(head, node2)
   InsertHeroNodeInOrder(head, node3)

   // 4. 显示
   ListNode(head)
}
  • 带排序的单链表
package main

import "fmt"

// 单链表

// 定义一个Node

type Node struct {
   no   int
   name string
   next *Node // 表示指向下一个节点的指针
}

// 给链表插入一个结点

// 编写第二种插入方式,根据 no. 编号从小到大的顺序加入.

func InsertHeroNodeInOrder(head *Node, newNode *Node) {
   // 思路
   // 1. 创建一个flag 🚩结点,用于遍历链表中的每个节点
   flag := head
   // 2. 让插入的节点的num,和temp的下一个节点的 no 做比较
   for {
      if flag.next == nil {
         // 找到了链表的最后,在链表的尾部插入节点
         flag.next = newNode
         return

      } else if flag.next.no > newNode.no {
         // 说明 newNode(第2) 应该插在 flag(第1) 和 flag.next(第3) 之间
         newNode.next = flag.next // newNode(第2) 的 next 是 flag.next(第3)
         flag.next = newNode      // flag(第1) 的 next 是 newNode(第2)
         return

      } else if flag.next.no == newNode.no { // 错误🙅
         // 说明链表中已经有这个no,不允许加入
         fmt.Println("序号已存在,请重新添加!")
         return
      }
      flag = flag.next // 让temp指向下一个结点,实现循环♻️
   }
}

// 显示链表的所有结点信息

func ListNode(head *Node) {

   // 1. 创建一个辅助结点[作为迭代的flag 🚩],从第一个结点开始
   flag := head.next

   // 2. 先判断该链表是不是一个空链表
   if flag == nil {
      fmt.Println("该链表空空如也...")
      return
   }

   // 2. 遍历这个链表
   for {
      fmt.Printf("[%d, %s] ",
         flag.no,
         flag.name,
      )
      // 下一个结点
      flag = flag.next
      // 判断是否存在该结点
      if flag == nil {
         break
      } else {
         fmt.Print("-> ")
      }
   }

}

func main() {
   // 1. 先创建一个头节点
   head := &Node{}

   // 2. 创建新的节点 Node
   node1 := &Node{
      no:   1,
      name: "宋江",
   }

   node2 := &Node{
      no:   4,
      name: "卢俊义",
   }

   node3 := &Node{
      no:   2,
      name: "林冲",
   }

   // 3. 加入
   InsertHeroNodeInOrder(head, node1)
   InsertHeroNodeInOrder(head, node2)
   InsertHeroNodeInOrder(head, node3)

   // 4. 显示
   ListNode(head)
}

参考: