探索Go语言中的循环单链表

简介

循环单链表是一种特殊的链表数据结构,它的最后一个节点指向链表的头节点,形成一个闭环。今天我们将探讨如何在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函数中,我们展示了如何创建一个循环单链表、插入节点、删除节点以及打印链表的状态:

go 复制代码
func main() {
    // ... (代码)
}

总结

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

实际应用

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

相关推荐
长栎12 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode16 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812221 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode21 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战23 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha42 分钟前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn42 分钟前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改1 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构