STL deque

deque是一种双向开口的连续线性空间,双向开口意思是可以在头尾两端分别做元素的插入和删除操作。vector其实也可以从头尾两端进行操作,从技术观点,但是效率奇差,无法接受。
deque和vector的最大差异,一在于deque允许常数时间对头端进行元素的插入或移除操作。二在于deque没有所谓的容量观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间拼接起来。
deque实现的复杂度很高,除非必要最好选择使用vector,对deque进行排序的操作,为了最高效率,可以将deque先完整复制到一个vector身上,将vector排序后(利用STL的sort算法),再复制回deque.

deque中控器

deque是由一段一段的定量连续空间构成,一旦有必要在deque的前端或尾端增加空间,便配置一段定量连续空间,串联在整个deque的头端或尾端,为了维持整体连续的假象,需要使用一块所谓的map(不是STL的map容器) 作为主控,这里的map是一块连续空间,其中每一个元素(在这称为节点node)都是指针,指向另一段较大的连续线性空间,称为缓冲区,缓冲区才是deque的存储空间体。(按我个人理解,这就相当于有两级的页目录,通过map查找不同内存缓冲区的位置在哪)。

在这里插入图片描述

deque迭代器

按照上面所述,deque的存储主体其实是各个缓冲区,而为了让这些分段的连续空间看起来像是一个整体的连续作用,归功于迭代器operator++和operator–,迭代器需要指出分段连续空间在哪,它需要判断自己是否已经处于所在的缓冲区边缘,如果是,一旦前进或者后退时必须跳跃到下一个或上一个缓冲区,为了能够正确跳跃,deque必须随时掌握管控中心。

迭代器需要保持与容器的联结 因此有以下组成, cur first last node, 其中node指向管控中心map, 其关系如下
在这里插入图片描述
deque的关键行为有,一旦在缓冲区的边缘,前进或后退时可能需要调用set_node()跳入一个缓冲区。

void set_node(map_pointer new_node){
node=new_node;
firse=*new_node;
last=first+difference_type(buffer_size());
}

以上这个函数是写在迭代器的结构中的,这个函数的输入参数是map管控中心的node参数,迭代器中的node则保存该参数,迭代器的first指向缓冲区的第一个元素,因为new_node是指针的指针,所以需要*new_node,first加上缓冲区的大小就是last的位置。
再定义++或者–时,当在缓冲区边缘时就要判断,以下给出一部分源码

self& operator++(){//类似++a
++cur;
if(cur==last)
{
set_node(node+1);//这里的+1其实就是在map管控中心加一,找下一个缓冲区
cur=first;
}
return *this;
}
self &operator --(){
if(cur==first)
{
 set_node(node-1);//-1,在map管控中心中减一,找前一个缓冲区
 cur=last;
}
--cur;
return *this;
}

在这里插入图片描述

关键的核心是在定义++ --或者+= -=时,要判断是不是在同一个缓冲区中,如果不是要跳到正确的节点中,这就需要使用set_node()函数。
这里有个技巧 operator-= 其实可以由operator+=完成,只需要return *this+=-n;

deque数据结构

deque需要维护start finish两个迭代器分别指向第一缓冲区和最后缓冲区的最后一个元素的下一个位置。同时必须记住当前map的大小,因为当map的节点不足时就要重新配置更大的一块map.
deque中的create_map_and_node()负责产生并安排好deque结构。根据需要的节点数,以及一个缓冲区的大小,决定在map中的个数 num_node=num_elements/buffer_size()+1;如果刚好整除会多分配一个节点,一个map要管理几个节点,最少是8个,最多是所需节点加2,前后各留一个,扩充时留用。接着让nstart和nfinish指向map所拥有的全部节点的最中央区,保持最中央,可使头尾两端的扩充能量一样大。最后要在map内为每个现用节点配置缓冲区(采用allocate_node()),这些缓冲区加起来就是deque的可用空间,最后一个缓冲区可能会留有一些余裕。

在使用push_back()或者push_front时,如果最后一个缓存区或者第一个缓存区仍有可以用空间则直接构造。如果没有了,那么就看之前在map中分配为前后分别留下来的扩充空间是否足够,如果够,那么就为他配置缓冲区,同时要改变finish或者start的位置。如果留下来的节点备用空间不足,就需要重换一个map,配置一个更大的map,需要拷贝原来的map的内容 ,再释放原map,最后记得重新设定map的起始地址、 大小、迭代器start和finish。

在使用clear()时,不能够全部缓冲区析构释放,应该从start.node+1到finish.node-1全部析构释放,如果头尾缓冲区不相等,那么都将所有元素析构,最后只释放尾缓冲区。如果只有一个缓冲区,那么直接将元素析构,不释放缓冲空间。
保留一个缓冲区是deque的策略也是初始状态。

erase()函数在清除某个位置的元素时或者区间的元素时,都是要考虑到移动的效率,进而进行分类讨论,使用不同的复制移动方法。
insert()函数除了在deque最前端或者最后端插入,在其他位置的插入也是关系到元素移动的放大,需要比较插入点之前的元素少或者后面少,接着使用copy()函数或者copy_backward()来完成元素的移动。再在对应位置插入新值。

自己写了一圈下来,对其中的原理更加了解了-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值