LRU Cache


LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

  • 題目大意:為LRU Cache設計一個數據結構,它支持兩個操作:

   1)get(key):如果key在cache中,則返回對應的value值,否則返回-1

   2)set(key,value):如果key不在cache中,則將該(key,value)插入cache中(注意,如果cache已滿,則必須把最近最久未使用的元素從cache中刪除);如果key在cache中,則重置value的值。

  • 解題思路:題目讓設計一個LRU Cache,即根據LRU算法設計一個緩存。在這之前需要弄清楚LRU算法的核心思想,LRU全稱是Least

Recently Used,即最近最久未使用的意思。在操作系統的內存管理中,有一類很重要的算法就是內存頁面置換算法(包括FIFO,LRU,LFU等幾種常見頁面置換算法)。事實上,Cache算法和內存頁面置換算法的核心思想是一樣的:都是在給定一個限定大小的空間的前提下,設計一個原則如何來更新和訪問其中的元素。下面說一下LRU算法的核心思想,LRU算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那么在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。

  而用什么數據結構來實現LRU算法呢?可能大多數人都會想到:用一個數組來存儲數據,給每一個數據項標記一個訪問時間戳,每次插入新數據項的時候,先把數組中存在的數據項的時間戳自增,並將新數據項的時間戳置為0並插入到數組中。每次訪問數組中的數據項的時候,將被訪問的數據項的時間戳置為0。當數組空間已滿時,將時間戳最大的數據項淘汰。

  這種實現思路很簡單,但是有什么缺陷呢?需要不停地維護數據項的訪問時間戳,另外,在插入數據、刪除數據以及訪問數據時,時間復雜度都是O(n)。

  那么有沒有更好的實現辦法呢?

  那就是利用鏈表和hashmap。當需要插入新的數據項的時候,如果新數據項在鏈表中存在(一般稱為命中),則把該節點移到鏈表頭部,如果不存在,則新建一個節點,放到鏈表頭部,若緩存滿了,則把鏈表最后一個節點刪除即可。在訪問數據的時候,如果數據項在鏈表中存在,則把該節點移到鏈表頭部,否則返回-1。這樣一來在鏈表尾部的節點就是最近最久未訪問的數據項。

  總結一下:根據題目的要求,LRU Cache具備的操作:

  1)set(key,value):如果key在hashmap中存在,則先重置對應的value值,然后獲取對應的節點cur,將cur節點從鏈表刪除,並移動到鏈表的頭部;若果key在hashmap不存在,則新建一個節點,並將節點放到鏈表的頭部。當Cache存滿的時候,將鏈表最后一個節點刪除即可。

  2)get(key):如果key在hashmap中存在,則把對應的節點放到鏈表頭部,並返回對應的value值;如果不存在,則返回-1。

  仔細分析一下,如果在這地方利用單鏈表和hashmap,在set和get中,都有一個相同的操作就是將在命中的節點移到鏈表頭部,如果按照傳統的遍歷辦法來刪除節點可以達到題目的要求么?第二,在刪除鏈表末尾節點的時候,必須遍歷鏈表,然后將末尾節點刪除,這個能達到題目的時間要求么?

  試一下便知結果:

  第一個版本實現:

#include <iostream>
#include <map>
#include <algorithm>
using namespace std;

struct Node
{
	int key;
	int value;
	Node *next;	
};


class LRUCache{
private:
	int count;
	int size ;
	map<int,Node *> mp;
	Node *cacheList;
public:	
    LRUCache(int capacity) {
      size = capacity;
      cacheList = NULL;
      count = 0;
    }
    
    int get(int key) {
    	if(cacheList==NULL)
    		return -1;
    	map<int,Node *>::iterator it=mp.find(key);
        if(it==mp.end())  //如果在Cache中不存在該key, 則返回-1 
        {
        	return -1;	
        }
        else
        {
        	Node *p = it->second;	
        	pushFront(p);    //將節點p置於鏈表頭部 
        }
        return cacheList->value;   
    }
    
