LRU的C++实现引申出的迭代器问题
leetcode上刷题。碰到一题实现LRU算法的题目。
LRU,Least recently used。是一种常见的cache和页面替换算法。算法和原理可以参阅相关wiki。
leetcode上的这一题,时间要求很苛刻,如果达不到O(1)复杂度的话,基本上会TLE。
所以,这一题如果用C++来解的话,需要用到list和unordered_map。其中unordered_map是C++11标准提供hash_map的实现。
实现LRU需要注意两点:一是必须保持list的有序,因为其先后关系体现了其最近调用的频次,二是查询必须快准狠,查询list中相关cache必须在O(1)内完成,决不允许遍历list实现。
这下问题来了。又要有序,又要hash。这如何是好?
google了一下,发现一个帖子。地址:https://groups.google.com/forum/#!topic/comp.lang.c++.moderated/bgOd8zMc65k
Q:
I have recently started using java and have come across the
LinkedHashMap. For those not in the know this is like a hash table (ie
fast indexing via hash function) but also has the neat feature where
iterating through the table the entries are retrieved in the order
they were inserted. This is really useful when implementing caches. I
wonder what plans there are (if any) for a similar class in the std
library?
A:
The suggestions are to store your data in one data structure, and then
store the iterators in another. Currently, C++ does not actually
guarantee the presence of a hash map, though your implementation may
provide one. The basic suggestion is that if you store your iterators
to std::unordered_map in a list, then you can add an iterator each
time to add to the map, effectively recording how things are accessed.
The data iterators provide very fast data to any individual element as
well.
If you want to create a class that wraps this featureset, you'll have
to do it yourself, though. There doesn't exist any pre-written
functionality for this.
意思是说,java中有一个linkedhashmap类型,比较容易拿来实现cache类算法,问c++是否有对应实现
回答的说,可以自己写,用list和unordered_map。
里面有一个思路很亮,将iterator存在hash_map中。。。也就是hash表结构为hash(key, [value,iterator])
等等,将iterator保存起来?我见过的iterator不是用来遍历container的么?还有这种新奇的用法?我记得好像会有迭代器失效的问题啊。
不管如何,我还是按照这个方法实现了下,果然很快AC了。
AC不行啊,为什么要这样用iterator。。
下面,就来探究探究iterator到底有哪些诡异的地方。
解决C++语言类细节,有两个网站必须去的
https://www.sgi.com/tech/stl/
http://www.cplusplus.com/
所以,其实一切的缘由皆在其中。
首先明确,迭代器,不只有一种,可以划分为更多细小的类型。下图说明了一切
The properties of each iterator category are:
category | properties | valid expressions | |||
---|---|---|---|---|---|
all categories | copy-constructible, copy-assignable and destructible | X b(a); |
|||
Can be incremented | ++a |
||||
Random Access | Bidirectional | Forward | Input | Supports equality/inequality comparisons | a == b |
Can be dereferenced as an rvalue | *a a->m |
||||
Output | Can be dereferenced as an lvalue (only for mutable iterator types) |
*a = t *a++ = t |
|||
default-constructible | X a; X() |
||||
Multi-pass: neither dereferencing nor incrementing affects dereferenceability | { b=a; *a++; *b; } |
||||
Can be decremented | --a a-- *a-- |
||||
Supports arithmetic operators + and - | a + n n + a a - n a - b |
||||
Supports inequality comparisons (<, >, <= and >=) between iterators | a < b a > b a <= b a >= b |
||||
Supports compound assignment operations += and -= | a += n a -= n |
||||
Supports offset dereference operator ([]) | a[n]
|
可以看到,不同的迭代器支持的操作都不太一样。详细可参阅相关文档。
同样是iterator,vector和list支持的操作就不一样。也就是说,不同的container的iterator性质不一样。
vector的iterator可以有如下操作 v.begin()+1
而list的iterator这样做是编译通不过的 l.begin()+1
why?
根据文档,list的iterator是
Member types iterator and const_iterator are bidirectional iterator types (pointing to an element and to a const element, respectively).
对应上表,知道为什么了吧。
同理,vector的iterator是Random Access。
所以,不同container的iterator操作一定要仔细辨别。
其实,这么规定也是有缘由的,链表本来就不支持随机访问,不是么,而连续数组则可以。
第二个问题,iterator invalidation。即迭代器失效的问题。
这个问题比较隐晦,一般而言vector这类的容器在修改容器内容后,容器内存有可能重新分配,从而导致迭代器指向的位置是个junk。
下面是文档描述:
vector:
Memory will be reallocated automatically if more than capacity() - size() elements are inserted into the vector. Reallocation does not change size(), nor does it change the values of any elements of the vector. It does, however, increase capacity(), and it invalidates [5] any iterators that point into the vector.
A vector's iterators are invalidated when its memory is reallocated. Additionally, inserting or deleting an element in the middle of a vector invalidates all iterators that point to elements following the insertion or deletion point. It follows that you can prevent a vector's iterators from being invalidated if you use reserve() to preallocate as much memory as the vector will ever use, and if all insertions and deletions are at the vector's end.
但是对于list而言,就不是这么回事了
Alist is exactly the opposite: iterators will not be invalidated, and will not be made to point to different elements, but, for list iterators, the predecessor/successor relationship is not invariant.
A similar property holds for all versions of insert() and erase(). List<T, Alloc>::insert() never invalidates any iterators, and list<T, Alloc>::erase() only invalidates iterators pointing to the elements that are actually being erased.
The ordering of iterators may be changed (that is, list<T>::iterator might have a different predecessor or successor after a list operation than it did before), but the iterators themselves will not be invalidated or made to point to different elements unless that invalidation or mutation is explicit.
所以,这就是为什么可以用hash_map来保存list的iterator而不会出现iterator失效的问题。
顺便总结一下其余的容器迭代器特性
Map has the important property that inserting a new element into a map does not invalidate iterators that point to existing elements. Erasing an element from a map also does not invalidate any iterators, except, of course, for iterators that actually point to the element that is being erased.
Set has the important property that inserting a new element into a set does not invalidate iterators that point to existing elements. Erasing an element from a set also does not invalidate any iterators, except, of course, for iterators that actually point to the element that is being erased.
Stack does not allow iteration through its elements. This restriction is the only reason for stack to exist at all. Note that any Front Insertion Sequence or Back Insertion Sequence can be used as a stack; in the case of vector, for example, the stack operations are the member functions back, push_back, and pop_back. The only reason to use the container adaptor stack instead is to make it clear that you are performing only stack operations, and no other operations.
Queue does not allow iteration through its elements.This restriction is the only reason for queue to exist at all. Any container that is both a front insertion sequence and a back insertion sequence can be used as a queue; deque, for example, has member functions front,back, push_front, push_back, pop_front, and pop_back The only reason to use the container adaptor queue instead of the container deque is to make it clear that you are performing only queue operations, and no other operations.
当然还有其他的,可以参阅相关文档。
LRU的C++实现引申出的迭代器问题的更多相关文章
- 设计模式之开放-封闭原则(引申出Objective-C中继承、Category、Protocol三者的区别,这点面试常问)
开放封闭原则(OCP原则The Open-Closed Principle)是面向对象的核心设计所在.它是说,软件开发实体(类.模块.函数等)应该可以扩展,但是不能修改. 这个原则有两个特征,一个是说 ...
- 【Azure Redis 缓存】由Azure Redis是否可以自定义密码而引申出Azure PaaS的Redis服务是否可以和自建的Redis进行主从配置呢?
问题描述 在自建的Redis服务中,可以通过 config set requirepass <Password> 设置Redis的登录密码,然后使用auth 命令输入密码.操作命令如下: ...
- 九九乘法口诀引申出NN乘法口诀
package com.tfj.function; import java.util.Scanner; public class NNTable { public void method1(int n ...
- angularjs某些指令在外部作用域继承并创建新的子作用域引申出的“值复制”与“引用复制”的问题
<!DOCTYPE html> <html lang="zh-CN" ng-app="app"> <head> <me ...
- 由inline-block小例子引申出的一些问题,及IE6、IE7兼容性解决方案
使用场景分析: 常见的对块与块之间的横向排列处理 对同级所有元素使用display:inline-block; , 之后块与块直接会产生间隙问题 解决办法: 给父级设 font-size:0; 别高兴 ...
- TCP/IP中的Payload概念以及由此引申出的一些问题
TCP报文一次性最大运输的货物量(Payload),大体可以这么来计算: IP报文头长度 + TCP报文头长度 + Payload长度 ≤ MTU 即左边的三者之和,要小于等于右边MTU的长 ...
- 设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)
上篇博客我们从醋溜土豆丝与清炒苦瓜中认识了“模板方法模式”,那么在今天这篇博客中我们要从电影院中来认识"迭代器模式"(Iterator Pattern).“迭代器模式”顾名思义就是 ...
- 迭代器模式(Iterator Pattern)
迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示. 迭代器模式(Iterator)就是分离了聚合对象的遍历行为,抽象出一个迭代器来负责这样既可以 ...
- iOS设计模式之迭代器模式
迭代器模式 基本理解 迭代器模式(Iterrator):提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露该元素的内部表示. 当你访问一个聚合对象,而且不管这些对象是什么都需要遍历的时候,你就应 ...
随机推荐
- c#读取LOG文件并解决读取提示被其他进程占用问题
c# 读写文件时文件正由另一进程使用,因此该进程无法访问该文件,在IO处理上遇到了无法操作的问题. 文件“C:\u_ex.log”正由另一进程使用,因此该进程无法访问该文件. u_ex.log是一个日 ...
- 【转】Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址
Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址 关注finddreams,一起分享,一起进步: http://blog.csdn.net/finddr ...
- Codeforces #442 Div2 E
#442 Div2 E 题意 给你一棵树,每个结点有开关(0表示关闭,1表示开启),两种操作: 反转一棵子树所有开关 询问一棵子树有多少开关是开着的 分析 先 DFS 把树上的结点映射到区间上,然后就 ...
- 51nod 循环数组最大子段和(动态规划)
循环数组最大子段和 输入 第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N+1行:N个整数 (-10^9 <= S[i] <= 10^9) 输出 输 ...
- BigDecimal不整除异常
通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常的 异 常 :java.lang.ArithmeticException: Non-terminatin ...
- java中的3大特性之多态
一.多态:一个对象具有多种表现形态(父类的引用类型变量指向了子类的对象) 二.多态的满足条件:1.必须要有继承关系2.必须要有方法的重写 三.int[]a; //a引用类型变量-->//引用in ...
- luogu P1824 进击的奶牛
题目描述 Farmer John建造了一个有N(2<=N<=100,000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1,...,xN (0<=xi<=1,000,000 ...
- java 面向接口编程的理解
初学者可能在学习中会有很多疑惑,为什么要这样,明明可以那样实现,这样做的好处又是什么? 可能会的人觉得很简单很容易理解,甚至可能觉得问的问题很智障,但对于小白来说可能是苦思冥想都不得其解的. 自己身为 ...
- Asp.Net MVC part2 View、Controller详解
View详解Razor视图引擎简介HtmlHelper强类型页面 Razor视图引擎简介强大的@:表示使用C#代码,相当于aspx中的<%%>可以完成输出功能当遇到html标签时会认为C# ...
- 苹果iOS系统下检查第三方APP是否安装及跳转启动
在iOS系统,使用Url Scheme框架在APP间互相跳转和传递数据,本文只介绍如果检测和跳转. Url Scheme框架 如果你想知道ios设备中是否安装QQ这个软件,我们可以通过一个简单方法判断 ...