连接两个向量的最佳方法是什么?


189

我正在使用多重踩踏,并且想要合并结果。例如:

std::vector<int> A;
std::vector<int> B;
std::vector<int> AB;

我希望AB必须按此顺序处理A的内容和B的内容。做这样的事情最有效的方法是什么?


1
如果在使用大型容器时要提高效率,则使用list可能会更有效,在list中,您可以使用多个指针操作将它们拼接在一起。但是列表有空间开销(考虑使用单个链接列表)。
Kemin Zhou

Answers:


317
AB.reserve( A.size() + B.size() ); // preallocate memory
AB.insert( AB.end(), A.begin(), A.end() );
AB.insert( AB.end(), B.begin(), B.end() );

6
谢谢!不会想到储备的。
jmasterx

10
它应该复制每个元素,所以它是O(n)
Kirill V. Lyadvinsky 2013年

1
不确定是否要问一个新问题,但是当考虑移动语义时可以改善这个答案吗?有什么方法可以期望/指示编译器执行单个内存移动,而不是遍历所有元素?
Broes De Cat

2
@boycy号。将一个元素push_back摊销固定的时间。向后推n个元素是O(n)
康拉德·林登巴赫

1
@Konrad我不是暗指,但感谢您的澄清。请注意,插入操作的复杂性永远不会根据要插入的元素数量给出(总是给出O(n)),而取决于容器中已经存在的元素数量,因为这可以衡量其可伸缩性。
博西

64

这正是成员函数std::vector::insert的用途

std::vector<int> AB = A;
AB.insert(AB.end(), B.begin(), B.end());

4
@Nick:相比什么慢?
GManNickG 2010年

2
也许它检查每个元素插入是否有足够的空间?事先使用储备金可以加快速度。
RvdK

10
@Nick:如果每个现代的stdlib实现都专注insert于随机访问迭代器并预先预留,我不会感到惊讶。
GManNickG 2010年

1
@Gman:这很公平,因为我们知道源也是向量(迭代器的distance复杂度为O(1))。尽管如此,insert当您经常通过预先计划可以做得更好时,仍然需要牢记性能保证。
尼克·巴斯汀

2
@RvdK检查空间仅是几条指令:负载容量,与大小比较,条件跳转;在大多数情况下,所有这些费用都可以忽略不计。由于size < capacity大多数情况下,分支预测可能会导致非重分配分支的指令进入指令管道,从而将分支引起的等待时间降至最低,但迭代次数较少。这假设一个好的向量实现,再加上CPU指令流水线和[好的]分支预测,但是对于现代工具链和台式机来说,这是相当可靠的假设。虽然不了解智能手机..
博西

27

取决于您是否真的需要物理连接这两个向量,还是要出于迭代的目的给出连接的外观。boost :: join函数

http://www.boost.org/doc/libs/1_43_0/libs/range/doc/html/range/reference/utilities/join.html

会给你这个。

std::vector<int> v0;
v0.push_back(1);
v0.push_back(2);
v0.push_back(3);

std::vector<int> v1;
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
...

BOOST_FOREACH(const int & i, boost::join(v0, v1)){
    cout << i << endl;
}

应该给你

1
2
3
4
5
6

注意boost :: join不会将两个向量复制到新容器中,而是会生成一对覆盖两个容器范围的迭代器(范围)。将有一些性能开销,但可能比首先将所有数据复制到新容器要少。


1
好主意。经过一会儿的思考,我意识到不使用boost库也可以实现此目标。我已经发布了答案,解释了如何。
罗纳德·索扎

11

根据Kiril V. Lyadvinsky的答案,我制作了一个新版本。此代码段使用模板和重载。有了它,您可以编写vector3 = vector1 + vector2vector4 += vector3。希望能有所帮助。

template <typename T>
std::vector<T> operator+(const std::vector<T> &A, const std::vector<T> &B)
{
    std::vector<T> AB;
    AB.reserve(A.size() + B.size());                // preallocate memory
    AB.insert(AB.end(), A.begin(), A.end());        // add A;
    AB.insert(AB.end(), B.begin(), B.end());        // add B;
    return AB;
}

template <typename T>
std::vector<T> &operator+=(std::vector<T> &A, const std::vector<T> &B)
{
    A.reserve(A.size() + B.size());                // preallocate memory without erase original data
    A.insert(A.end(), B.begin(), B.end());         // add B;
    return A;                                        // here A could be named AB
}

1
您的意思是将每个向量的元素彼此相加吗?还是您要附加?现在已经很清楚了,但是接下来的五年。如果含义不明确,则不应重载运算符。
SR

