从零开始写STL-容器-双端队列

什么是双端队列?在介绍vector源码,我们发现在vector前端插入元素往往会引起大量元素的重新分配,双端队列(deque)就是为了解决这一问题,双端队列中在首端和末端插入元素的时间复杂度都为O(1),也许你会说链表不行吗,但是其实链表存在一定的缺陷,比如每个结点都要多花出两份存储指针的空间,下面我们将通过源码来分析deque的实现。

基本实现模型 链状数组

动态数组中在首端插入元素低效率的根本原因在于不能在首端分配新的内存,结合链表的实现我们可以实现一个链状数组,其中维护一个内存缓存数组,数组中每个元素指向一片固定大小的内存块,当前内存块耗尽(到达最末端或者首端)就会通过分配器来调度迭代器的位置进入下一个或者前一个内存块。

要维护一个双端队列需要实现哪些?

  • 内存缓冲区数组的位置
  • 当前已使用的缓冲区大小和位置(通过 维护数组中两个指针,一个指向被使用的最前面的内存块,一个指向被使用的最后面的内存块。

双端队列迭代器

要维护一个迭代器需要实现哪些东西?

  • 首先需要知道当前在哪个内存块(内存分配器所分配的块状内存)中
  • 还要知道内存块的开始/结束位置 以免访问未初始化的内存

    实现迭代器的随机存取时,注意通过内存块容纳元素数量和内存块位置来决定元素之间的距离。
	template<class T, size_t Bufsize = 0>
struct _deque_iterator : public random_access_iterator<T>
{
typedef _deque_iterator<T> iterator;
// 这里计算的是一个内存块中可以存放多少T类型的实例
static size_t buffer_size()
{
return _deque_buf_size(Bufsize, sizeof(T));
} _deque_iterator()
{
cur = first = last = NULL;
node = NULL;
}
T* cur;//此迭代器所指缓冲区中的元素
T* first;//缓冲区开头元素
T* last;//缓冲区尾部元素
map_pointer node;//缓冲区控制器 void set_node(map_pointer new_node)
{
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
} reference operator*() const
{
return *cur;
}
pointer operator->() const
{
return &(operator*());
}
difference_type operator-(const self& x)const
{
return difference_type(buffer_size())*(node - x.node - 1) + (cur - first) + (x.last - x.cur);// 间隔内存块*每个内存块个数 + 结点offset
}
self& operator++()
{
++cur;
if (cur == last) //到当前内存块最后一个元素,由链状数组 内存分配器 来指向下一个内存块
{
set_node(node + 1);
cur = first;
}
return *this;
}
self operator++(int)
{
self tmp = *this;
++*this;
return tmp;
}
self& operator--()
{
--cur;
if (cur == first)
{
set_node(node - 1);
cur = last;
}
return *this;
}
self operator--(int)
{
self tmp = *this;
--*this;
return tmp;
}
//随机存取
self& operator+=(difference_type n)
{
difference_type offset = n + (cur - first);
if (offset >= 0 && offset < (difference_type)(buffer_size()))
cur += n;//在当前的缓冲块内部
else
{
difference_type node_offset = offset > 0 ? offset / difference_type(buffer_size())
: -difference_type((-offset - 1) / buffer_size()) - 1;
set_node(node + node_offset);// 计算内存块的偏移量
cur = first + (offset - node_offset*difference_type(buffer_size()));
}
return *this;
}
self operator+(difference_type n) const
{
self tmp = *this;
return tmp += n;
} self& operator-=(difference_type n)
{
return *this += -n;
}
self operator-(difference_type n)const
{
self tmp = *this;
return tmp -= n;
}
reference operator[](difference_type n) const
{
return *(*this + n);
} bool operator==(const self &x) { return x.cur == cur; }
bool operator!=(const self &x)
{
return !(*this==x);
}
bool operator<(const self& x)
{
return (node == x.node) ? (cur < x.cur) : (node < x.node);//优先比较缓冲区!
}
};

双端队列源码

deque 除了维护一个先前说过的指向map的指针外,也维护start finish 两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素,同时管理当前map的大小,在节点不足时重新分配内存。

typedef 部分

		typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef size_t size_type;
typedef _deque_iterator<T> iterator;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
typedef pointer* map_pointer;

内部数据

protected:
iterator start, finish;//维护已经使用内存块的头尾
map_pointer map;//指向内存分配器
size_type map_size;//存放数据量
std::allocator<T> data_allocator;//内存分配器
std::allocator<pointer> map_allocator;//注意这个内存分配器是用来分配内存块的

内存分配

		void create_map_and_nodes(size_type num_elements)
{
//分配内存结点数量
size_type num_nodes = num_elements / buffer_size() + 1;
map_size = std::max((size_t)8, num_nodes + 2);
map = map_allocator.allocate(map_size);
//取中间部分来存放数据,这样给头围留下较为稳定均衡的增长空间
map_pointer nstart = map + (map_size - num_nodes) / 2;
map_pointer nfinish = nstart + num_nodes - 1;
map_pointer cur;
try
{
//注意这里cur 是 T**
for (cur = nstart; cur <= nfinish; cur++)
{
//分配内存块 这里*cur 是 T*
*cur = data_allocator.allocate(buffer_size());
}
}
catch (...)
{ }
start.set_node(nstart);
finish.set_node(nfinish);
start.cur = start.first;
finish.cur = finish.first + num_elements%buffer_size();
}
//填充值
void fill_initialize(size_t n,const value_type& val)
{
create_map_and_nodes(n);
map_pointer cur;
try
{
for (cur = start.node; cur < finish.node; cur++)
std::uninitialized_fill(*cur, *cur + buffer_size(), val);
std::uninitialized_fill(finish.first, finish.last, val);
}
catch (...)
{
delete this;
throw __uncaught_exception;
}
}
//要增加的内存块数 以及是否在前端添加(便于更加高效移动元素)
void reallocate_map(size_type nodes_to_add, bool add_at_front)
{
size_type old_num_nodes = finish.node - start.node + 1;
size_type new_num_nodes = old_num_nodes + nodes_to_add; map_pointer new_start;
if (map_size > 2 * new_num_nodes)//无需重新分配内存
{
new_start = map + (map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
if (new_start < start.node)
std::copy(start.node, finish.node + 1, new_start);
else
std::copy_backward(start.node, finish.node + 1, new_start + old_num_nodes);
}
else
{
size_type new_map_size = map_size + std::max(map_size, nodes_to_add) + 2;
map_pointer new_map = map_allocator.allocate(new_map_size);
new_start = new_map + (new_map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
std::copy(start.node, finish.node + 1, new_start);
map = new_map;
map_size = new_map_size;
}
}
void push_front_aux(const value_type& val)
{
if (start.node - map < 1)
reallocate_map(1, true);
*(start.node - 1) = data_allocator.allocate(buffer_size());
try
{
start.set_node(start.node - 1);
start.cur = start.last - 1;
data_allocator.construct(start.cur, val);
}
catch (...)// commit or rollback!
{
start.set_node(start.node + 1);
start.cur = start.first;
data_allocator.deallocate(*(start.node - 1),buffer_size());
throw;
}
}
void push_back_aux(const value_type& val)
{
if (map_size - (finish.node - map) < 2)
reallocate_map(1, false);
*(finish.node + 1) = data_allocator.allocate(buffer_size());
try
{
data_allocator.construct(finish.cur, val);
finish.set_node(finish.node + 1);
finish.cur = finish.first;
}
catch (...)
{
finish.set_node(finish.node - 1);
finish.cur = finish.last;
data_allocator.deallocate(*(finish.node + 1), buffer_size());
throw;
}
}

数据获取相关

		iterator begin()
{
return start;
}
iterator end()
{
return finish;
}
reference operator[](size_type n)
{
return start[(difference_type)n];
}
reference front()
{
return *start;
}
reference back()
{
return *(finish - 1);
}
size_type size()
{
return finish - start;
}
size_type max_size() { return size_type(-1); }
bool empty() { return finish == start; }

Modifiers

		void push_back(const value_type& val)
{
if (finish.cur != finish.last - 1)
{
//没必要重新分配空间
data_allocator.construct(finish.cur, val);
++finish.cur;
}
else
push_back_aux(val);
}
void push_front(const value_type& val)
{
if (start.cur != start.first)
{
data_allocator.construct(start.cur - 1, val);
--start.cur;
}
else
push_front_aux(val);
}
void clear()
{
for (auto it = start.cur + 1; it < finish.cur; it++)
data_allocator.destroy(it);//销毁内存块中元素
for (auto it = start.node + 1; it <= finish.node; it++)//销毁并回收内存块
map_allocator.destroy(it),map_allocator.deallocate(it,1);
finish = start;
}
void pop_back()
{
if (finish.cur != finish.first)
{
--finish.cur;
data_allocator.destroy(finish.cur);
}
else
{
//回收当前内存块
data_allocator.deallocate(finish.first,buffer_size());
finish.set_node(finish.node - 1);
finish.cur = finish.last - 1;
data_allocator.destroy(finish.cur);
}
}
void pop_front()
{
if (start.cur != start.last - 1)
{
data_allocator.destroy(start.cur);
start.cur++;
}
else
{
destroy(start.cur);
set_node(start.node + 1);
start.cur = start.first;
}
} iterator erase(iterator pos)
{
iterator next = pos++;
difference_type index = pos - start;
if (index < size() / 2)
{
copy_backward(start, pos, next);
pop_front();
}
else
{
copy(next, finish, pos);
pop_back();
}
return start + index;
}
//这里和vector 删除元素的思路类似
//通过前端 后端 元素数量选择最高效的移动数据方式
iterator erase(iterator first, iterator last)
{
if (first == start&&last == finish)
{
clear();
return finish;
}
else
{
difference_type n = last - first;
difference_type elems_before = first - start;
if (elems_before < (size() - n) / 2)//如果前方的元素较少
{
copy_backward(start, first, last);
iterator new_start = start + n;
for (auto it = start; it < new_start; it++)
data_allocator.destroy(it);
for (auto it = start; it < new_start; it++)
data_allocator.deallocate(it, buffer_size());
start = new_start;
}
else
{
copy(last, finish, first);
iterator new_finish = finish - n;
for (auto it = new_finish; it < finish; it++)
data_allocator.destroy(it);
for (auto it = new_finish; it < finish; it++)
data_allocator.deallocate(it, buffer_size());
finish = new_finish;
}
return start + elems_before; }
} iterator insert(iterator pos, const value_type& x)
{
if (pos.cur == start.cur)
{
push_front(x);
return start;
}
else if (pos.cur == finish.cur)
{
push_back(x);
return finish - 1;
}
else
{
difference_type index = pos - start;
value_type x_copy = x;
//通过Push_front 把头元素复制到前一个内存块,然后将原内存块元素移动到对应位置
if (index < size() / 2)
{
push_front(front());
iterator front1 = start;
++front1;
iterator front2 = front1;
++front2;
pos = start + index;
iterator pos1 = pos;
++pos1;
copy(front2, pos1, front1); }
else
{
push_back(back());
iterator back1 = finish;
--back1;
iterator back2 = back1;
--back2;
pos = start + index;
copy_backward(pos, back2, back1);
}
*pos = x_copy;
return pos;
}
}

从零开始写STL-容器-双端队列的更多相关文章

  1. C++STL之双端队列容器

    C++STL之双端队列容器 deque双端队列容器与vector很类似,采用线性表顺序存储结构.但与vector区别,deque采用分块的线性存储结构来存储数据,每块的大小一般为512B,将之称为de ...

  2. 从零开始写STL—容器—vector

    从0开始写STL-容器-vector vector又称为动态数组,那么动态体现在哪里?vector和一般的数组又有什么区别?vector中各个函数的实现原理是怎样的,我们怎样使用会更高效? 以上内容我 ...

  3. STL容器-deque-双端队列

    注明:全部来自转载,供自己学习与复习使用 deque双向开口可进可出的容器 我们知道连续内存的容器不能随意扩充,因为这样容易扩充别人那去 deque却可以,它创造了内存连续的假象. 其实deque由一 ...

  4. [STL] deque 双端队列

  5. STL队列 之FIFO队列(queue)、优先队列(priority_queue)、双端队列(deque)

    1.FIFO队列   std::queue就是普通意思上的FIFO队列在STL中的模版. 1.1主要的方法有: (1)T front():访问队列的对头元素,并不删除对头元素 (2)T back(): ...

  6. c++实现双端队列

    在使用c++容器的时候其底层如何实现  例如  vector 容器  :是一个内存可以二倍扩容的向量容器,使用方便但是对内存要求严格,弊端明显    list  容器  : 双向循环链表    deq ...

  7. 【C++】STL常用容器总结之五:双端队列deque

    6.双端队列deque 所谓的deque是”double ended queue”的缩写,双端队列不论在尾部或头部插入元素,都十分迅速.而在中间插入元素则会比较费时,因为必须移动中间其他的元素.双端队 ...

  8. stl之deque双端队列容器

    deque与vector很相似,不仅能够在尾部插入和删除元素,还能够在头部插入和删除. 只是当考虑到容器元素的内存分配策略和操作性能时.deque相对vector较为有优势. 头文件 #include ...

  9. STL容器:deque双端队列学习

    所谓deque,是"double-ended queue"的缩写; 它是一种动态数组形式,可以向两端发展,在尾部和头部插入元素非常迅速; 在中间插入元素比较费时,因为需要移动其它元 ...

随机推荐

  1. AJPFX关于增强for的概述和使用(foreach)

    增强for的概述和使用(foreach)1.增强for的概述和使用(foreach)                格式:                for(数组或者Collection集合中元素 ...

  2. 关于react native在window下运行安卓的时候报 could not connect to development server

    当出现这种问题是网上的解答方案都是一模一样的! 我这边先给个地址讲的是解决方案http://blog.csdn.net/qq_25827845/article/details/52974991 但是我 ...

  3. php接收json格式数据(text/xml)

    在API服务中,目前流行采用json形式来交互. 给前端调用的接口输出Json数据,这个比较简单,只需要组织好数据,用json_encode($array) 转化一下,前端就得到json格式的数据. ...

  4. 从0开始搭建SQL Server 2012 AlwaysOn 第二篇(配置故障转移集群)

    本篇主要讲配置Windows 故障转移集群及遇到的相关问题(坑),因为AlwaysOn是基于Windows的故障转移集群的 在讲解步骤之前需要了解一下故障转移集群仲裁配置 四种集群的仲裁配置: 1.多 ...

  5. Android(java)学习笔记200:JNI之NDK的概念

    1.交叉编译 (1)概念 在一个平台(硬件)和os(软件)环境下,编译出另一种平台和os下可以运行的二进制代码. e.g:     电脑端                               ...

  6. linux crontab创建计划任务

    1.编辑计划任务 编辑crontab文件 crontab -e 2.查看计划任务日志 查看crontab日志 tail -100f /var/log/cron 3.创建计划任务格式 (1)基本格式 : ...

  7. 火狐删除配置文件 会删除目录下所有文件 切记不要把配置文件建立在桌面 恢复软件:易我数据恢复向导 9.0 DiskGenius500

    火狐删除配置文件 会删除目录下所有文件 切记不要把配置文件建立在桌面 恢复软件:易我数据恢复向导 9.0  DiskGenius500 结果:由于时间比较常 恢复文件均失败了~

  8. weblogic启动 web应用ssh关闭 nohup命令

    平时我们操作linux服务器的时候,都是通过ssh远程连接,然后启动服务器上的服务的,所以有时候启动weblogic,我们关闭ssh,weblogic 服务也相应的关闭了,那么我们就只能用nohup这 ...

  9. c++复合类型

    1.数组 数组存储同类型的值: 数组使用下标或索引对元素进行标号,从0开始编号: 只能在定义数组时才能使用初始化,此后就不可以了,也不能将一个数组赋给另一个数组: 初始化数组时,提供的值可以少于数组元 ...

  10. MySQL 日志初探

    目录 MySQL 日志初探 零.概述 一.Error Log(错误日志) 二.General Query Log(通用查询日志) 三.Slow Query Log (慢查询日志) 四.Binary L ...