• 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即可

  • 相关阅读:
    mysql权限管理
    centos 6.5安装node.js
    sublime 配置jade高亮显示
    解决国内npm依赖包安装慢的问题
    sublime text3配置node.js开发环境
    datepicker 时间戳转换问题
    关于c++正则表达式的用法
    Android系统binder机制的研究分析
    TCP/IP的分层复用问题
    关于设计模式中外观模式的研究以及关于设计模式中四大原则的理解
  • 原文地址:https://www.cnblogs.com/remixnameless/p/15902784.html
Copyright © 2020-2023  润新知