自学内容网 自学内容网

探索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方法,我们可以灵活地在链表的头部或尾部插入新节点。InsertAtHeadInsertAtTail方法简化了这个过程。
  • 随机位置插入InsertAtRandomPosition方法允许我们在一个指定位置插入节点。
  • 删除操作DeleteAtPosition方法展示了如何删除指定位置的节点,并处理了特殊情况,如删除头节点或尾节点。
  • 打印链表PrintList方法用来遍历并打印整个链表。

运行和测试

main函数中,我们展示了如何创建一个循环单链表、插入节点、删除节点以及打印链表的状态:

func main() {
    // ... (代码)
}

总结

通过这个实现,我们了解了如何在Go中构建和操作一个循环单链表。循环单链表虽然在某些方面比普通链表复杂,但在特定应用中,它们提供的优点使其成为值得学习的数据结构。通过代码,我们还展示了Go语言在处理复杂数据结构时的简洁性和高效性。

实际应用

  • 循环调度:如操作系统中的轮转调度算法。
  • 循环缓冲区:如在视频播放或数据流中使用。
  • 游戏开发:例如无限循环的游戏地图。


原文地址:https://blog.csdn.net/2303_76247682/article/details/144251881

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!