《STL 源码剖析》 deque 实现原理
deque概述
deque是双向开口的连续线性空间,而vector是单向开口的连续线性空间。
deque没有容量的概念,它是动态地以分段连续空间组合而成,随时可以增加一点新的空间并链接起来。 不会像 vector 因 “旧空间不足,而重新配置更大空间,然后复制旧元素,再释放旧空间”。
因此deque 没有reserve功能。
deque提供 Ramdon Access Iterator,但复杂度和vector不能相提并论,这也影响到各类算法方面。因此,除非必要,应尽量使用vector。
例如,对deque进行排序,为了最高效率,可以将deque复制到vector,采用stl sort排序完,再复制回deque。
deque 是表象的连续线性空间。实际上deque 由一段一段的定量连续空间构成。deque最大任务,对多段连续空间的处理,维护整体连续的假象,并提供随机存取接口。而它的代价在于复杂的迭代器结构。
deque中控器
deque 采用一块所谓的map(非stl map)作为主控,map是一小块连续空间。每个元素(节点)都是指针,其指向一段较大的连续性空间,被称为缓冲区。缓冲区才是 deque的存储主体。
deque 迭代器和数据结构
deque 为了维持整体连续的假象,因此总体的任务就落在迭代器的 opterator ++ 和 opteartor – 身上。
其迭代器主要由四个元素构成:
- cur:当前指向的元素地址
- firest:元素所属缓冲区的头
- last:元素所属缓冲区的尾
- node:指向缓冲区所属的map的位置
在缓冲区内,迭代器遍历方式和普通迭代器相同,当进行到缓冲区边缘时,需要调用 set_node() 跳到下一个缓冲区。
void set_node(map_pointer new_node)
{
node = new_node;
first = *new_node;
last = first + difference_typ(buffer_size());
}
deque数据结构主要内容
- map: 指向map
- map_size: map内有多少指针
- start:第一个节点的第一个元素
- finish:最后一个节点的最后一个元素
deque 操作原理
deque随机存储
deque 实现其随机存储的方式,总体思路上,根据总长度和每段缓冲区固定长度,来决定,要查找map中第几个node,再确定是缓冲区中,第几块内存。不过具体详细实现还是需要看源代码
例子:
- 假设一个缓冲区可以放入8个元素;
- 当deque查找[20] 元素时,20/8 + 1 = 3 (第三个node,为何+1? 是因为 map结构中 要预留头尾两个节点,用于扩充,实际元素在map 第二个node开始存储);
- 缓冲区第几个: 20%8 = 4(缓冲区第四个元素)
deque 插入
deque 头尾插入,push_front,push_back,在通常情况下,缓冲区剩余空间足够时,和vector类似,当不足时(只剩一个时),将会插入元素,并申请一块新的缓冲区。
deque 指定位置插入insert(iterator postion,const T& x)
,当postion 等于收尾时,则直接调用 push_front,push_back;当postion在中间位置时,会调用内部私有成员函数insert_aux
进行实现。
insert_aux 原理,查看插入的postion前后元素数量多少,对少的的部分进行,移动式拷贝;
如果,前半部分元素少,则对前半部分的元素,进行整体前移拷贝;
如果,后半部分元素少,则对后半部分元素,进行整体后移拷贝。
由此可见,insert_aux 效率不高,由于虚构的连续空间,效率上,比vector更差。
deque 删除
deque 头尾删除,pop_front,pop_back,与插入相反,当pop的元素是缓冲区第一个或最后一个时,将删除元素后,并清除该缓冲区。
deque 指定迭代器位置删除erase(iterator pos)
原理和insert_aux类似,主要移动式拷贝的方向是缩收的。
如果,前半部分元素少,则对前半部分的元素,进行整体后移拷贝;
如果,后半部分元素少,则对后半部分元素,进行整体前移拷贝。
deque 指定范围删除erase(iterator first, iterator last)
原理上和 删除单个类似。不同在于删除的元素多少问题,以及缓冲区的释放
deque 和stack、queue的关系
stack 和 queue的实现是基于deque,只是对其操作做了限制;
由于stack和queue 对deque的操作进行进行了限制,因此在其操作效率上都比较高。
(我认为 stack和queue实际上是一个算法容器概念的逻辑,只要能符合其先进先出
,先进后出
的逻辑。也可以用list实现)