数据结构与算法(五):LRU
相关文献:
数据结构与算法(一):基础理论
数据结构与算法(二):线性表的实现
数据结构与算法(三):线性表算法设计练习
数据结构与算法(四):斐波那契数列
数据结构与算法(五):LRU
数据结构与算法(六):栈
数据结构与算法(七):栈/队列的算法解题思想
数据结构与算法(八):队列
数据结构与算法(九):二叉树/线索化二叉树
数据结构与算法(十):哈夫曼树
前言
在应用程序中往往使用到缓存,而缓存它是有一个容量限制的,当我们需要把数据缓存的时候,这个容量满了,我们需要设计一个算法去告诉应用程序应该移除哪些旧的缓存数据。
一、LRU的实现理论
-
LRU
(最近最少使⽤):是⼀种缓存淘汰算法。顾名思义,最⻓时间没有被使⽤的元素会被从缓存中淘汰。
1.队列方式实现LRU
先进来的元素先被优先淘汰,这样的特性像极了队列。于是就可以分析:
-
Queue(队列)
:此队列将具有特定容量,因为缓存的
⼤⼩有限。当引⼊⼀个新元素时,它就会被添加到队列
的尾部。需要淘汰的元素放在队列的头部;先进先出; - 时间复杂度是:
O(n)
- 空间复杂度是:
O(n)
- 每⼀项存储的是
key-value
,通过key
取value
时,需
要遍历。
而缓存里的元素被再次使用的话,就又会把这个元素移动到队列的尾部,这种情况无疑时间复杂度是O(n)。
如何减⼩缓存命中的时间复杂度减⼩到O(1)
?
2.链表+Hash表 实现LRU
- 通过
链表与Hash表
:
Hash表
⽤来存储key-value
,减⼩缓存命中时间;
链表
负责管理缓存元素的顺序。
为什么要用双链表?
单向链表:删除节点需要访问前驱节点,O(n)
双向链表:结点有前驱指针,删除/移动节点都是纯指针变动,O(1)
使用链表+Hash表
方式的话,虽然时间复杂度降低了,但是空间复杂度上来了。
那么就会引发一个选型的问题。如果我的数据量足够多的话,程序会更在意查找速度,更适用于方式2;如果我得数据量较少查找起来很快,更使用于方式1。
二、LRU的队列实现案例
我们平常使用颜色UIColor是可以通过字符串、rgb数值等等解析而来。我们常常会重复地使用一个颜色,但是那玩意儿之前就已经解析过了,没必要重复解析吧,所以我们可以设计一个算法去缓存它。
比如开源框架SYColor
像这样的解析颜色缓存数据量较少查找起来很快,更使用于方式1去实现LRU。
那我们来设计一个简单的缓存类TinyLRUCache
:
//
// TinyLRUCache.h
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// key<TinyLRUCachePolicy> -> createValue -> value
// 用来创建value的
@protocol TinyLRUCachePolicy <NSObject>
- (id)createValue;
@end
@interface TinyLRUCache<__covariant KeyType, __covariant ObjectType> : NSObject
- (instancetype)initWithCapacity:(NSUInteger)numItems;
// 1. key 存在 -》
// 1. key不存在
- (nullable ObjectType)objectForKey:(KeyType<TinyLRUCachePolicy>)aKey;
@end
NS_ASSUME_NONNULL_END
//
// TinyLRUCache.m
//
#import "TinyLRUCache.h"
// Array <key-value>
// key存在的时候,更新key
// Entry是缓存里的一个数据对象
@interface Entry : NSObject
@property (strong, nonatomic) id key; // key
@property (strong, nonatomic) id object; // value
+ (instancetype)entryWithObject:(id)object forKey:(id)key;
@end
@implementation Entry
+ (instancetype)entryWithObject:(id)object forKey:(id)key {
Entry *entry = [self new];
entry.key = key;
entry.object = object;
return entry;
}
@end
// TinyLRUCache是缓存类
@interface TinyLRUCache() {
NSUInteger _numItems;
NSMutableArray <Entry *>*_cache; // 使用数组
}
@end
@implementation TinyLRUCache
// 初始化限定缓存的数量
- (instancetype)initWithCapacity:(NSUInteger)numItems {
self = [super init];
if (self) {
_numItems = numItems;
// malloc
_cache = [NSMutableArray arrayWithCapacity:numItems];
}
return self;
}
/**
通过key找value
1. removeNode
1. moveNodeToHead
2. removeTailNode
3. addNodeToHead
*/
- (nullable id)objectForKey:(id<TinyLRUCachePolicy>)aKey {
for (size_t i = 0; i < _cache.count; i++) {
// key没有命中
if (_cache[i].key != aKey) {
continue;
}
// key命中_cache
if (i == _cache.count - 1) {
return _cache[i].object;
}
// key命中,不是_cache的末尾
Entry *entry = _cache[i];
[_cache removeObjectAtIndex:i];
[_cache addObject:entry];
return _cache[_cache.count - 1].object;
}
// 最近最少使用
if (_cache.count == _numItems) {
[_cache removeObjectAtIndex:0];
}
if ([aKey respondsToSelector:@selector(createValue)]) {
[_cache addObject:[Entry entryWithObject:[aKey createValue] forKey:aKey]];
}
return _cache.lastObject.object;
}
- (NSString *)description {
if (_cache.count == 0) {
return @"<empty cache>";
}
NSMutableString *all = [NSMutableString stringWithString:@"\n|------------TinyLRUCache----------|\n"];
[_cache enumerateObjectsUsingBlock:^(Entry * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[all appendString:[NSString stringWithFormat:@"|-%lu-|--key:--%@--value:--%@--|\n",(unsigned long) (unsigned long)idx, obj.key, obj.object]];
}];
return all;
}
@end
使用TinyLRUCache
:
//
// ViewController.m
//
#import "ViewController.h"
#import "TinyLRUCache.h"
@implementation NSString(TinyLRUCachePolicy)
- (NSString *)createValue {
return [NSString stringWithFormat:@"%@_Cat1237", self];
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
TinyLRUCache <NSString *, NSString *>*cache = [[TinyLRUCache alloc] initWithCapacity:5];
NSLog(@"%@", [cache objectForKey:@"1"]);
NSLog(@"%@", [cache objectForKey:@"2"]);
NSLog(@"%@", [cache objectForKey:@"3"]);
NSLog(@"%@", [cache objectForKey:@"4"]);
NSLog(@"%@", [cache objectForKey:@"5"]);
NSLog(@"%@", [cache objectForKey:@"2"]);
NSLog(@"%@", cache);
}
@end
三、LRU的Hash表+链表 实现案例(链表无头结点)
最近最多使用的存储在头
最近最少使用的存储在尾
使用_head头指针指向首元结点
使用_tail尾指针指向尾结点
不需要初始化虚拟头结点_head/尾结点_tail
创建一个LRUCache
缓存类
//
// LRUCache.h
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol TinyLRUCachePolicy <NSObject>
- (id)createValue;
@end
@interface LRUCache<__covariant KeyType, __covariant ObjectType> : NSObject
- (instancetype)initWithCapacity:(NSUInteger)numItems;
- (nullable ObjectType)objectForKey:(KeyType<TinyLRUCachePolicy>)aKey;
@end
NS_ASSUME_NONNULL_END
//
// LRUCache.m
//
#import "LRUCache.h"
// 定义一个双向链表节点 _DoubleLinkNode
@interface _DoubleLinkNode : NSObject {
@package
__weak _DoubleLinkNode *_prev; // 前驱
__weak _DoubleLinkNode *_next; // 后继
// 数据域
id _key;
id _value;
}
@end
@implementation _DoubleLinkNode
@end
@interface _DoubleLink : NSObject
@end
@interface _DoubleLink() {
@package
NSMutableDictionary *_dic; // hash表
NSUInteger _count; // 已有节点个数
NSUInteger _capacity; // 节点最大数量
// 双向链表
// 无虚拟头结点 - 不需要初始化
_DoubleLinkNode *_head; // 头节点
_DoubleLinkNode *_tail; // 尾结点
}
- (void)addNodeToHead:(_DoubleLinkNode *)node;
- (void)moveNodeToHead:(_DoubleLinkNode *)node;
- (void)removeNode:(_DoubleLinkNode *)node;
- (_DoubleLinkNode *)removeTailNode;
@end
@implementation _DoubleLink
- (instancetype)initWithCapacity:(NSUInteger)numItems {
self = [super init];
if (self) {
_capacity = numItems;
// 初始化哈希表
_dic = [NSMutableDictionary dictionaryWithCapacity:numItems];
}
return self;
}
// 添加节点到头部
- (void)addNodeToHead:(_DoubleLinkNode *)node {
_dic[node->_key] = node; // 将节点保存到hash表
_count++;
if (_head) {
// 头插
node->_next = _head;
_head->_prev = node;
_head = node;
} else {
// 说明双向链表还没有元素
// 头结点/尾结点都是第一个元素
_head = _tail = node;
}
}
// 节点移动到头部
- (void)moveNodeToHead:(_DoubleLinkNode *)node {
if (_head == node) return; // 如果移动的是头节点,则不需要移动
if (_tail == node) { // 如果被移动的节点是尾结点
_tail = node->_prev;
_tail->_next = nil;
} else { // 被移动节点是中间节点
node->_next->_prev = node->_prev;
node->_prev->_next = node->_next;
}
node->_next = _head;
node->_prev = nil;
_head->_prev = node;
_head = node;
}
// 移除节点
- (void)removeNode:(_DoubleLinkNode *)node {
[_dic removeObjectForKey:node->_key]; // 从hash表中移除数据
_count--;
// 节点移除
if (node->_next) node->_next->_prev = node->_prev;
if (node->_prev) node->_prev->_next = node->_next;
if (_head == node) _head = node->_next; // 移除的是头部
if (_tail == node) _tail = node->_prev; // 移除的是尾部
}
// 移除尾结点
- (_DoubleLinkNode *)removeTailNode {
if (!_tail) return nil;
_DoubleLinkNode *tail = _tail; // 先保存,后面需要返回出去
[_dic removeObjectForKey:_tail->_key]; // 从hash表中移除数据
_count--;
if (_head == _tail) {
_head = _tail = nil;
} else {
_tail = _tail->_prev;
_tail->_next = nil;
}
return tail;
}
@end
@interface LRUCache() {
_DoubleLink *_lru; // hash表+双向链表
NSUInteger _numItems; // 缓存个数
}
@end
@implementation LRUCache
- (instancetype)initWithCapacity:(NSUInteger)numItems {
self = [super init];
if (self) {
_numItems = numItems;
_lru = [[_DoubleLink alloc] initWithCapacity:numItems];
}
return self;
}
- (nullable id)objectForKey:(id<TinyLRUCachePolicy>)aKey {
_DoubleLinkNode *node = _lru->_dic[aKey];
// value如何产生你自己说了算,这里只做演示
id value = nil;
if ([aKey respondsToSelector:@selector(createValue)]) {
value = [aKey createValue];
}
if (node) {
// 把节点移动到头部
node->_value = value;
[_lru moveNodeToHead:node];
} else {
// 超出限制,删除双向链表的尾部节点
if (_lru->_count == _numItems) {
[_lru removeTailNode];
}
node = [_DoubleLinkNode new];
node->_key = aKey;
node->_value = value;
[_lru addNodeToHead:node];
}
return node;
}
- (NSString *)description {
if (_numItems == 0) {
return @"<empty cache>";
}
NSMutableString *all = [NSMutableString stringWithString:@"\n|------------LRUCache----------|\n"];
_DoubleLinkNode *node = _lru->_head;
int index = 0;
while (node && node->_key) {
[all appendString:[NSString stringWithFormat:@"|-%d-|--key:--%@--value:--%@--|\n",index, node->_key, node->_value]];
node = node->_next;
index++;
}
return all;
}
@end
//
// main.m
//
#import <Foundation/Foundation.h>
#import "LRUCache.h"
@implementation NSString(TinyLRUCachePolicy)
- (NSString *)createValue {
return [NSString stringWithFormat:@"%@_Cat1237", self];
}
@end
int main(int argc, const char * argv[]) {
LRUCache <NSString *, NSString *>*cache = [[LRUCache alloc] initWithCapacity:5];
[cache objectForKey:@"1"];
[cache objectForKey:@"2"];
[cache objectForKey:@"3"];
[cache objectForKey:@"4"];
[cache objectForKey:@"5"];
[cache objectForKey:@"2"];
// [cache objectForKey:@"1"];
NSLog(@"%@", cache);
return 0;
}
四、LRU的Hash表+链表 实现案例(链表有头结点)
最近最多使用的存储在头
最近最少使用的存储在尾
使用_head头指针指向头结点
使用_tail尾指针指向尾结点
初始化虚拟头结点_head/尾结点_tail
创建一个LRUCache
缓存类
//
// LRUCache.h
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol TinyLRUCachePolicy <NSObject>
- (id)createValue;
@end
@interface LRUCache<__covariant KeyType, __covariant ObjectType> : NSObject
- (instancetype)initWithCapacity:(NSUInteger)numItems;
- (nullable ObjectType)objectForKey:(KeyType<TinyLRUCachePolicy>)aKey;
@end
NS_ASSUME_NONNULL_END
//
// LRUCache.m
//
#import "LRUCache.h"
@interface _DoubleLinkNode : NSObject
{
@package
_DoubleLinkNode *_prev;
_DoubleLinkNode *_next;
id _key;
id _value;
}
@end
@implementation _DoubleLinkNode
@end
@interface _DoubleLink : NSObject
@end
@interface _DoubleLink() {
@package
NSMutableDictionary *_dic; // hash表
NSUInteger _count; // 缓存节点个数
NSUInteger _capacity; // 最大缓存个数
// 双向链表
_DoubleLinkNode *_head; // 头结点-有虚拟头结点
_DoubleLinkNode *_tail; // 尾结点-有虚拟尾结点
}
- (void)addNodeToHead:(_DoubleLinkNode *)node;
- (void)moveNodeToHead:(_DoubleLinkNode *)node;
- (void)removeNode:(_DoubleLinkNode *)node;
- (_DoubleLinkNode *)removeTailNode;
@end
@implementation _DoubleLink
- (instancetype)initWithCapacity:(NSUInteger)numItems {
self = [super init];
if (self) {
_capacity = numItems;
_dic = [NSMutableDictionary dictionaryWithCapacity:numItems];
// 虚拟头部和虚拟尾部节点
_head = [_DoubleLinkNode new];
_tail = [_DoubleLinkNode new];
// 双向链表
_head->_next = _tail;
_tail->_prev = _head;
}
return self;
}
// 添加节点到首元节点处
- (void)addNodeToHead:(_DoubleLinkNode *)node {
_dic[node->_key] = node; // 节点元素添加到hash表
_count++;
node->_prev = _head;
node->_next = _head->_next;
node->_next->_prev = node;
_head->_next = node;
}
// 节点移动到首元结点处
- (void)moveNodeToHead:(_DoubleLinkNode *)node {
// 先移除后添加
[self removeNode:node];
[self addNodeToHead:node];
}
// 移除节点
- (void)removeNode:(_DoubleLinkNode *)node {
[_dic removeObjectForKey:node->_key]; // 节点元素从hash表上移除
_count--;
node->_prev->_next = node->_next;
node->_next->_prev = node->_prev;
}
// 移除最后一个节点元素
- (_DoubleLinkNode *)removeTailNode {
_DoubleLinkNode *node = _tail->_prev;
[self removeNode:node];
return node;
}
@end
@interface LRUCache() {
_DoubleLink *_lru; // hash表+双向链表
NSUInteger _numItems; // 最大缓存数
}
@end
@implementation LRUCache
- (instancetype)initWithCapacity:(NSUInteger)numItems {
self = [super init];
if (self) {
_numItems = numItems;
_lru = [[_DoubleLink alloc] initWithCapacity:numItems];
}
return self;
}
- (nullable id)objectForKey:(id<TinyLRUCachePolicy>)aKey {
_DoubleLinkNode *node = _lru->_dic[aKey];
// value怎么创建的自己定,这里只做演示
id value = nil;
if ([aKey respondsToSelector:@selector(createValue)]) {
value = [aKey createValue];
}
if (node) {
// 如果缓存中存在节点 - 更新数据,并移动到链表首元节点处
node->_value = value;
[_lru moveNodeToHead:node];
} else {
// 超出限制,删除双向链表的尾部节点
if (_lru->_count == _numItems) {
[_lru removeTailNode];
}
node = [_DoubleLinkNode new];
node->_key = aKey;
node->_value = value;
[_lru addNodeToHead:node];
}
return node;
}
- (NSString *)description {
if (_numItems == 0) {
return @"<empty cache>";
}
NSMutableString *all = [NSMutableString stringWithString:@"\n|------------LRUCache----------|\n"];
_DoubleLinkNode *node = _lru->_head->_next;
int index = 0;
while (node && node->_key) {
[all appendString:[NSString stringWithFormat:@"|-%d-|--key:--%@--value:--%@--|\n",index, node->_key, node->_value]];
node = node->_next;
index++;
}
return all;
}
@end
//
// main.m
//
#import <Foundation/Foundation.h>
#import "LRUCache.h"
@implementation NSString(TinyLRUCachePolicy)
- (NSString *)createValue {
return [NSString stringWithFormat:@"%@_Cat1237", self];
}
@end
int main(int argc, const char * argv[]) {
LRUCache <NSString *, NSString *>*cache = [[LRUCache alloc] initWithCapacity:5];
[cache objectForKey:@"1"];
[cache objectForKey:@"2"];
[cache objectForKey:@"3"];
[cache objectForKey:@"4"];
[cache objectForKey:@"5"];
[cache objectForKey:@"2"];
[cache objectForKey:@"1"];
NSLog(@"%@", cache);
return 0;
}