    void set(int key, int value) {
    	if(cacheList==NULL)   //如果鏈表為空,直接放在鏈表頭部 
    	{
	    	cacheList = (Node *)malloc(sizeof(Node));
	    	cacheList->key = key;
	    	cacheList->value = value;
	    	cacheList->next = NULL;
	    	mp[key] = cacheList;
	    	count++;
	    }
	    else   //否則,在map中查找 
	    {
    		map<int,Node *>::iterator it=mp.find(key);
    		if(it==mp.end())   //沒有命中 
    		{
    			if(count == size)  //cache滿了
				{
					Node * p = cacheList;
					Node *pre = p;
					while(p->next!=NULL)
					{
						pre = p;
						p= p->next;	
					}
					mp.erase(p->key);
					count--;
					if(pre==p)         //說明只有一個節點 
						p=NULL;	
					else
						pre->next = NULL;
					free(p);
				} 
				Node * newNode = (Node *)malloc(sizeof(Node));	
				newNode->key = key;
				newNode->value = value;
				
				newNode->next = cacheList;
				cacheList = newNode;
   				
   				mp[key] = cacheList;
	    		count++;	
		    }
		    else
		    {
    			Node *p = it->second;	
    			p->value = value;
    			pushFront(p);
    		}
    	}
    	
    }
    
    void pushFront(Node *cur)   //單鏈表刪除節點,並將節點移動鏈表頭部,O(n) 
    {
    	if(count==1)
    		return;
   		if(cur==cacheList)
   			return;
   		Node *p = cacheList;
   		while(p->next!=cur)
   		{
			p=p->next;   	
	   	}
	   	p->next = cur->next;   //刪除cur節點
		   
		cur->next = cacheList;
		cacheList = cur; 
    }
    
    void printCache(){
    	
    	Node *p = cacheList;
    	while(p!=NULL)
    	{
	    	cout<<p->key<<" ";
	    	p=p->next;
	    }
	    cout<<endl;
    }
};


int main(void)
{
	/*LRUCache cache(3);
	cache.set(2,10);
	cache.printCache();
	cache.set(1,11);
	cache.printCache();
	cache.set(2,12);
	cache.printCache();
	cache.set(1,13);
	cache.printCache();
	cache.set(2,14);
	cache.printCache();
	cache.set(3,15);
	cache.printCache();
	cache.set(4,100);
	cache.printCache();
	cout<<cache.get(2)<<endl;
	cache.printCache();*/
	
	LRUCache cache(2); 
	cout<<cache.get(2)<<endl;
	cache.set(2,6);
	cache.printCache();
	cout<<cache.get(1)<<endl;
	cache.set(1,5);
	cache.printCache();
	cache.set(1,2);
	cache.printCache();
	cout<<cache.get(1)<<endl;
	cout<<cache.get(2)<<endl;
	return 0;
}

  提交之后,提示超時:

  因此要對算法進行改進,如果把pushFront時間復雜度改進為O(1)的話是不是就能達到要求呢?

  但是  在已知要刪除的節點的情況下,如何在O(1)時間復雜度內刪除節點?

  如果知道當前節點的前驅節點的話,則在O(1)時間復雜度內刪除節點是很容易的。而在無法獲取當前節點的前驅節點的情況下,能夠實現么?對,可以實現的。

  具體的可以參照這幾篇博文:

  http://www.cnblogs.com/xwdreamer/archive/2012/04/26/2472102.html

  http://www.nowamagic.net/librarys/veda/detail/261

   原理:假如要刪除的節點是cur,通過cur可以知道cur節點的后繼節點curNext,如果交換cur節點和curNext節點的數據域,然后刪除curNext節點(curNext節點是很好刪除地),此時便在O(1)時間復雜度內完成了cur節點的刪除。

  改進版本1:

