zoukankan      html  css  js  c++  java
  • 数据结构与算法之美 06 | 链表(上)-如何实现LRU缓存淘汰算法


    常见的缓存淘汰策略:
    先进先出 FIFO
    最少使用LFU(Least Frequently Used)
    最近最少使用 LRU(Least Recently Used)


    链表定义:
    链表也是线性表的一种,
    数组需要一块连续的内存空间来存储,对内存要求比较高,
    链表恰恰相反,它并不需要一块连续的内存空间,它通过"指针"将一组零散的内存块
    串联起来使用。

    最常见的链表结构:
    单链表
    双向链表
    循环链表

    用空间换时间:
    当内存空间充足的时候,如果更加追求代码的执行速度,可以选择空间复杂度相对较高、
    但时间复杂度相对很低的算法或数据结构。

    链表 vs 数组性能
    数组 链表
    插入删除 O(n) O(1)
    随机访问 O(1) O(n)


    如果基于链表实现LRU缓存淘汰算法?
    思路: 维护一个有序单链表,越靠近链表尾部的结点是越早之间访问的数据,
    当有一个新的数据被访问时,从链表头开始顺序遍历链表。

    1. 如果此数据之前已经被缓存在链表中,可以遍历得到这个数据对应的结点,
    并将其从原来的位置删除,然后再插入到链表的头部。

    2. 如果此数据没有在缓存链表中,又可以分为两种情况:
    如果此时缓存未满,则将此结点直接插入到链表的头部;
    如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。

    缓存访问时间复杂度: 因为不管缓存有没有满,都需要遍历一遍链表,因此时间复杂度为O(n)
    可以通过"散列表(Hash table)"来记录每个数据的位置,将缓存访问的时间复杂度降低为O(1)


    内容小结:
    链表是跟数组"相反"的数据结构,它跟数组一样,也是非常基础、常用的数据结构。
    不过链表比数组稍微复杂。

    从普通的单链表衍生出 双向链表、循环链表、双向循环链表

    和数组相比,链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高。


    基于Python语言实现的单链表
    # 定义链表节点
    class Node(object):
        def __init__(self, data, n=None):
            self.data = data
            self.next = n
    
    
    # 定义链表及其增删改查
    class LinkList(object):
    
        def __init__(self):
            # 初始化空链表
            self.head = None
            self.tail = None
            self.length = 0
    
        def is_empty(self):
            # 判断链表是否为空
            return self.length == 0
    
        def append(self, dataOrNode):
            """
            在尾部添加数据
            :param dataOrNode: Data or Node obj
            :return: True or None
            """
            # 判断是一个数据还是Node对象
            if isinstance(dataOrNode, Node):
                item = dataOrNode
            else:
                item = Node(dataOrNode)
    
            if self.length == 0:
                # 判断是一个空链表, 直接赋值
                self.head = item
            else:
                # 将旧尾部节点的next指向新增加的数据
                old_tail = self.tail
                old_tail.next = item
    
            self.tail = item
            self.length += 1
            return True
    
        def delete(self, index):
            """
            删除指定位置的数据
            :param index: 位置
            :return: True or False
            """
            if self.is_empty():
                print("this chain table is empty.")
                return False
    
            if index < 0 or index >= self.length:
                print("error: out of index.")
                return False
    
            if index == 0:
                # 直接删除第一个数据
                self.head = self.head.next
                self.length -= 1
                return True
            else:
                j = 0
                node = self.head
                prev = self.head
                # 从头开始遍历,遍历到指定位置,然后删除数据
                while node.next and j < index:
                    prev = node
                    node = node.next
                    j += 1
    
                if j == index:
                    prev.next = node.next
                    self.length -= 1
                    return True
                if index == self.length - 1:
                    self.tail = prev
    
        def insert(self, index, dataOrNode):
            """
            在指定位置插入数据
            :param index: 位置
            :param dataOrNode: Data or Node Obj
            :return: True or False
            """
            if self.is_empty():
                print("this chain table is empty")
                return False
    
            if index < 0 or index >= self.length:
                print("error: out of index")
                return False
    
            if isinstance(dataOrNode, Node):
                item = dataOrNode
            else:
                item = Node(dataOrNode)
    
            if index == 0:
                # 在首部直接插入数据
                item.next = self.head
                self.head = item
                self.length += 1
            else:
                j = 0
                node = self.head
                prev = self.head
                # 从头开始遍历,遍历到指定位置,然后插入数据
                while node.next and j < index:
                    prev = node
                    node = node.next
                    j += 1
                if j == index:
                    item.next = node
                    prev.next = item
                    self.length += 1
            return True
    
        def update(self, index, data):
            """
            更新指定位置的数据
            :param index: 位置
            :param data: 数据
            :return: True or False
            """
            if self.is_empty() or index < 0 or index >= self.length:
                print("error: out of index")
                return False
    
            j = 0
            node = self.head
            # 从头开始遍历,遍历到指定位置,然后更新数据
            while node.next and j < index:
                node = node.next
                j += 1
    
            if j == index:
                node.data = data
                return True
            return False
    
        def get_item(self, index):
            """
            获取指定位置的数据
            :param index:
            :return:
            """
            if self.is_empty() or index < 0 or index >= self.length:
                print("error: out of index")
                return
    
            j = 0
            node = self.head
            while node.next and j < index:
                node = node.next
                j += 1
    
            if j == index:
                return node.data
    
        def clear(self):
            """
            删除所有数据
            :return:
            """
            self.head = None
            self.length = 0
            return True
    
        def __len__(self):
            return self.length
    
        def __getitem__(self, item):
            # 使用[]获取实例属性 如obj[item], python会自动调用__getitem__方法;
            return self.get_item(item)
    
        def __setitem__(self, key, value):
            # 使用[]设置实例属性 如obj[key] = value, python会自动调用__setitem__方法;
            return self.update(key, value)
    
    
    if __name__ == '__main__':
        link = LinkList()
        for i in range(5):
            link.append(i)
    
        print("初始化后,链表长度为:", len(link))
        for i in range(len(link)):
            print("初始化数据:", link.get_item(i))
    
        print("删除指定位置数据:", link.delete(0))
    
        print("删除指定数据后,链表长度为:", len(link))
        for i in range(len(link)):
            print("删除后的数据为:", link.get_item(i))
    
        print("指定位置插入数据:", link.insert(1, 100))
    
        print("插入数据后的链表长度:", len(link))
        for i in range(len(link)):
            print("插入后的数据:", link.get_item(i))
    
        print("更新指定数据", link.update(1, 200))
    
        # 更新数据
        link[1] = 100
    
        # 获取数据
        print(link[1])
    View Code

    基于Go语言实现的单链表
    package main
    
    import (
        "fmt"
    )
    
    type Object interface {
    }
    
    // 定义节点
    type Node struct {
        data Object
        next *Node
    }
    
    // 定义单向链表
    type List struct {
        head *Node
        tail *Node
        size uint64
    }
    
    // 初始化链表
    func (list *List) Init() {
        (*list).size = 0   // 此时链表是空的
        (*list).head = nil // 没有头
        (*list).tail = nil // 没有尾
    }
    
    // 向尾部添加数据
    func (list *List) Append(node *Node) bool {
        if node == nil {
            return false
        }
    
        // 将尾部的next设置为空
        (*node).next = nil
    
        // 将新元素放入单链表中
        if (*list).size == 0 {
            (*list).head = node
        } else {
            // 将旧尾部数据的next指向新的数据
            oldTail := (*list).tail
            (*oldTail).next = node
        }
    
        // 调整尾部位置及链表元素数量
        (*list).tail = node // node成为新的尾部
        (*list).size ++     // 元素数量增加
        return true
    }
    
    // 插入数据
    func (list *List) Insert(i uint64, node *Node) bool {
        // 空的节点、索引超出范围和空链表都无法做插入操作
        if node == nil || i > (*list).size || (*list).size == 0 {
            return false
        }
    
        if i == 0 {
            // 直接排在第一
            (*node).next = (*list).head
            (*list).head = node
        } else {
            // 找前一个元素
            preItem := (*list).head
            for j := 1; uint64(j) < i; j++ {
                // 数前面i个元素
                preItem = (*preItem).next
            }
            // 原有元素放到新元素后面,新元素放到前一个元素后面
            (*node).next = (*preItem).next
            (*preItem).next = node
        }
    
        (*list).size ++
        return true
    }
    
    // 删除元素
    func (list *List) Remove(i uint64, node *Node) bool {
        if i >= (*list).size {
            return false
        }
        if i == 0 {
            node = (*list).head
            (*list).head = (*node).next
            if (*list).size == 1 {
                (*list).tail = nil
            }
        } else {
            preItem := (*list).head
            for j := 1; uint64(j) < i; j++ {
                preItem = (*preItem).next
            }
            node = (*preItem).next
            (*preItem).next = (*node).next
    
            if i == ((*list).size - 1) {
                (*list).tail = preItem
            }
        }
        (*list).size --
        return true
    }
    
    // 获取元素
    func (list *List) Get(i uint64) *Node {
        if i >= (*list).size {
            return nil
        }
    
        item := (*list).head
        for j := 0; uint64(j) < i; j++ {
            item = (*item).next
        }
        return item
    }
    
    func main() {
    
        // 初始化长度为100的空链表
        var list = List{}
        list.Init()
        for i := 1; i <= 100; i++ {
            var node = Node{data: i}
            list.Append(&node)
        }
    
        var node = list.Get(35)
        fmt.Printf("Current node position: %d, data: %d
    ", node, node.data)
        var deleteNode = &Node{}
        result := list.Remove(35, deleteNode)
        fmt.Printf("Delete result: %+v 
    ", result)
    
        var node2 = list.Get(35)
        fmt.Printf("Current node position: %p, data: %d
    ", node2, node2.data)
    
        newNode := Node{data: 100}
        result2 := list.Insert(34, &newNode)
        fmt.Printf("Insert result: %+v 
    ", result2)
    
        var node3 = list.Get(34)
        fmt.Printf("Current node position: %p, data: %d
    ", node3, node3.data)
    
    
        fmt.Printf("Head: %d, Tail: %d", list.head.data, list.tail.data)
    }
    View Code
  • 相关阅读:
    vue 使用print.js实现前端打印功能
    lin UI微信小程序组件库
    将博客搬至CSDN
    第20节:Java集合框架 【多测师_王sir】
    第19节:Java三大特性-多态之接口 【多测师_王sir】
    第18节:Java练习题 【多测师_王sir】
    第17节:Java三大特性-继承之重写 【多测师_王sir】
    第16节:Java练习题【多测师_王sir】
    第15节:Java三大特性【多测师_王sir】
    第14节:Java练习题【多测师_王sir】
  • 原文地址:https://www.cnblogs.com/vincenshen/p/9741715.html
Copyright © 2011-2022 走看看