Go语言数据结构与算法-链表

单双向链表

示例代码:

container/list标准库实现
package main

import (
	"container/list"
	"fmt"
)

func TraversList(lst *list.List) {
	head := lst.Front()
	for head.Next() != nil {
		fmt.Printf("%v ", head.Value)
		head = head.Next()
	}
	fmt.Println(head.Value)
}

func ReverseList(lst *list.List) {
	tail := lst.Back()
	for tail.Prev() != nil {
		fmt.Printf("%v ", tail.Value)
		tail = tail.Prev()
	}
	fmt.Println(tail.Value)
}

func main() {
	lst := list.New()
	lst.PushBack(1)
	lst.PushBack(2)
	lst.PushBack(3)
	lst.PushBack(4)
	lst.PushBack(5)
	TraversList(lst)
	ReverseList(lst)

	_ = lst.Remove(lst.Back())  //移除尾元素,同时返回元素的值,注意元素不能是nil
	_ = lst.Remove(lst.Front()) //移除首元素,Remove操作复杂度为O(1)
	fmt.Printf("length %d\n\n", lst.Len())
	TraversList(lst)
}

>>>>>>
1 2 3 4 5
5 4 3 2 1
length 3

2 3 4
自己动手实现
package main

import "fmt"

// ListNode 链表节点元素
type ListNode struct {
	Value int       //该节点数据
	Prev  *ListNode //上一个节点
	Next  *ListNode //下一个节点
}

// DoubleList 双向链表
type DoubleList struct {
	Head   *ListNode //头节点
	Tail   *ListNode //尾节点
	Length int       //节点数量
}

// NewDoubleList 初始化链表
func NewDoubleList() *DoubleList {
	return &DoubleList{}
}

// Append 追加到链表尾部
func (l *DoubleList) Append(i int) {
	n := &ListNode{Value: i}
	t := l.Tail
	if t == nil {
		l.Head = n
		l.Tail = n
	} else {
		t.Next = n
		n.Prev = t
		l.Tail = n
	}
	l.Length += 1
}

// Get 获取链表中id下标的节点
func (l *DoubleList) Get(index int) *ListNode {
	if l.Length <= index {
		return nil
	}
	pres := l.Head
	for i := 0; i < index; i++ {
		pres = pres.Next
	}
	return pres
}

// InsertAfter 链表插入节点
func (l *DoubleList) InsertAfter(i int, pNode *ListNode) {
	n := &ListNode{Value: i}
	if pNode.Next == nil {
		pNode.Next = n
		n.Prev = pNode
	} else {
		nNode := pNode.Next
		nNode.Prev = n
		n.Prev = pNode
		n.Next = nNode
		pNode.Next = n
	}
	l.Length += 1
}

// Traverse 链表正向值
func (l *DoubleList) Traverse() {
	pres := l.Head
	for pres != nil {
		fmt.Printf("%d ", pres.Value)
		pres = pres.Next
	}
	fmt.Println()
}

// Reverse 链表反向值
func (l *DoubleList) Reverse() {
	t := l.Get(l.Length-1)
	for t != nil {
		fmt.Printf("%d ", t.Value)
		t = t.Prev
	}
	fmt.Println()
}
func main() {
	l := NewDoubleList()
	l.Append(1)
	l.Append(2)
	l.Append(3)
	l.Append(4)
	l.Append(5)
	l.Traverse()
	gNode := l.Get(3)
	l.InsertAfter(9, gNode)
	l.Traverse()
	l.Reverse()
}


>>>>>>>
1 2 3 4 5 
1 2 3 4 9 5 
5 9 4 3 2 1 

应用:

LRU缓存淘汰
  1. LUR(Least Recently Used)最近最少使用【冷数据】
  2. 思路:缓存的key放到链表中,头部的元素表示最近刚使用
    • 如果命中缓存,从链表中找到对应的key,移到链表头部
    • 如果没有命中缓存:
      • 如果缓存容量没超,放入缓存,并把key放到链表头部
      • 如果超出缓存容量,删除链表尾部元素,再把key放到链表头部
示例代码:

模拟LRU缓存淘汰机制:

package main

import (
	"container/list"
	"fmt"
)

var cache map[int]string
var lst *list.List

const CAP = 10 //缓存容量上限

func init() {
	cache = make(map[int]string, CAP)
	lst = list.New()
}

func readFromDisk(key int) string {
	return "china"
}

func read(key int) string {
	if v, exists := cache[key]; exists { //命中缓存
		head := lst.Front() //链表内第一个元素
		notFound := false
		for {
			if head == nil {
				notFound = true
				break
			}
			if head.Value.(int) == key { //从链表内找到对应的key
				lst.MoveToFront(head) //把key移到链表头部
				break
			} else {
				head = head.Next()
			}
		}
		if notFound { //正常情况下不会发生这种情况
			lst.PushFront(key)
		}
		return v
	} else { //没有命中缓存
		v = readFromDisk(key) //从磁盘中读取数据
		cache[key] = v        //放入缓存
		lst.PushFront(key)    //放入链表头部
		if len(cache) > CAP { //缓存已满
			tail := lst.Back()              //链表最后一个元素
			delete(cache, tail.Value.(int)) //从缓存中移除很久不使用的元素
			lst.Remove(tail)                //从链表中删除最后一个元素
			fmt.Printf("remove %d from cache\n", tail.Value.(int))
		}
		return v
	}
}
func TraversList(lst *list.List) {
	head := lst.Front()
	for head.Next() != nil {
		fmt.Printf("%v ", head.Value)
		head = head.Next()
	}
	fmt.Println(head.Value)
}

func main() {
	for i := 1; i <= 12; i++ {
		_ = read(i)
	}
	for k, v := range cache {
		fmt.Printf("%d:%s\n", k, v)
	}
	_ = read(1)
	_ = read(5)
	for k, v := range cache {
		fmt.Printf("%d:%s\n", k, v)
	}
	fmt.Println("-------")
	TraversList(lst)
}

>>>>>>
remove 1 from cache
remove 2 from cache
6:china
7:china
8:china
10:china
12:china
3:china
5:china
11:china
4:china
9:china
remove 3 from cache
6:china
7:china
8:china
10:china
12:china
1:china
5:china
11:china
4:china
9:china
-------
5 1 12 11 10 9 8 7 6 4

单双向循环链表

添加元素

package main

import (
	"container/ring"
	"fmt"
)

func TraverseRing(ring *ring.Ring) {
	//通过Do()来遍历ring,内部实际上调用了Next()而非Prev()
	ring.Do(func(i interface{}) {
		fmt.Printf("%v ", i)
	})
	fmt.Println()
}

func main() {
	//必须指定长度,各元素被初始化为nil
	r := ring.New(5)
	r2 := r.Prev()
	for i := 0; i < 3; i++ {
		r.Value = i
		r = r.Next()
	}
	for i := 0; i < 3; i++ {
		r2.Value = i
		r2 = r2.Prev()
	}

	// r和r2当前所在的指针位置不同,所以遍历出来的顺序也不同
	TraverseRing(r)
	TraverseRing(r2)
}

>>>>
1 0 0 1 2 
1 2 1 0 0

应用:

基于滑动窗口的统计:

  • 最近100次接口调用的平均耗时
  • 最近10笔订单的平均值
  • 最近30个交易日股票的最高点

ring的容量即为滑动窗口的大小、把待观察变量按时间顺序不停地写入ring即可

posted @ 2022-02-16 23:13  自己有自己的调调、  阅读(97)  评论(0编辑  收藏  举报