1、背景
鸽了很久很久的一个东西了…现在有时间了,还是来把它补齐一下。
双向链表,在 C++ 中经常使用到:
习题练手:
Github 代码仓库,欢迎 start!:
2、代码实现
双向链表(Doubly Linked List)实现说明
基于 Go 语言实现了一个带有虚拟头结点和虚拟尾结点的双向链表(Doubly Linked List),旨在简化边界条件处理并提高代码可读性与健壮性。
- 使用虚拟头结点(dummyHead)和虚拟尾结点(dummyTail),避免边界情况下的空指针判断。
- 所有插入、删除操作均统一处理,无需额外判断是否为头尾节点。
- 提供正向和反向遍历接口,返回切片便于测试和调试。
- 插入节点提取公共方法,简化代码。
- 提供完整的单元测试,保证代码质量。
2.1、v1 版本(未提取公共插入代码)
package v1
import (
"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 = dummyTail
dummyTail.prev = dummyHead
list := &List{
head: dummyHead,
tail: dummyTail,
len: 0,
}
return list
}
// PushBack 尾插
func (l *List) PushBack(v any) {
node := &ListNode{
val: v,
}
node.next = l.tail
node.prev = l.tail.prev
l.tail.prev.next = node
l.tail.prev = node
l.len++
}
// PushFront 头插
func (l *List) PushFront(v any) {
node := &ListNode{
val: v,
}
node.prev = l.head
node.next = l.head.next
l.head.next.prev = node
l.head.next = node
l.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 = at
node.next = at.next
node.prev.next = node
node.next.prev = node
l.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 = at
node.prev = at.prev
at.prev.next = node
at.prev = node
l.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.next
node.next.prev = node.prev
node.next = nil
node.prev = nil
l.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 v2
import (
"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 = dummyTail
dummyTail.prev = dummyHead
list := &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 = node
next.prev = node
l.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.next
node.next.prev = node.prev
node.next = nil
node.prev = nil
l.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 版本上的进一步升级,十分值得学习和使用!