链表

链表与数组的对比

  • 存储
    • 数组需要一块连续的内存空间来存储
    • 链表不需要一块连续的内存空间,而是通过“指针”将一组零散的内存块串联起来

链表结构

单链表

  • 结点
    • 链表的每个结点,除了存储数据,还需要记录链上下一个结点的地址
  • 后继指针 next
    • 记录下一个结点地址的指针叫做后继指针
  • 头节点
    • 记录链表的基地址,可以通过基地址遍历得到整条链表
  • 尾结点
    • 尾结点的指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链上的最后一个节点

链表的操作

  • 支持数据的查找、插入和删除操作
    • 在链表中插入和删除一个数据的时间复杂度是O(1)
    • 链表查找需要O(n)的时间复杂度

双向链表

双向链表是一种特殊的单链表。跟单链表的区别是尾结点。
单链表的尾结点指向空地址,表示为最后的节点。
循环链表的尾结点指针指向链表的头节点。

循环链表

单链表只有一个方向,节点只有一个后继指针指向后面的节点。
双向链表有两个方向,每个结点除了一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的节点。

应用场景

LRU缓存淘汰算法

  • 链表的一个经典应用场景是LRU缓存淘汰算法
    缓存淘汰算法用来解决在有限大小的缓存内,当缓存用满时,数据的清理策略。常见的缓存淘汰策略有三种:

    • 先进先出策略FIFO(First In, First Out)
    • 最少使用策略LFU(Least Frequently Used)
    • 最近最少使用策略LRU(Least Recently Used)
  • 我们通过力扣上的一道题来看如何应用
    Leetcode LRU缓存机制

  • GO实现

package main

import "fmt"

//LRU HashLink
type LRUCache struct {
    hashmap map[int]*LinkNode
    LinkList LinkList
}

//链表
type LinkList struct {
    head *LinkNode//指向第一个节点,通过遍历节点next到所有节点
    tail *LinkNode//指向最后一个节点,通过遍历prev到所有节点
    size int
    capacity int
}

//链表节点
type LinkNode struct {
    key int//对应map中的key
    value int
    prev *LinkNode
    next *LinkNode
}

func Constructor(capacity int) LRUCache {
    var LRUCache LRUCache
    LRUCache.InitLink(capacity)
    return LRUCache
}

func (this *LRUCache) Get(key int) int {
    if(this.hashmap[key]==nil) {
        return -1
    }
    //放到最新
    this.MakeNodeRecenty(key)
    return this.hashmap[key].value
}

func (this *LRUCache) Put(key int, value int)  {
    if(this.hashmap[key]!=nil) {
        this.RemoveLink(key)
    }
    this.AddLink(key,value)
}

//初始化链表
func (l *LRUCache) InitLink(capacity int) {
    l.hashmap = make(map[int]*LinkNode)

    head := &LinkNode{}
    tail := &LinkNode{}

    l.LinkList.head = head
    l.LinkList.tail = tail

    l.LinkList.head.next = tail
    l.LinkList.tail.prev = head

    l.LinkList.size = 0
    l.LinkList.capacity = capacity
}

//添加链表节点
//添加到链表尾部
//尾部节点即是最新的节点
func (l *LRUCache) AddLink(key int, value int) {
    //存储已满,删除旧的一个节点
    if(l.LinkList.size>=l.LinkList.capacity) {
        l.RemoveLastLink()
    }

    newNode := &LinkNode{}
    newNode.key = key
    newNode.value = value
    l.LinkList.size++
    l.hashmap[key] = newNode
}

//将节点放到最新位置
func (l *LRUCache) MakeNodeRecenty(key int) {
    node := l.hashmap[key]
    value := node.value
    l.RemoveLink(key)
    l.AddLink(key, value)
}

//删除指定key
func (l *LRUCache) RemoveLink(key int) {
    node := l.hashmap[key]

    node.prev.next = node.next
    node.next.prev = node.prev

    l.hashmap[key] = nil
    l.LinkList.size--
}

//删除最旧的节点
//最旧的节点即是头部节点
func (l *LRUCache) RemoveLastLink() {
    node := l.LinkList.head.next
    key := node.key
    node.next.prev = node.prev
    node.prev.next = node.next
    l.hashmap[key] = nil
    l.LinkList.size--
}

func main() {
    obj := Constructor(1)
    obj.Put(2,1)
    param_1 := obj.Get(2)
    obj.Put(3,2)
    param_2 := obj.Get(2)
    param_3 := obj.Get(3)


    fmt.Printf("%v", param_1)
    fmt.Printf("%v", param_2)
    fmt.Printf("%v", param_3)
}

参考

链表_极客时间
大话数据结构