探索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语言在处理复杂数据结构时的简洁性和高效性。

实际应用

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

相关推荐
AskHarries3 分钟前
如何利用EasyExcel导出带有选择校验框的excel?
java·后端·spring cloud·excel
egekm_sefg5 分钟前
【Golang】——Gin 框架中的模板渲染详解
开发语言·golang·gin
在在进步36 分钟前
R学习——数据框
开发语言·r语言
大G哥40 分钟前
27. 聚类分析 - 使用R进行客户分群
开发语言·信息可视化·r语言
我不是程序猿儿1 小时前
【C#】反射 和 特性(Attribute)、[AttributeUsage(AttributeTargets.Property)]
开发语言·c#
妈妈说名字太长显傻1 小时前
【C++】string类
开发语言·c++
丢丢丢丢丢丢~1 小时前
c++创建每日文件夹,放入每日日志
开发语言·c++
華華3551 小时前
读程序题...
开发语言·c++·算法
m0_547486661 小时前
西安交通大学2001年数据结构试题
数据结构