2
@SR我的意思是串联。我三年前写了这个答案。我仍然知道这意味着什么。没问题。如果C ++可以提供自己的重载,那就更好了。(并且::采取了;)
aloisdg移至codidact.com,2017年

绝对不清楚,这通常v1 + v2不代表加法。
Apollys支持Monica


替代方法是@像在F#中那样使用
aloisdg移至codidact.com,

5

按照Bradgonesurfing的回答,很多时候并不需要真的将两个向量(O(n))串联起来,而是像对待它们一样将它们当作串联(O(1))来使用。如果是这种情况,则无需Boost库即可完成。

诀窍是创建一个向量代理:一个包装器类,该类处理对两个向量的引用,在外部将其视为单个连续的向量。

用法

std::vector<int> A{ 1, 2, 3, 4, 5};
std::vector<int> B{ 10, 20, 30 };

VecProxy<int> AB(A, B);  // ----> O(1). No copies performed.

for (size_t i = 0; i < AB.size(); ++i)
    std::cout << AB[i] << " ";  // 1 2 3 4 5 10 20 30

实施方式

template <class T>
class VecProxy {
private:
    std::vector<T>& v1, v2;
public:
    VecProxy(std::vector<T>& ref1, std::vector<T>& ref2) : v1(ref1), v2(ref2) {}
    const T& operator[](const size_t& i) const;
    const size_t size() const;
};

template <class T>
const T& VecProxy<T>::operator[](const size_t& i) const{
    return (i < v1.size()) ? v1[i] : v2[i - v1.size()];
};

template <class T>
const size_t VecProxy<T>::size() const { return v1.size() + v2.size(); };

主要好处

创建它的时间为O(1)(恒定时间),并且具有最少的额外内存分配。

一些需要考虑的东西

  • 仅当您真正知道在处理引用时在做什么时,才应该这样做该解决方案旨在解决所提出问题的特定目的,并且效果很好。如果您不确定引用的工作方式,则在其他任何上下文中使用它可能导致意外行为。
  • 在这个例子中,AB确实提供一种非const访问运算符([])。随意包含它,但要记住:由于AB包含引用,因此为其赋值还会影响A和/或B中的原始元素。无论这是否是理想的功能,这是一个特定于应用程序的问题,您应该仔细考虑。
  • 直接对A或B进行的任何更改(如分配值,排序等)也将“修改” AB。这并不一定很糟糕(实际上,它可能非常方便:AB不需要显式更新以使其自身与A和B保持同步),但是肯定是一种必须意识到的行为。重要的例外:将A和/或B的大小调整为更大的值可能会导致它们重新分配到内存中(需要连续的空间),这反过来会使AB无效。
  • 由于对元素的每次访问都必须经过测试(即“ i <v1.size()”),因此,VecProxy访问时间虽然恒定,但也比向量要慢一些。
  • 这种方法可以推广到n个向量。我没有尝试过,但这应该没什么大不了的。

2

尚未提及的另一种简单变体:

copy(A.begin(),A.end(),std::back_inserter(AB));
copy(B.begin(),B.end(),std::back_inserter(AB));

并使用合并算法:

#include <algorithm> #include <vector> #include <iterator> #include <iostream> #include <sstream> #include <string> template<template<typename, typename...> class Container, class T> std::string toString(const Container<T>& v) { std::stringstream ss; std::copy(v.begin(), v.end(), std::ostream_iterator<T>(ss, "")); return ss.str(); }; int main() { std::vector<int> A(10); std::vector<int> B(5); //zero filled std::vector<int> AB(15); std::for_each(A.begin(), A.end(), [](int& f)->void { f = rand() % 100; }); std::cout << "before merge: " << toString(A) << "\n"; std::cout << "before merge: " << toString(B) << "\n"; merge(B.begin(),B.end(), begin(A), end(A), AB.begin(), [](int&,int&)->bool {}); std::cout << "after merge: " << toString(AB) << "\n"; return 1; }


-1

如果您的向量已排序*,请从<algorithm>中检出set_union

set_union(A.begin(), A.end(), B.begin(), B.end(), AB.begin());

链接中有一个更详尽的示例

*感谢rlbond


4
而且,它与直接追加的功能不同-输出范围中的元素是唯一的,这可能不是OP想要的(它们甚至可能不具有可比性)。当然,这不是最有效的方法。
彼得

-1

所有解决方案都是正确的,但是我发现编写一个函数来实现这一点比较容易。像这样:

template <class T1, class T2>
void ContainerInsert(T1 t1, T2 t2)
{
    t1->insert(t1->end(), t2->begin(), t2->end());
}

这样,您可以避免这样的临时放置:

ContainerInsert(vec, GetSomeVector());
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.