Go实现单向链表

121 阅读3分钟

写在前面

仓库链接

结构定义

  • Head结构
type Head[T any] struct {  
    // start 起始节点  
    start INode[T]  
    // length 链表长度  
    length uint64  
    // curr 当前节点  
    curr INode[T]  
    // end 结尾节点  
    end INode[T]  
}
  • 接口定义
// node接口
type INode[T any] interface {  
    Next() INode[T]  
    Value() T  
    SetNext(INode[T])  
    SetValue(T)  
}
// head接口
type IHead[T any] interface {  
    First() INode[T]  
    Last() INode[T] 
    Length() uint64  
    IsEmpty() bool  
    Clear()  
    Curr() INode[T]  
    Next() INode[T]  
    // Append 尾插  
    Append(val T) IHead[T]  
    AppendNode(node INode[T]) IHead[T]  
    // Prepend 头插  
    Prepend(val T) IHead[T]  
    PrependNode(node INode[T]) IHead[T]  
    // Range 遍历  
    Range(fn func(node INode[T]) bool)  
    Slice() []T  
    Show() string  
    // Insert 插入  
    Insert(index uint64, val T) IHead[T]  
    InsertNode(index uint64, node INode[T]) IHead[T]  
    // Remove 移除  
    Remove(index uint64) IHead[T]  
    RemoveValue(val T) IHead[T]  
    RemoveHead() IHead[T]  
    RemoveTail() IHead[T]  
    // Reverse 反转  
    Reverse() IHead[T]  
    // Copy 复制  
    Copy() IHead[T]  
    // Find 查找  
    Find(fn func(val T) bool) (INode[T], bool)  
    FindIndex(fn func(val T) bool) (uint64, bool)  
    FindAll(fn func(val T) bool) []INode[T]
    // ...
}

实现具体方法

type Option[T any] func(*Head[T])

func New[T any](opts ...Option[T]) IHead[T] {  
    head := &Head[T]{}  
    for _, opt := range opts {  
        opt(head)  
    }  
    return head  
}

First

直接返回start节点, 该节点为单向链表起始节点

func (h *Head[T]) First() INode[T] {  
    return h.start  
}

Length

返回链表元素个数, 理论上直接返回length值即可, 但是INode具备SetNext方法, 因此可能会在尾节点追加一个链, 因此需要判断, 并维护length

func (h *Head[T]) Length() uint64 {  
    h.calcLength()  
    return h.length  
}

// calcLength 判断end节点是否为实际的end节点, 如果不是, 重新维护end和length
func (h *Head[T]) calcLength() {  
    for h.end != nil && h.end.Next() != nil {  
        h.length++  
        h.end = h.end.Next()  
    }  
}

IsEmpty

对length判断, 但是前面说了, 可能会被追加链到末尾, 因此也需要维护

func (h *Head[T]) IsEmpty() bool {  
    h.calcLength()  
    return h.length == 0  
}

Last

同上, 维护endlength, 并返回end

func (h *Head[T]) Last() INode[T] {  
    h.calcLength()  
    return h.end  
}

Append

尾插法, 向链表尾部持续添加节点

func (h *Head[T]) Append(val T) IHead[T] {  
    h.length++  
    if h.start == nil {  
        h.setFirst(NewNode[T](val))  
        return h  
    }  

    h.end.SetNext(NewNode[T](val))  
    h.end = h.end.Next()  
    return h  
}

Prepend

头插法, 向链表头部持续添加节点

func (h *Head[T]) Prepend(val T) IHead[T] {  
    h.length++  
    if h.start == nil {  
        h.setFirst(NewNode[T](val))  
        return h  
    }  

    node := NewNode[T](val)  
    node.SetNext(h.start)  
    h.start = node  
  
    return h  
}

Insert

向指定索引位置插入数据, 这里插入的是数据, 不是节点, 所以基于该数据初始化节点, 然后调用节点插入方法插入

插入节点时候, 判断插入的位置是否属于特殊位置(头部或者尾部, 如果是, 则直接调用头插或者尾插结束, 如果不是, 则计算插入的位置, 断开原来的链, 与新链连接)

func (h *Head[T]) Insert(index uint64, val T) IHead[T] {  
    node := NewNode(val)  
    return h.InsertNode(index, node)  
}  
  
