简介
循环单链表是一种特殊的链表数据结构,它的最后一个节点指向链表的头节点,形成一个闭环。今天我们将探讨如何在Go语言中实现和操作这种数据结构。
为什么选择循环单链表?
- 连续访问:在循环单链表中,可以无限循环地遍历数据元素,这在某些应用场景下非常有用,例如游戏中的无限滚动背景或队列管理。
- 内存效率:由于循环特性,链表的尾节点可以直接链接到头节点,无需额外的指针或标记。
代码实现
以下是我们在Go中实现循环单链表的完整代码:
go
// -------------------------------------------
// @file : CircularLinkedList.go
// @author : Serein
// @contact : xming9523@gmail.com
// @blog : https://blog.childish.vip
// @time : 2024/12/3 21:15:22 星期二
// -------------------------------------------
package main
import "fmt"
// 循环单链表
// CircularListNode 节点结构体定义
type CircularListNode struct {
Val int // 节点的值
Next *CircularListNode // 指向下一个节点的指针
}
// CircularLinkList 循环单链表结构体定义
type CircularLinkList struct {
Head *CircularListNode // 链表的头节点
Tail *CircularListNode // 链表的尾节点
}
// NewCircularLinkList 初始化循环单链表
func NewCircularLinkList() *CircularLinkList {
return &CircularLinkList{Head: nil, Tail: nil} // 返回一个新的空循环链表
}
func (l *CircularLinkList) insertNode(val int, atHead bool) {
newNode := &CircularListNode{Val: val} // 创建一个新的节点
// 如果链表为空
if l.Head == nil {
l.Head = newNode // 新节点作为头节点
l.Tail = newNode // 也是尾节点
l.Tail.Next = l.Head // 形成循环
return
}
// 在头部插入节点
if atHead {
newNode.Next = l.Head // 新节点的下一个节点是原来的头节点
l.Head = newNode // 新节点成为新的头节点
l.Tail.Next = l.Head // 更新尾节点的下一个节点为新的头节点
} else {
// 在尾部插入节点操作
l.Tail.Next = newNode // 当前尾节点的下一个节点是新节点
l.Tail = newNode // 新节点成为新的尾节点
l.Tail.Next = l.Head // 是链表保持循环
}
}
// InsertAtHead 在头部插入节点操作
func (l *CircularLinkList) InsertAtHead(val int) {
l.insertNode(val, true) // 调用通用的插入方法,在头部插入
}
// InsertAtTail 在尾部插入节点操作
func (l *CircularLinkList) InsertAtTail(val int) {
l.insertNode(val, false) // 调用通用的插入方法,在尾部插入
}
// InsertAtRandomPosition 在随机位置插入节点
func (l *CircularLinkList) InsertAtRandomPosition(pos int, val int) {
if pos <= 0 || l.Head == nil { // 如果位置无效和链表为空
l.insertNode(val, true) // 直接插入到头部
return
}
// 计算链表的长度
length := 1 // 初始长度为1,因为至少有一个头节点
current := l.Head
for current.Next != l.Head {
length++ // 增加长度计数
current = current.Next // 移动到下一个节点
}
if pos >= length { // 如果位置超过链表长度
l.insertNode(pos, false) // 插入尾部
return
}
current = l.Head
for i := 0; i < pos-1; i++ {
current = current.Next // 移动到指定位置的前一个节点
}
newNode := &CircularListNode{Val: val} // 创建新节点
newNode.Next = current.Next // 新节点的下一个节点是当前节点的下一个节点
current.Next = newNode // 当前节点的下一个节点指向新节点
}
func (l *CircularLinkList) DeleteAtPosition(pos int) {
if l.Head == nil || pos < 0 { // 如果链表为空或位置无效,直接返回
return
}
// 如果删除的是头节点
if pos == 0 {
if l.Head == l.Tail { // 如果只有一个头节点,就直接把头节点和尾节点置空
l.Head = nil
l.Tail = nil
} else {
l.Head = l.Head.Next // 头节点移到下一个节点
l.Tail.Next = l.Head // 更新尾节点的下一个节点为新的头节点
}
return
}
current := l.Head
for i := 0; current.Next != l.Head && i < pos-1; i++ { // 移动到要删除的节点的前一个节点
current = current.Next
}
if current.Next == l.Head { // 如果到达了链表的末尾,返回
return
}
// 删除节点
toDelete := current.Next
current.Next = toDelete.Next // 将当前节点的下一个节点指向删除节点的下一个节点
if toDelete.Next == l.Tail { // 如果删除节点是尾节点
l.Tail = current // 更新尾节点
}
}
// 打印循环单链表的操作
func (l *CircularLinkList) PrintList() {
if l.Head == nil { // 如果链表为空
return
}
current := l.Head
for {
fmt.Println(current.Val) // 打印当前节点的值
current = current.Next // 移动到下一个节点
if current == l.Head { // 如果回到头头节点,循环结束
break
}
}
}
func main() {
circularList := NewCircularLinkList()
circularList.InsertAtHead(1)
circularList.InsertAtTail(2)
circularList.InsertAtHead(3)
circularList.InsertAtTail(4)
circularList.InsertAtRandomPosition(2, 5)
circularList.PrintList()
circularList.DeleteAtPosition(2)
fmt.Println("删除后")
circularList.PrintList()
}
关键点解析
- 节点结构体 :
CircularListNode
定义了每个节点的结构,包含一个值和一个指向下一个节点的指针。 - 链表结构体 :
CircularLinkList
使用头指针和尾指针来管理整个链表,确保了操作的效率。 - 插入操作 :通过
insertNode
方法,我们可以灵活地在链表的头部或尾部插入新节点。InsertAtHead
和InsertAtTail
方法简化了这个过程。 - 随机位置插入 :
InsertAtRandomPosition
方法允许我们在一个指定位置插入节点。 - 删除操作 :
DeleteAtPosition
方法展示了如何删除指定位置的节点,并处理了特殊情况,如删除头节点或尾节点。 - 打印链表 :
PrintList
方法用来遍历并打印整个链表。
运行和测试
在main
函数中,我们展示了如何创建一个循环单链表、插入节点、删除节点以及打印链表的状态:
go
func main() {
// ... (代码)
}
总结
通过这个实现,我们了解了如何在Go中构建和操作一个循环单链表。循环单链表虽然在某些方面比普通链表复杂,但在特定应用中,它们提供的优点使其成为值得学习的数据结构。通过代码,我们还展示了Go语言在处理复杂数据结构时的简洁性和高效性。
实际应用
- 循环调度:如操作系统中的轮转调度算法。
- 循环缓冲区:如在视频播放或数据流中使用。
- 游戏开发:例如无限循环的游戏地图。