当前位置: 首页 > news >正文

【GoLang】3、基于虚拟头尾节点快速实现双向链表

1、背景

鸽了很久很久的一个东西了…现在有时间了,还是来把它补齐一下。

双向链表,在 C++ 中经常使用到:

  • [C++系列] 54. list的介绍及使用

习题练手:

  • 146. LRU 缓存

Github 代码仓库,欢迎 start!

  • go-blog list

2、代码实现

双向链表(Doubly Linked List)实现说明

基于 Go 语言实现了一个带有虚拟头结点和虚拟尾结点的双向链表(Doubly Linked List),旨在简化边界条件处理并提高代码可读性与健壮性。

  • 使用虚拟头结点(dummyHead)和虚拟尾结点(dummyTail),避免边界情况下的空指针判断。
  • 所有插入、删除操作均统一处理,无需额外判断是否为头尾节点。
  • 提供正向和反向遍历接口,返回切片便于测试和调试。
  • 插入节点提取公共方法,简化代码。
  • 提供完整的单元测试,保证代码质量。

2.1、v1 版本(未提取公共插入代码)

package v1import ("fmt"
)// ListNode 链表节点数据结构定义
type ListNode struct {prev, next *ListNode // 前驱节点、后继节点val        any       // 数据
}// List 双向链表数据结构定义
type List struct {head, tail *ListNode // 头节点、尾节点len        int       // 链表长度
}// Next ListNode Next 获取下一个节点,如果不存在,则返回nil
func (l *ListNode) Next() *ListNode {if l != nil && l.next != nil {return l.next}return nil
}// Prev ListNode Prev 获取前一个节点,如果不存在,则返回nil
func (l *ListNode) Prev() *ListNode {if l != nil && l.prev != nil {return l.prev}return nil
}// NewList List 创建一个链表
// 使用虚拟结点写法,创建虚拟头结点、尾结点,避免后续各类边界判断
func NewList() *List {dummyHead := &ListNode{val: nil}dummyTail := &ListNode{val: nil}dummyHead.next = dummyTaildummyTail.prev = dummyHeadlist := &List{head: dummyHead,tail: dummyTail,len:  0,}return list
}// PushBack 尾插
func (l *List) PushBack(v any) {node := &ListNode{val: v,}node.next = l.tailnode.prev = l.tail.prevl.tail.prev.next = nodel.tail.prev = nodel.len++
}// PushFront 头插
func (l *List) PushFront(v any) {node := &ListNode{val: v,}node.prev = l.headnode.next = l.head.nextl.head.next.prev = nodel.head.next = nodel.len++
}// InsertAfter 在 at 节点后面插入节点
// at 不存在,则直接插入末尾
func (l *List) InsertAfter(at *ListNode, v any) {if at == nil {l.PushBack(v)return}node := &ListNode{val: v,}node.prev = atnode.next = at.nextnode.prev.next = nodenode.next.prev = nodel.len++
}// InsertBefore 在 at 节点前面插入节点
// at 不存在则直接插入开头
func (l *List) InsertBefore(at *ListNode, v any) {if at == nil {l.PushFront(v)return}node := &ListNode{val: v,}node.next = atnode.prev = at.prevat.prev.next = nodeat.prev = nodel.len++
}// Find 查找节点
// 仅返回第一个匹配的节点
func (l *List) Find(v any) *ListNode {// 跳过虚拟头结点、跳过虚拟尾结点for node := l.head.next; node.Next() != nil; node = node.next {if node.val == v {return node}}return nil
}// Remove 删除节点
func (l *List) Remove(v any) {node := l.Find(v)if node != nil {node.prev.next = node.nextnode.next.prev = node.prevnode.next = nilnode.prev = nill.len--}
}// GetLen 获取长度
func (l *List) GetLen() int {return l.len
}// ForEach 正向遍历
func (l *List) ForEach() []any {res := make([]any, 0)for node := l.head.next; node.Next() != nil; node = node.next {res = append(res, node.val)}return res
}// ForReverse 反向遍历
func (l *List) ForReverse() []any {res := make([]any, 0)for node := l.tail.prev; node.Prev() != nil; node = node.prev {res = append(res, node.val)}return res
}// Print 打印
func (l *List) Print() {res := make([]any, 0)// 跳过虚拟头结点、跳过虚拟尾结点for node := l.head.next; node.Next() != nil; node = node.next {res = append(res, node.val)}fmt.Println(res)
}

2.2、v2 版本(提取公共插入代码)

