有没有类似数组的数据结构可以在两侧都增加大小

Is there any array-like data structure that can grow in size on both sides?

本文关键字:增加 数组 数据结构 有没有      更新时间:2023-10-16

我是一名从事高性能计算课程小项目的学生,因此效率是一个关键问题。

假设我有一个N个浮点数的向量,我想去掉最小的N个元素和最大的N个元件。有两种简单的方法:

A

sort in ascending order    // O(NlogN)
remove the last n elements // O(1)
invert elements order      // O(N)
remove the last n elements // O(1)

B

sort in ascending order     // O(NlogN)
remove the last n elements  // O(1)
remove the first n elements // O(N)

在A中,反转元素顺序需要交换所有元素,而在B中,移除前n个元素需要移动所有其他元素以占据空的位置。使用std::remove也会出现同样的问题。

如果我可以免费去除前n个元素,那么解决方案B会更便宜。如果不是有一个向量,即在vector::end()之后有一些空空间的数组,而是在vector::begin()之前有一个有一些空闲空间的容器,那么这应该很容易实现。

所以问题是:在一些库(STL、Boost)中,是否已经存在类似的数组(即连续内存,没有链表),允许在数组两侧插入/删除O(1)?

如果没有,你认为有比创建这样的数据结构更好的解决方案吗?

您是否想过将std::partition与以下示例中的自定义函子一起使用:

#include <iostream>
#include <vector>
#include <algorithm>
template<typename T>
class greaterLess {
T low;
T up;
public:
greaterLess(T const &l, T const &u) : low(l), up(u) {}
bool operator()(T const &e) { return !(e < low || e > up); }
};
int main()
{
std::vector<double> v{2.0, 1.2, 3.2, 0.3, 5.9, 6.0, 4.3};
auto it = std::partition(v.begin(), v.end(), greaterLess<double>(2.0, 5.0));
v.erase(it, v.end());
for(auto i : v) std::cout << i << " ";
std::cout << std::endl;
return 0;
}

通过这种方式,您可以在O(N)时间内从向量中擦除元素。

尝试boost::circular _buffer:

它支持随机访问迭代器、在缓冲区开始或结束时的恒定时间插入和擦除操作以及与std算法的互操作性。

查看了源代码后,数据似乎(而且只是合乎逻辑的)被保存为一个连续的内存块。

需要注意的一点是,缓冲区具有固定的容量,在耗尽它之后,元素将被覆盖。您可以自己检测这种情况并手动调整缓冲区的大小,也可以使用声明容量巨大的boost::circular_buffer_space_optimized,因为如果不需要,它不会分配它。

要收缩&在两端生长一个向量,您可以使用切片的想法,如果需要高效生长,则保留额外的内存以提前扩展到前端和后端。

简单地说,为first&最后的元素和适当大小的向量,以在存储的浮点的底层块上创建数据窗口。C++类可以提供内联函数,例如删除项、寻址到数组、查找第n个最大值、向下或向上移动切片值以插入保持排序顺序的新元素。如果没有可用的备用元素,则动态分配一个新的更大的浮动存储,允许以阵列拷贝为代价继续增长。

循环缓冲区被设计为FIFO,在末尾添加新元素,在前面删除新元素,并且不允许在中间插入,自定义类也可以(平凡地)支持不同于0..N-1 的数组下标值

由于内存的局部性,避免了指针链造成的过度间接性,以及在现代处理器上进行下标计算的流水线操作,尽管插入时会复制元素,但基于数组(或向量)的解决方案可能是最有效的。Deque是合适的,但它不能保证连续存储。

其他补充信息。研究提供切片的课程,找到一些可行的替代方案进行评估:

A) std::slice,它使用slice_arraysB) 助推级范围

希望这是你所希望的那种特定信息,一般来说,一个更简单、更清晰的解决方案比一个棘手的解决方案更容易维护。我希望排序数据集上的切片和范围非常常见,例如过滤实验数据,其中"异常值"被排除为错误读数。

我认为一个好的解决方案,实际上应该是-O(NlogN),2xO(1),任何二进制搜索O(logN+1)用于过滤外围值,而不是删除固定数量的小值或大值;重要的是,"O"相对较快,有时O(1)算法在实践中对于N的实际值可能比O(N)算法慢。

作为@40two答案的补充,在对数组进行分区之前,您需要找到分区枢轴,也就是说,您需要在未排序的数组中找到第n个最小的数字和第n个最大的数字。在SO中有一个讨论:如何在未排序数组中找到第k个最大数

有几种算法可以解决这个问题。有些是确定性的O(N)-on,其中一个是在寻找中位数(中位数)时的变化。存在一些具有O(N)平均情况的非确定性算法。找到这些算法的一本很好的参考书是《算法导论》。也在这样的书中

因此,最终,您的代码将在O(N)时间中运行