C++ 当我们需要存储“时,列表是否比向量更好?”;最后n个项目是什么;?
有很多问题建议我们应该始终使用向量,但在我看来,对于需要存储“最后n项”的场景,列表会更好 例如,假设我们需要存储看到的最后5项: 迭代0:C++ 当我们需要存储“时,列表是否比向量更好?”;最后n个项目是什么;?,c++,c++11,fifo,buffering,data-stream,C++,C++11,Fifo,Buffering,Data Stream,有很多问题建议我们应该始终使用向量,但在我看来,对于需要存储“最后n项”的场景,列表会更好 例如,假设我们需要存储看到的最后5项: 迭代0: 3,24,51,62,37, 然后在每次迭代中,删除索引0处的项,并在末尾添加新项: 迭代1: 24,51,62,37,8 迭代2: 51,62,37,8,12 在这个用例中,对于一个向量,复杂度将是O(n),因为我们必须复制n个项目,但是在一个列表中,它应该是O(1),因为我们总是在每个迭代中砍掉头部,并添加到尾部 我的理解正确吗?这是std::l
3,24,51,62,37,
然后在每次迭代中,删除索引0处的项,并在末尾添加新项:
迭代1:
24,51,62,37,8
迭代2:
51,62,37,8,12
在这个用例中,对于一个向量,复杂度将是O(n),因为我们必须复制n个项目,但是在一个列表中,它应该是O(1),因为我们总是在每个迭代中砍掉头部,并添加到尾部
我的理解正确吗?这是std::list的实际行为吗?是一个更好的选择。或者,如果您对std::deque进行了基准测试,发现它的性能不适合您的特定用途,那么您可以在固定大小的数组中实现一个循环缓冲区,存储缓冲区开头的索引。在替换缓冲区中的元素时,您将覆盖起始索引处的元素,然后将起始索引设置为其以前的值加上缓冲区大小的一个模
列表遍历非常慢,因为列表元素可以分散在整个内存中,而向量移动实际上惊人地快,因为即使是一个大的内存块,内存在单个内存块上的移动也非常快
会议C++ 2015会议的谈话可能会引起你的兴趣。
< P>简要地说<代码> STD::向量< /代码>对于一个不改变大小的内存来说是更好的。在你的情况下,如果你把所有的数据向前移动或者在矢量中附加新的数据,那一定是浪费。正如@戴维所说的<代码>:STD: <代码>是一个不错的选择。因为您将弹出头部
并向后推
例如双向列表
从名单上看
与其他基本标准序列容器(数组、向量和
deque),列表在插入、提取和删除方面通常表现更好
在容器内的任何位置移动元素
迭代器已经获得,因此在算法中也是如此
它们会大量使用这些,比如排序算法
与其他列表相比,列表和转发列表的主要缺点
序列容器是指它们无法通过
他们的立场;例如,要访问列表中的第六个元素,
必须从已知位置(如开始或结束)进行迭代
结束)到该位置,该位置在
这些它们还消耗一些额外的内存来保持链接
与每个元素相关的信息(可能是重要的
小元素的大列表的系数)
关于
用于涉及频繁插入或删除图元的操作
在开始或结束以外的位置,DEQUE表现更差
和列表和引用相比,具有更少的一致性迭代器和引用
转发列表
因此,与数组相比,向量在交换中消耗更多的内存
能够以高效的方式管理存储和动态增长
对
与其他动态序列容器(DEQUE、LIST和
前向(U列表),向量非常有效地访问其元素
(就像数组一样)和相对高效的添加或删除
元素从其末端开始。对于涉及插入或删除的操作
如果在端部以外的位置移除图元,它们的性能会更差
而且迭代器和引用的一致性较差
比列表和转发列表
对。从末尾删除元素的std::vector的时间复杂度是线性的。对于您正在做的事情,std::deque可能是一个很好的选择,因为它在列表的开头和结尾提供了恒定的插入和删除时间,并且比std::list性能更好 资料来源:
如果您需要存储最后的
N
-元素,那么从逻辑上讲,您正在执行某种队列或循环缓冲区,并且是和队列的实现
您可以手动使用或实现简单的循环缓冲区:
template<int Capcity>
class cbuffer
{
public:
cbuffer() : sz(0), p(0){}
void push_back(int n)
{
buf[p++] = n;
if (sz < Capcity)
sz++;
if (p >= Capcity)
p = 0;
}
int size() const
{
return sz;
}
int operator[](int n) const
{
assert(n < sz);
n = p - sz + n;
if (n < 0)
n += Capcity;
return buf[n];
}
int buf[Capcity];
int sz, p;
};
模板
cbuffer类
{
公众:
cbuffer():sz(0),p(0){}
无效推回(int n)
{
buf[p++]=n;
如果(深圳=Capcity)
p=0;
}
int size()常量
{
返回sz;
}
整数运算符[](整数n)常量
{
断言(n
对于5个int元素的循环缓冲区:
int main()
{
cbuffer<5> buf;
// insert random 100 numbers
for (int i = 0; i < 100; ++i)
buf.push_back(rand());
// output to cout contents of the circular buffer
for (int i = 0; i < buf.size(); ++i)
cout << buf[i] << ' ';
}
intmain()
{
cbuffer-buf;
//随机插入100个数字
对于(int i=0;i<100;++i)
buf.向后推(rand());
//输出到循环缓冲区的cout内容
对于(int i=0;i cout我认为即使使用std::deque,在某些情况下也会有复制项的开销,因为std::deque本质上是数组的映射,所以std::list是消除复制开销的一个好主意
为了提高std::list遍历的性能,您可以实现一个内存池,以便std::list将从主干及其空间位置分配内存进行缓存。两者都不是。您的集合具有固定大小,并且std::array
就足够了
您实现的数据结构称为环形缓冲区。要实现它,您需要创建一个数组并跟踪当前第一个元素的偏移量
当您添加一个元素将一个项目推出缓冲区时(即,当您删除第一个元素时),您将增加偏移量
要获取缓冲区中的元素,请添加索引和偏移量,并取其模和缓冲区长度。如果可以使用Boost,请尝试:
// Create a circular buffer with a capacity for 5 integers.
boost::circular_buffer<int> cb(5);
// Insert elements into the buffer.
cb.push_back(3);
cb.push_back(24);
cb.push_back(51);
cb.push_back(62);
cb.push_back(37);
int a = cb[0]; // a == 3
int b = cb[1]; // b == 24
int c = cb[2]; // c == 51
// The buffer is full now, so pushing subsequent
// elements will overwrite the front-most elements.
cb.push_back(8); // overwrite 3 with 8
cb.push_back(12); // overwrite 24 with 12
// The buffer now contains 51, 62, 37, 8, 12.
// Elements can be popped from either the front or the back.
cb.pop_back(); // 12 is removed
cb.pop_front(); // 51 is removed
#include <iterator>
template<typename Container>
class CircularBuffer
{
public:
using iterator = typename Container::iterator;
using value_type = typename Container::value_type;
private:
Container _container;
iterator _pos;
public:
CircularBuffer() : _pos(std::begin(_container)) {}
public:
value_type& operator*() const { return *_pos; }
CircularBuffer& operator++() { ++_pos ; if (_pos == std::end(_container)) _pos = std::begin(_container); return *this; }
CircularBuffer& operator--() { if (_pos == std::begin(_container)) _pos = std::end(_container); --_pos; return *this; }
};
#include <iostream>
#include <array>
int main()
{
CircularBuffer<std::array<int,5>> buf;
*buf = 1; ++buf;
*buf = 2; ++buf;
*buf = 3; ++buf;
*buf = 4; ++buf;
*buf = 5; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; ++buf;
std::cout << *buf << " "; --buf;
std::cout << *buf << " "; --buf;
std::cout << *buf << " "; --buf;
std::cout << *buf << " "; --buf;
std::cout << *buf << " "; --buf;
std::cout << *buf << " "; --buf;
std::cout << std::endl;
}
g++ -std=c++17 -O2 -Wall -Wextra -pedantic -Werror
#ifndef RING_DEQUEUE_H
#define RING_DEQUEUE_H
#include <memory>
#include <type_traits>
#include <limits>
template <typename T, size_t N>
class ring_dequeue {
private:
static_assert(N <= std::numeric_limits<size_t>::max() / 2 &&
N <= std::numeric_limits<size_t>::max() / sizeof(T),
"size of ring_dequeue is too large");
using alloc_traits = std::allocator_traits<std::allocator<T>>;
public:
using value_type = T;
using reference = T&;
using const_reference = const T&;
using difference_type = ssize_t;
using size_type = size_t;
ring_dequeue() = default;
// Disable copy and move constructors for now - if iterators are
// implemented later, then those could be delegated to the InputIterator
// constructor below (using the std::move_iterator adaptor for the move
// constructor case).
ring_dequeue(const ring_dequeue&) = delete;
ring_dequeue(ring_dequeue&&) = delete;
ring_dequeue& operator=(const ring_dequeue&) = delete;
ring_dequeue& operator=(ring_dequeue&&) = delete;
template <typename InputIterator>
ring_dequeue(InputIterator begin, InputIterator end) {
while (m_tailIndex < N && begin != end) {
alloc_traits::construct(m_alloc, reinterpret_cast<T*>(m_buf) + m_tailIndex,
*begin);
++m_tailIndex;
++begin;
}
if (begin != end)
throw std::logic_error("Input range too long");
}
ring_dequeue(std::initializer_list<T> il) :
ring_dequeue(il.begin(), il.end()) { }
~ring_dequeue() noexcept(std::is_nothrow_destructible<T>::value) {
while (m_headIndex < m_tailIndex) {
alloc_traits::destroy(m_alloc, elemPtr(m_headIndex));
m_headIndex++;
}
}
size_t size() const {
return m_tailIndex - m_headIndex;
}
size_t max_size() const {
return N;
}
bool empty() const {
return m_headIndex == m_tailIndex;
}
bool full() const {
return m_headIndex + N == m_tailIndex;
}
template <typename... Args>
void emplace_front(Args&&... args) {
if (full())
throw std::logic_error("ring_dequeue full");
bool wasAtZero = (m_headIndex == 0);
auto newHeadIndex = wasAtZero ? (N - 1) : (m_headIndex - 1);
alloc_traits::construct(m_alloc, elemPtr(newHeadIndex),
std::forward<Args>(args)...);
m_headIndex = newHeadIndex;
if (wasAtZero)
m_tailIndex += N;
}
void push_front(const T& x) {
emplace_front(x);
}
void push_front(T&& x) {
emplace_front(std::move(x));
}
template <typename... Args>
void emplace_back(Args&&... args) {
if (full())
throw std::logic_error("ring_dequeue full");
alloc_traits::construct(m_alloc, elemPtr(m_tailIndex),
std::forward<Args>(args)...);
++m_tailIndex;
}
void push_back(const T& x) {
emplace_back(x);
}
void push_back(T&& x) {
emplace_back(std::move(x));
}
T& front() {
if (empty())
throw std::logic_error("ring_dequeue empty");
return *elemPtr(m_headIndex);
}
const T& front() const {
if (empty())
throw std::logic_error("ring_dequeue empty");
return *elemPtr(m_headIndex);
}
void remove_front() {
if (empty())
throw std::logic_error("ring_dequeue empty");
alloc_traits::destroy(m_alloc, elemPtr(m_headIndex));
++m_headIndex;
if (m_headIndex == N) {
m_headIndex = 0;
m_tailIndex -= N;
}
}
T pop_front() {
T result = std::move(front());
remove_front();
return result;
}
T& back() {
if (empty())
throw std::logic_error("ring_dequeue empty");
return *elemPtr(m_tailIndex - 1);
}
const T& back() const {
if (empty())
throw std::logic_error("ring_dequeue empty");
return *elemPtr(m_tailIndex - 1);
}
void remove_back() {
if (empty())
throw std::logic_error("ring_dequeue empty");
alloc_traits::destroy(m_alloc, elemPtr(m_tailIndex - 1));
--m_tailIndex;
}
T pop_back() {
T result = std::move(back());
remove_back();
return result;
}
private:
alignas(T) char m_buf[N * sizeof(T)];
size_t m_headIndex = 0;
size_t m_tailIndex = 0;
std::allocator<T> m_alloc;
const T* elemPtr(size_t index) const {
if (index >= N)
index -= N;
return reinterpret_cast<const T*>(m_buf) + index;
}
T* elemPtr(size_t index) {
if (index >= N)
index -= N;
return reinterpret_cast<T*>(m_buf) + index;
}
};
#endif