func (h *Head[T]) InsertNode(index uint64, node INode[T]) IHead[T] {  
    if index == 0 {  
        return h.PrependNode(node)  
    }  

    if index >= h.length {  
        return h.AppendNode(node)  
    }  

    prev := h.start  
    for i := uint64(1); i < index; i++ {  
        prev = prev.Next()  
    }  

    // 计算插入的节点是不是一个链表, 如果是, 要把链表维护进当前链表, 也就是头节点插入index位置, 尾节点与当前链表断开处相连
    n := node  
    h.length++  
    for n.Next() != nil {  
        h.length++  
        n = n.Next()  
    }  
    n.SetNext(prev.Next())  
    prev.SetNext(node)  
    return h  
}

Remove

删除指定位置的节点, 判断index是否属于特殊位置, 如果是, 则直接调用删除头节点或者尾节点方法即可.

RemoveValue接收一个回调函数, 因为该链表是范型, 内部不好直接判断(比较), 因此暴露比较的回调函数参数, 由用户自行实现比较函数(例如对象是个自定义结构体或者map, 用户可以根据其中某一个属性判断), 然后把匹配的节点都删除, 这样就完成了根据节点值删除节点的功能

func (h *Head[T]) Remove(index uint64) IHead[T] {  
    if index >= h.Length() {  
        return h  
    }  

    if index == h.Length()-1 {  
        return h.RemoveTail()  
    }  

    if index == 0 {  
        return h.RemoveHead()  
    }  

    prev := h.start  
    for i := uint64(1); i < index; i++ {  
        prev = prev.Next()  
    }  

    prev.SetNext(prev.Next().Next())  
    h.length--  
    return h  
}  
  
func (h *Head[T]) RemoveValue(fn func(val T) bool) IHead[T] {  
    if h.IsEmpty() {  
        return h  
    }  

    for fn(h.start.Value()) {  
        h.start = h.start.Next()  
        h.length--  
    }  

    prev := h.start  
    curr := h.start.Next()  

    for curr != nil {  
        if fn(curr.Value()) {  
            prev.SetNext(curr.Next())  
            h.length--  
            curr = prev.Next()  
            continue  
        }  
        prev = curr  
        curr = curr.Next()  
    }
    
    h.end = prev
    
    return h  
}  
  
func (h *Head[T]) RemoveTail() IHead[T] {  
    if h.IsEmpty() {  
        return h  
    }  

    if h.length == 1 {  
        h.start = nil  
        h.curr = nil  
        h.end = nil  
        h.length = 0  
        return h  
    }  

    prev := h.start  
    for i := uint64(1); i < h.Length()-1; i++ {  
        prev = prev.Next()  
    }  

    prev.SetNext(nil)  
    h.length--  
    return h  
}  
  
func (h *Head[T]) RemoveHead() IHead[T] {  
    if h.IsEmpty() {  
        return h  
    }  

    h.start = h.start.Next()  
    h.length--  
    return h  
}

基本使用

package main  
  
import (  
    "fmt"  

    "github.com/aide-cloud/slices/linked/singly"  
)  
  
func main() {  
    h := singly.New(singly.WithValues(1, 2, 3, 4, 5))  
    fmt.Println(h.Show()) 
    
    list := h.Slice()  
    fmt.Println(fmt.Sprintf("cap: %d, len: %d, %v", cap(list), len(list), list))  

    h.Append(6).Append(7).Append(8).Append(9).Append(10)  
    fmt.Println(h.Show())
    
    list = h.Slice()  
    fmt.Println(fmt.Sprintf("cap: %d, len: %d, %v", cap(list), len(list), list)) 
    
    h.Remove(1)  
    fmt.Println(h.Show())  

    h.RemoveValue(func(val int) bool {  
        return val >= 8  
    })  

    h.Prepend(100).Append(200).Append(300).Append(400).Append(500)  
    fmt.Println(h.Show()) 
    
    list = h.Slice()  
    fmt.Println(fmt.Sprintf("cap: %d, len: %d, %v", cap(list), len(list), list))  
}  

输出

(1)-->(2)-->(3)-->(4)-->(5)  
cap: 5, len: 5, [1 2 3 4 5]  
(1)-->(2)-->(3)-->(4)-->(5)-->(6)-->(7)-->(8)-->(9)-->(10)  
cap: 10, len: 10, [1 2 3 4 5 6 7 8 9 10]  
(1)-->(3)-->(4)-->(5)-->(6)-->(7)-->(8)-->(9)-->(10)  
(100)-->(1)-->(3)-->(4)-->(5)-->(6)-->(7)-->(200)-->(300)-->(400)-->(500)  
cap: 11, len: 11, [100 1 3 4 5 6 7 200 300 400 500]  

最后

内部还实现了一些其他的方法, 例如头插节点, 尾插节点等等, 感兴趣的同学可以通过仓库查看