IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Least Recently Used Algorithm

    klion26发表于 2014-11-02 04:50:18
    love 0

    LRU(Least Recently Used)算法是操作系统中的一种页面置换(在缓存系统中也会用到),思想就是:每次都把最近最少使用的那个页面置换出去,这个思想基于,当前使用的页面在不久的将来也会使用。

    比如在内存为 3 的情况下,依次请求如下页面2,3,4,2,1,3,7,5,4,3.那么内存中保存的依次保存的页面会变成如下所示(每一行表示当前页面请求之后,内存中的页面情况,左边的页面比右边页面旧(也就是最后一次访问的时间早),这里有一个动态视频,给出每一次的情况(需要翻墙)

    1.  2
    2.  2 3
    3.  2 3 4
    4.  3 4 2
    5.  4 2 1
    6.  2 1 3
    7.  1 3 7
    8.  3 7 5
    9.  7 5 4
    10.  5 4 3

    到这里基本想法就结束了,剩下的就是怎么实现的问题了。对于不同的要求,有不同的实现。

    第一种:最简单的模拟,用一个单链表表示 LRU 的大小,表头存最旧的页面,表尾存最新的页面,然后每次 get 和 put 的时候,都遍历一次单链表进行相应操作。由于每次都要遍历单链表,所以每次操作都是 O(L)的复杂度,其中 L 表示 LRU 的大小。代码如下

    typedef struct {
        int key;
        int val;
    } elem;
    class LRUCache{
    public:
        elem *arr;  // lru cache
        int sz; // total number of elements in the list currently.
        int cap; //capacity
        LRUCache(int capacity) {  //init LRUCache
            arr = new elem[capacity];  //
            sz = 0;
            cap = capacity;
     }
     /* move the used element to the end of list */
     void adjust(int a) {
         if (a == sz - 1) {//the last one
            return ;
         }
         elem cur = arr[a];
         for (int i = a; i < sz - 1; i ++) {
            arr[i] = arr[i + 1]; // move all elements after position a 1 step left
         }
         arr[sz - 1] = cur; // move arr[a] to the end
     }
     //get the value of key, return -1 if it doesn't exit
     int get(int key) {
         //iterate the whole list to find if the key exits
         for (int i = 0; i < sz; i ++) {
             if (arr[i].key == key) {
                int a = arr[i].val;
                adjust(i);
                return a; // existent key
             }
        }
        return -1;
     }
     //update the key/value
     void set(int key, int value) {
         for (int i = 0; i < sz; i ++) {
             if (arr[i].key == key) { // existent
                arr[i].val = value; //update value ,and adjust the list
                adjust(i);
                return;
             }
         }
         if (sz == cap) { // check if reach the capacity
             for (int i = 0; i < sz - 1; i ++) {
                 arr[i] = arr[i + 1]; // delete the least used element
             }
             arr[sz - 1].key = key;
             arr[sz - 1].val = value;
         } else {
             arr[sz].key = key;
             arr[sz].val = value;
             sz ++; // increase the size
         }
     }
    };

    第二种写法就是用双链表存 LRU 中保存的实际内容,然后用 HASH 表保存每一个 key 所对应的内容在双链表中的位置,其中双链表还是表头存最旧的,表尾存最新的,用 HASH 就可以加速查找,用双链表则是更新的时候可以达到 O(1)[单链表不能获得前驱节点的信息],如果这里用 map 实现,而不是 hash_map 的话,那么复杂度是 log(L),这个是由 map 的复杂度决定的。代码如下:

    #include 
    #include 
    #include 
    
    using namespace std;
    using namespace stdext;
    
    template
    struct LRUCacheEntry
    {
        K key;
        T data;
        LRUCacheEntry* prev;
        LRUCacheEntry* next;
    };
    
    template
    class LRUCache
    {
    private:
        hash_map< K, LRUCacheEntry* > _mapping;
        vector< LRUCacheEntry* > _freeEntries;
        LRUCacheEntry * head;
        LRUCacheEntry * tail;
        LRUCacheEntry * entries;
    public:
        LRUCache(size_t size){
        entries = new LRUCacheEntry[size];
        for (int i=0; i;
        tail = new LRUCacheEntry;
        head->prev = NULL;
        head->next = tail;
        tail->next = NULL;
        tail->prev = head;
     }
     ~LRUCache()
     {
        delete head;
        delete tail;
        delete [] entries;
     }
     void put(K key, T data)
     {
        LRUCacheEntry* node = _mapping[key];
        if(node)
          {
            // refresh the link list
            detach(node);
            node->data = data;
            attach(node);
          }
        else{
           if ( _freeEntries.empty() )
             {// lru cache is full
                 node = tail->prev;
                 detach(node);//delete a node
                 _mapping.erase(node->key);
                 node->data = data;
                 node->key = key;
                 _mapping[key] = node;
                 attach(node);//add the new node
             }
           else{
                 node = _freeEntries.back();
                 _freeEntries.pop_back();
                 node->key = key;
                 node->data = data;
                 _mapping[key] = node;
                 attach(node);
               }
           }
     }
    
     T get(K key)
     {
          LRUCacheEntry* node = _mapping[key];
          if(node)
            {//if node is already in, refresh the double-link-list
               detach(node);
               attach(node);
               return node->data;
            }
           else return NULL;
     }
    
    private:
        void detach(LRUCacheEntry* node)
        {// delete the node from the double-link-list
             node->prev->next = node->next;
             node->next->prev = node->prev;
        }
        void attach(LRUCacheEntry* node)
        {//add node to the head of double-link-list
             node->next = head->next;
             node->prev = head;
             head->next = node;
             node->next->prev = node;
        }
    };

    第二种方法利用双链表保存实际的 cache 内容,然后用 hash 来加速查找,hash 存的是每一个 key/value 的地址,这样就可以直接找到相应的 key/value 元素了。这种方法中,查找的复杂度是 O(1),更新的复杂度,只需要进行一次查找,一次 detach,一次 attach,所以也是 O(1)的,较之第一种方法的优势就体现出来了。

    最后,如果你想看下自己写的 LRU 是否正确,速度如何,可以在 Leetcode 上进行提交,地址:https://oj.leetcode.com/problems/lru-cache/,提交之后可以查看是否正确,正确的话,查看用时多少(第一种方法,可能可以在 Leetcode 上通过,也可能会得到一个 Time Limit Exceeded 的结果,这个就看你人品了)

    Reference

    Implement a LRU Cache in C++

    您可能也喜欢:

    有关链表逆序

    Too many levels of symbolic links

    C和指针12章 链表

    String to Integer (atoi)

    XP和Fedora的双系统之路
    无觅


沪ICP备19023445号-2号
友情链接