LRU算法O(1)复杂度

请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。

在请求分页系统中,只要求将当前需要的一部分页面装入内存,以便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存时,再通过雕爷功能将其调入,同时还可以通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。

页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。

常见的页面置换算法有以下几种:

1、最佳置换算法(Optimal,OPT)

2、先进先出页面置换算法(First In First Out,FIFO)

3、最近最久未使用( Least Recently Used,LRU)置换算法

4、最不常用算法(LFU,Least Frequently Used)

5、时钟算法(clock)、改进时钟算法1、最佳置换算法(Optimal,OPT)

目录

1、最佳置换算法(Optimal,OPT)

2、先进先出页面置换算法(First In First Out,FIFO)

3、最近最久未使用( Least Recently Used,LRU)置换算法

4、最不常用算法(LFU,Least Frequently Used)


1、最佳置换算法(Optimal,OPT)

所选择的被换出的页面将是以后永不使用的,或者是在最长时间内不再被访问,通常可以保证获得最低的缺页率。但是由于人们无法预知一个页面多长时间不再被访问,因此该算法是一种理论上的算法。最佳置换算法可以用来评价其他算法。

2、先进先出页面置换算法(First In First Out,FIFO)

选择换出的页面是最先进入的页面。该算法实现简单,但会将那些经常被访问的页面也被换出,从而使缺页率升高。

3、最近最久未使用( Least Recently Used,LRU)置换算法

虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出,它认为过去一段时间内未使用的页面,在最近的将来也不会被访问。

实现方式一:

在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面时最近最久未访问的。

实现方式二:

为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。

4、最不常用算法(LFU,Least Frequently Used)


是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。

这种算法选择最近时期使用次数最少的页作为淘汰页。为每个页面配置一个计数器,一旦某页被访问,则将其计数器的值加1,在需要选择一页置换时,则将选择其计数器值最小的页面,即内存中访问次数最少的页面进行淘汰。

这种算法可能存在的问题是:有些也在进程开始时被访问的次数很多,但以后这些页可能不再被访问,这样的页不该长时间停留在内存中。解决这个问题的方法之一是定期的将计时器右移,以形成指数衰减的平均使用次数。

注意LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。

5.抖动

6、LRU实现O(1)实现

题目链接:链接:https://leetcode-cn.com/problems/lru-cache/

已知:

java的数据结构时间复杂度。

栈 Stack

压栈:O(1)
出栈:O(1)
栈顶:O(1)
查找:O(n)

队列 Queue/Deque/Circular Queue

插入:O(1)
移除:O(1)
求长:O(1)

这里可以选择双端队列deque

对于 get 操作,首先判断 key 是否存在:如果 key 不存在,则返回 -1;

                         如果 key 存在,根据hashmap返回value,并将其提到队首。(双端队列不区分队首队尾,假定addFirst为队首)

对于 put 操作,首先判断 key 是否存在:如果 key 不存在,hashmap ,put进去。双端队列队首插入key。

                           然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的key;

                       如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到队首。

public class LRUCache {

    private Map<Integer, Integer> cache = new HashMap<Integer, Integer>(); // 用来存放key-value对,cache缓存
    private Deque<Integer> deque = new LinkedList<>();                       // LRU算法用来淘汰最近最久未使用
    private int capacity;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
    }

    public int get(int key) {
        if (!cache.containsKey(key)) return -1;

        deque.remove(key);                  // 注意:deque里面remove(Object o) 如果是list,remove有多态要用:remove(Integer.valueOf(key))
        deque.addFirst(key);
        return cache.get(key);              // 如果 key 存在,先通过哈希表定位
    }

    public void put(int key, int value) {
        if (!cache.containsKey(key)) {
            cache.put(key, value);
            deque.addFirst(key);
            if (deque.size() > capacity) {
                Integer tmpNode1 = deque.removeLast();
                cache.remove(tmpNode1);
            }
        } else {
            cache.put(key, value);
            deque.remove(key);
            deque.addFirst(key);
        }
    }

}

对于 get 操作,首先判断 key 是否存在:如果 key 不存在,则返回 -1;

                         如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。

对于 put 操作,首先判断 key 是否存在:如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。

                           然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;

                       如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。

class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;

        public DLinkedNode() {
        }

        public DLinkedNode(int _key, int _value) {
            key = _key;
            value = _value;
        }
    }
public class LRUCache {
    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode newNode = new DLinkedNode(key, value);
            // 添加进哈希表
            cache.put(key, newNode);
            // 添加至双向链表的头部
            addToHead(newNode);
            if (cache.size() > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail = removeTail();
                // 删除哈希表中对应的项
                cache.remove(tail.key);
            }
        } else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}

猜你喜欢

转载自blog.csdn.net/yu1336199790/article/details/114833195
今日推荐