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-constructiblecopy-assignable and destructible X b(a);
b = a;
Can be incremented ++a
a++
Random Access Bidirectional Forward Input Supports equality/inequality comparisons a == b
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
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 backpush_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,backpush_frontpush_backpop_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.

当然还有其他的,可以参阅相关文档。

时间: 2024-10-15 15:17:42

LRU的C++实现引申出的迭代器问题的相关文章

angularjs在指令中使用子作用域引申出的‘值复制’与‘引用复制’的问题

<!DOCTYPE html> <html lang="zh-CN" ng-app="app"> <head> <meta charset="utf-8"> <title>在指令中使用子作用域</title> <link rel="stylesheet" href="../bootstrap.min.css"> </h

TCP/IP中的Payload概念以及由此引申出的一些问题

TCP报文一次性最大运输的货物量(Payload),大体可以这么来计算: IP报文头长度  +  TCP报文头长度  +  Payload长度  ≤ MTU 即左边的三者之和,要小于等于右边MTU的长度,其中: Internet 路由器接口标准MTU = 1500 IP报文头长度 = 20 TCP报文头长度 = 20 所以 Payload长度≤ MTU – IP报文头长度 – TCP报文头长度                     ≤ 1500 -20 -20                

设计模式之三:迭代器模式(IteratorPattern)

迭代器(Iterator)模式,又叫游标(Cursor)模式.其定义为:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节.迭代器模式是和容器相关的,对容器对象的访问设计到遍历算法. 迭代器模式由以下角色组成: 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口. 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并记录遍历中的当前位置. 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口. 具体容器

【设计模式】迭代器模式

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式.这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示. 迭代器模式属于行为型模式. 介绍 意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示. 主要解决:不同的方式来遍历整个整合对象. 何时使用:遍历一个聚合对象. 如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象. 关键代码:定义接口:hasNext, next. 应用实例:JAVA 中的

设计模式之第6章-迭代器模式(Java实现)

设计模式之第6章-迭代器模式(Java实现) “我已经过时了,就不要讲了吧,现在java自带有迭代器,还有什么好讲的呢?”“虽然已经有了,但是具体细节呢?知道实现机理岂不美哉?”“好吧好吧.”(迭代器闷闷不乐的答应下来.作者吃着小笼包,咂咂嘴道:哼,想偷懒,窗户都没有~). 迭代器模式之自我介绍 正如你们所见,我目前已经没落了,基本上没人会单独写一个迭代器,除非是产品性质的研发,我的定义如下:Provide a way to access the elements of an aggregate

学习日记之迭代器模式和Effective C++

迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象的各个元素,而又不暴露该对象的内部表示. (1),当需要访问一个聚合对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑迭代器模式. (2),你需要对聚集有多种方式遍历时,可以考虑用迭代器模式. (3),当遍历不同的聚集结构,应提供如开始.下一个.当前项等统一的接口. (4),迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器来负责,这样即可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据. Effec

设计模式之迭代器模式--- Pattern Iterator

模式的定义 迭代器模式定义: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. 提供一种方法访问一个容器对象中各个元素,而又不需要暴露对象的内部细节. 类型 行为类 模式的使用场景 方便遍历访问容器内的元素 优点 面向对象设计原则中的单一职责原则,对于不同的功能,我们要尽可能的把这个功能分解出单一的职责,不

【迭代器模式在.NET中的应用】

迭代器模式 定义参考 wiki: Iterator pattern 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据.   ------- 节选自 <大话设计模式>P207 我们可以将迭代器模式抽离出几个重要组成部分: 1. 迭代器抽象类 2. 聚集抽象类 3. 具体迭代器类 4. 具体聚集类 1. 迭代器抽象类 1 public interface IEnumerator 2 { 3 bool Mov

【设计模式】——迭代器模式

迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示.当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,或者你需要对聚集有多种方式遍历时,你就应该考虑用迭代器模式,为遍历不同的聚集结构提供如开始.下一个.是否结束.当前哪一项等统一接口. #include <iostream> #include <vector> using namespace std; typedef string object; //Iterato