Go 【数据结构】单链表详解
作者 :刘俊 Bingo Gophist
链表的定义
链表是由一个个数据节点组成的。它是一个递归结构,要么它是空的,要么它存在一个节点指向另一个节点的引用。
链表的特点
不能随机访问,只能根据链一个一个查找,查找的时间复杂度是 O(n)
- 链表是以节点方式存储的。
- 每个节点包含data域,next域。next域指向下一个节点。
- 链表中的各个节点不一定是连续存储,如图所示:
单链表
单链表,就是链表是单向的,像我们上面这个结构一样,可以一直往下找到下一个数据节点,它只有一个方向,它不能往回找。
代码实现的思路
- 先创建一个节点
type Node struct {
no int
name string
next *Node // 这个表示指向下一个节点
}
-
给链表加入结点
-
方法一 在链表的最后加入
- 创建一个flag 🚩结点,用于遍历链表中的每个节点
- 通过遍历,找到该链表的最后一个结点
- 把原先最后一个结点的 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
}
- 方法二 根据 编号从小到大的顺序加入
- 创建一个flag 🚩结点,用于遍历链表中的每个节点
- 让插入的节点的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指向下一个结点,实现循环♻️
}
}
- 显示链表的所有结点信息
- 创建一个辅助结点作为迭代的flag 🚩,从第一个结点开始
- 判断该链表是不是一个空链表
- 如果不是,遍历这个链表,并显示其中的信息
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
}
}
}
- 删除链表中的节点
- 创建一个flag 🚩结点,用于遍历链表中的每个节点
- flag.next 从第一个节点开始遍历
- deleteNode 和 flag.next 它们指向的下一个节点相同,代表它们本身相同
- 要删除 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)
}
在线运行
- 单链表
- 带排序的单链表
完整代码
- 单链表 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)
}
参考:
-
尚硅谷 - 韩顺平老师