原文地址: http://www.cnblogs.com/dolphin0520/p/3741519.htmlhtml
LRU Cacheios
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.數組
1)get(key):若是key在cache中,則返回對應的value值,不然返回-1緩存
2)set(key,value):若是key不在cache中,則將該(key,value)插入cache中(注意,若是cache已滿,則必須把最近最久未使用的元素從cache中刪除);若是key在cache中,則重置value的值。數據結構
Recently Used,即最近最久未使用的意思。在操做系統的內存管理中,有一類很重要的算法就是內存頁面置換算法(包括FIFO,LRU,LFU等幾種常見頁面置換算法)。事實上,Cache算法和內存頁面置換算法的核心思想是同樣的:都是在給定一個限定大小的空間的前提下,設計一個原則如何來更新和訪問其中的元素。下面說一下LRU算法的核心思想,LRU算法的設計原則是:若是一個數據在最近一段時間沒有被訪問到,那麼在未來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。測試
而用什麼數據結構來實現LRU算法呢?可能大多數人都會想到:用一個數組來存儲數據,給每個數據項標記一個訪問時間戳,每次插入新數據項的時候,先把數組中存在的數據項的時間戳自增,並將新數據項的時間戳置爲0並插入到數組中。每次訪問數組中的數據項的時候,將被訪問的數據項的時間戳置爲0。當數組空間已滿時,將時間戳最大的數據項淘汰。spa
這種實現思路很簡單,可是有什麼缺陷呢?須要不停地維護數據項的訪問時間戳,另外,在插入數據、刪除數據以及訪問數據時,時間複雜度都是O(n)。操作系統
那麼有沒有更好的實現辦法呢?.net
那就是利用鏈表和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中,都有一個相同的操做就是將在命中的節點移到鏈表頭部,若是按照傳統的遍歷辦法來刪除節點能夠達到題目的要求麼?第二,在刪除鏈表末尾節點的時候,必須遍歷鏈表,而後將末尾節點刪除,這個能達到題目的時間要求麼?
試一下便知結果:
第一個版本實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
#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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
#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來實現:
具體實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
#include <iostream>
#include <map>
#include <algorithm>
#include <list>
using
namespace
std;
struct
Node
{
&n
|