package v2import ("fmt"
)// ListNode 链表节点数据结构定义
type ListNode struct {prev, next *ListNode // 前驱节点、后继节点val        any       // 数据
}// List 双向链表数据结构定义
type List struct {head, tail *ListNode // 头节点、尾节点len        int       // 链表长度
}// Next ListNode Next 获取下一个节点,如果不存在,则返回nil
func (l *ListNode) Next() *ListNode {if l != nil && l.next != nil {return l.next}return nil
}// Prev ListNode Prev 获取前一个节点,如果不存在,则返回nil
func (l *ListNode) Prev() *ListNode {if l != nil && l.prev != nil {return l.prev}return nil
}// NewList List 创建一个链表
// 使用虚拟结点写法,创建虚拟头结点、尾结点,避免后续各类边界判断
func NewList() *List {dummyHead := &ListNode{val: nil}dummyTail := &ListNode{val: nil}dummyHead.next = dummyTaildummyTail.prev = dummyHeadlist := &List{head: dummyHead,tail: dummyTail,len:  0,}return list
}// insert 插入的通用方法
func (l *List) insert(prev, next *ListNode, v any) {node := &ListNode{val: v, prev: prev, next: next}prev.next = nodenext.prev = nodel.len++
}// PushBack 尾插
func (l *List) PushBack(v any) {l.insert(l.tail.prev, l.tail, v)
}// PushFront 头插
func (l *List) PushFront(v any) {l.insert(l.head, l.head.next, v)
}// InsertAfter 在 at 节点后面插入节点
// at 不存在,则直接插入末尾
func (l *List) InsertAfter(at *ListNode, v any) {if at == nil {l.PushBack(v)return}l.insert(at, at.next, v)
}// InsertBefore 在 at 节点前面插入节点
// at 不存在则直接插入开头
func (l *List) InsertBefore(at *ListNode, v any) {if at == nil {l.PushFront(v)return}l.insert(at.prev, at, v)
}// Find 查找节点
// 仅返回第一个匹配的节点
func (l *List) Find(v any) *ListNode {// 跳过虚拟头结点、跳过虚拟尾结点for node := l.head.next; node != l.tail; node = node.next {if node.val == v {return node}}return nil
}// Remove 删除节点
func (l *List) Remove(v any) {node := l.Find(v)if node != nil {node.prev.next = node.nextnode.next.prev = node.prevnode.next = nilnode.prev = nill.len--}
}// GetLen 获取长度
func (l *List) GetLen() int {return l.len
}// ForEach 正向遍历
func (l *List) ForEach() []any {res := make([]any, 0)for node := l.head.next; node != l.tail; node = node.next {res = append(res, node.val)}return res
}// ForReverse 反向遍历
func (l *List) ForReverse() []any {res := make([]any, 0)for node := l.tail.prev; node != l.head; node = node.prev {res = append(res, node.val)}return res
}// Print 打印
func (l *List) Print() {res := make([]any, 0)// 跳过虚拟头结点、跳过虚拟尾结点for node := l.head.next; node != l.tail; node = node.next {res = append(res, node.val)}fmt.Println(res)
}

3、总结

v2 版本确实是简单易写不易错,且引入了 头尾 的虚拟节点后,在 头插、尾插、头删、尾删 等边界情况下都需要再特殊考虑了。

同时值得一提的是,Go 官方库在一开始其实就支持 list这个数据结构了,且代码写的也十分精简,优雅。

几乎是在 v2 版本上的进一步升级,十分值得学习和使用!

相关文章:

  • 计算Transformer的Flops
  • 从 0 到 1 打造社区产品:短说社区助力开启社交新篇
  • Java编程中的设计模式:单例模式的深度剖析
  • 深度解析 Caffeine:高性能 Java 缓存库
  • LED-Merging: 无需训练的模型合并框架,兼顾LLM安全和性能!!
  • iOS App 上架步骤解析:适合资源有限团队的上架流程与注意事项
  • 【Verilog】Verilator的TestBench该用C++还是SystemC
  • OpenSSL 混合加密
  • 16.数据聚合
  • C++的前世今生-C++11
  • 进入python虚拟环境的方法
  • hive集群优化和治理常见的问题答案
  • 「ECG信号处理——(18)基于时空特征的心率变异性分析」2025年6月23日
  • 实时反欺诈:基于 Spring Boot 与 Flink 构建信用卡风控系统
  • 2025.06.23【甲基化】methylKit:甲基化测序数据分析安装与详细使用教程
  • 鸿蒙容器组件 Row 全解析:水平布局技术与多端适配指南
  • 《Effective Python》第十章 健壮性——善用 try/except/else/finally,写出更健壮的 Python 异常处理代码
  • 体制内写公文,用ai工具辅助写材料
  • Advent of Cyber 1 [2019] - [Day 13] | TryHackMe
  • Go 语言使用 excelize 库操作 Excel 的方法
  • 河南省专业做网站公司/域名购买哪个网站好
  • 免费做logo的网站/在线教育
  • 网站推广软文案例/百度竞价冷门产品
  • 网站建设方法:/免费的外链平台
  • 沈阳app制作网站建设推/电商平台怎么加入
  • 网站服务器买了后怎么做/客户推广渠道有哪些