void pushFront(Node *cur)  //單鏈表刪除節點,並將節點移動鏈表頭部,O(1) 
    {
    	if(count==1)
    		return;
    	//先刪除cur節點 ,再將cur節點移到鏈表頭部 
    	Node *curNext = cur->next;
		if(curNext==NULL)  //如果是最后一個節點 
		{
			Node * p = cacheList;
			while(p->next!=cur)
			{
				p=p->next; 
			}
			p->next = NULL;	
			
			cur->next = cacheList;
			cacheList = cur;
		} 
		else    //如果不是最后一個節點 
		{
			cur->next = curNext->next;	
			int tempKey = cur->key;
			int tempValue = cur->value;
			
			cur->key = curNext->key;
			cur->value = curNext->value;
			
			curNext->key = tempKey;
			curNext->value = tempValue;
			
			curNext->next = cacheList;
			cacheList  = curNext;
			
			mp[curNext->key] = curNext;
			mp[cur->key] = cur;
		}
    }
    

  提交之后,提示Accepted,耗時492ms,達到要求。

   有沒有更好的實現辦法,使得刪除末尾節點的復雜度也在O(1)?那就是利用雙向鏈表,並提供head指針和tail指針,這樣一來,所有的操作都是O(1)時間復雜度。

  改進版本2:

 

#include <iostream>
#include <map>
#include <algorithm>
using namespace std;

struct Node
{
	int key;
	int value;
	Node *pre;
	Node *next;	
};


class LRUCache{
private:
	int count;
	int size ;
	map<int,Node *> mp;
	Node *cacheHead;
	Node *cacheTail;
public:	
    LRUCache(int capacity) {
      size = capacity;
      cacheHead = NULL;
      cacheTail = NULL;
      count = 0;
    }
    
    int get(int key) {
    	if(cacheHead==NULL)
    		return -1;
    	map<int,Node *>::iterator it=mp.find(key);
        if(it==mp.end())  //如果在Cache中不存在該key, 則返回-1 
        {
        	return -1;	
        }
        else
        {
        	Node *p = it->second;	
        	pushFront(p);    //將節點p置於鏈表頭部 
        }
        return cacheHead->value;   
    }
    
    void set(int key, int value) {
    	if(cacheHead==NULL)   //如果鏈表為空,直接放在鏈表頭部 
    	{
	    	cacheHead = (Node *)malloc(sizeof(Node));
	    	cacheHead->key = key;
	    	cacheHead->value = value;
	    	cacheHead->pre = NULL;
	    	cacheHead->next = NULL;
	    	mp[key] = cacheHead;
	    	cacheTail = cacheHead;
	    	count++;
	    }
	    else   //否則,在map中查找 
	    {
    		map<int,Node *>::iterator it=mp.find(key);
    		if(it==mp.end())   //沒有命中 
    		{
    			if(count == size)  //cache滿了
				{
					if(cacheHead==cacheTail&&cacheHead!=NULL)  //只有一個節點 
					{
						mp.erase(cacheHead->key);
						cacheHead->key = key;
						cacheHead->value = value;
						mp[key] = cacheHead;
					}
					else
					{
						Node * p =cacheTail;
						cacheTail->pre->next = cacheTail->next;   
						cacheTail = cacheTail->pre;

						mp.erase(p->key);
					
						p->key= key;
						p->value = value;
					
						p->next = cacheHead;
						p->pre = cacheHead->pre;
						cacheHead->pre = p;
						cacheHead = p;
						mp[cacheHead->key] = cacheHead;
					}
				} 
				else
				{
					Node * p = (Node *)malloc(sizeof(Node));
					p->key = key;
					p->value = value;
					
					p->next = cacheHead;
					p->pre = NULL;
					cacheHead->pre = p;
					cacheHead = p;
					mp[cacheHead->key] = cacheHead;
					count++;
				}
		    }
		    else
		    {
    			Node *p = it->second;	
    			p->value = value;
    			pushFront(p);
    		}
    	}
    	
    }

    
    void pushFront(Node *cur)   //雙向鏈表刪除節點,並將節點移動鏈表頭部,O(1) 
    {
    	if(count==1)
    		return;
   		if(cur==cacheHead)
   			return;
			
		if(cur==cacheTail)
		{
			cacheTail = cur->pre;
		}
		
		cur->pre->next = cur->next;   //刪除節點
		if(cur->next!=NULL)
			cur->next->pre = cur->pre;
		 
		cur->next = cacheHead;
		cur->pre = NULL;
		cacheHead->pre = cur;
		cacheHead = cur;	
    }
    
    void printCache(){
    	
    	Node *p = cacheHead;
    	while(p!=NULL)
    	{
	    	cout<<p->key<<" ";
	    	p=p->next;
	    }
	    cout<<endl;
    }
};


int main(void)
{
	LRUCache cache(3);
	cache.set(1,1);
	//cache.printCache();
	
	cache.set(2,2);
	//cache.printCache();
	
	cache.set(3,3);
	cache.printCache();
	
	cache.set(4,4);
	cache.printCache();
	
	cout<<cache.get(4)<<endl;
	cache.printCache();
	
	cout<<cache.get(3)<<endl;
	cache.printCache();
	cout<<cache.get(2)<<endl;
	cache.printCache();
	cout<<cache.get(1)<<endl;
	cache.printCache();
	
	cache.set(5,5);
	cache.printCache();
	
	cout<<cache.get(1)<<endl;
	cout<<cache.get(2)<<endl;
	cout<<cache.get(3)<<endl;
	cout<<cache.get(4)<<endl;
	cout<<cache.get(5)<<endl;
	
	return 0;
}

  提交測試結果:

  可以發現,效率有進一步的提升。

  其實在STL中的list就是一個雙向鏈表,如果希望代碼簡短點,可以用list來實現:

  具體實現:

 

#include <iostream>
#include <map>
#include <algorithm>
#include <list>
using namespace std;

struct Node
{
	int key;
	int value;
};


class LRUCache{
private:
	int maxSize ;
	list<Node> cacheList;
	map<int, list<Node>::iterator > mp;
public:	
    LRUCache(int capacity) {
      maxSize = capacity;
    }
    
    int get(int key) {
    	map<int, list<Node>::iterator >::iterator it = mp.find(key);
    	if(it==mp.end())	  //沒有命中 
    	{
	    	return -1;
	    }
	    else  //在cache中命中了 
	    {
	    	list<Node>::iterator listIt = mp[key];
    		Node newNode;
	    	newNode.key = key;
	    	newNode.value = listIt->value;
    		cacheList.erase(listIt);               //先刪除命中的節點		
	    	cacheList.push_front(newNode);   //將命中的節點放到鏈表頭部 
	    	mp[key] = cacheList.begin();
    	}
    	return cacheList.begin()->value;
    }
    
    void set(int key, int value) {
  		map<int, list<Node>::iterator >::iterator it = mp.find(key);
    	if(it==mp.end())   //沒有命中 
    	{
	    	if(cacheList.size()==maxSize)  //cache滿了 
	    	{
	    		mp.erase(cacheList.back().key); 	
	    		cacheList.pop_back();
	    	}
	    	Node newNode;
	    	newNode.key = key;
	    	newNode.value = value;
	    	cacheList.push_front(newNode);
	    	mp[key] = cacheList.begin();
	    }
	    else  //命中 
	    {
   			list<Node>::iterator listIt = mp[key];
    		cacheList.erase(listIt);               //先刪除命中的節點 			
			Node newNode;
	    	newNode.key = key;
	    	newNode.value = value;
	    	cacheList.push_front(newNode);   //將命中的節點放到鏈表頭部 
	    	mp[key] = cacheList.begin();
    	}
    }
};


int main(void)
{
	LRUCache cache(3);
	cache.set(1,1);
	
	cache.set(2,2);
	
	cache.set(3,3);
	
	cache.set(4,4);
	
	cout<<cache.get(4)<<endl;
	
	cout<<cache.get(3)<<endl;
	cout<<cache.get(2)<<endl;
	cout<<cache.get(1)<<endl;
	
	cache.set(5,5);
	
	cout<<cache.get(1)<<endl;
	cout<<cache.get(2)<<endl;
	cout<<cache.get(3)<<endl;
	cout<<cache.get(4)<<endl;
	cout<<cache.get(5)<<endl;
	
	return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2024 CODEPRJ.COM