Cách tốt nhất để nối hai vectơ là gì?


189

Tôi đang sử dụng đa nhiệm và muốn hợp nhất các kết quả. Ví dụ:

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

Tôi muốn AB phải có nội dung của A và nội dung của B theo thứ tự đó. Cách hiệu quả nhất để làm một cái gì đó như thế này là gì?


1
Nếu tìm kiếm hiệu quả khi bạn làm việc với các thùng chứa kích thước lớn, có thể sẽ hiệu quả hơn khi sử dụng danh sách, nơi bạn có thể nối cái này với cái kia bằng một vài thao tác con trỏ. Nhưng danh sách có không gian trên không (xem xét sử dụng danh sách liên kết đơn).
Kemin Zhou

Câu trả lời:


316
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
Cảm ơn! Sẽ không nghĩ đến dự trữ.
jmasterx

10
cần sao chép từng phần tử, vì vậy đó là O (n)
Kirill V. Lyadvinsky

1
Không chắc có nên hỏi một câu hỏi mới hay không, nhưng câu trả lời này có thể được cải thiện khi đưa ngữ nghĩa di chuyển vào tài khoản không? Có một số cách tôi có thể mong đợi / hướng dẫn trình biên dịch thực hiện một lần di chuyển bộ nhớ thay vì lặp qua tất cả các phần tử?
Broes De Cat

2
@boycy Không. Nó được khấu hao theo thời gian liên tục để đẩy_back một phần tử. Để đẩy lùi n phần tử là O (n)
Konrad Lindenbach

1
@Konrad Tôi không ngụ ý khác, nhưng cảm ơn đã làm rõ. Lưu ý rằng độ phức tạp của thao tác chèn không bao giờ được đưa ra về mặt số lượng phần tử được chèn - sẽ luôn cung cấp cho O (n) - nhưng về mặt số lượng phần tử đã có trong vùng chứa, vì điều đó cung cấp thước đo khả năng mở rộng của nó .
boycy 14/03/2016

64

Đây chính xác std::vector::insertlà chức năng của thành viên dành cho

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

4
@Nick: Chậm so với cái gì?
GManNickG

2
Có lẽ nó kiểm tra đủ không gian trên mỗi phần tử chèn? Sử dụng dự trữ trước sẽ tăng tốc nó.
RvdK

10
@Nick: Tôi sẽ không ngạc nhiên nếu mọi triển khai stdlib hiện đại chuyên insertvề các trình vòng lặp truy cập ngẫu nhiên và được đặt trước.
GManNickG

1
@Gman: Đó là một điểm công bằng vì chúng ta biết rằng nguồn cũng là một vectơ (trong đó iterator distancecó độ phức tạp O (1)). Tuy nhiên, đảm bảo hiệu suất insertlà điều cần lưu tâm khi bạn thường có thể làm tốt hơn bằng cách lên kế hoạch trước.
Nick Bastin

2
@RvdK kiểm tra không gian chỉ là một vài hướng dẫn: khả năng tải, so sánh với kích thước, bước nhảy có điều kiện; tất cả trong số đó là chi phí không đáng kể cho hầu hết các trường hợp. Vì size < capacityhầu hết thời gian, dự đoán nhánh có thể sẽ khiến các lệnh của nhánh không phân bổ lại nằm trong đường dẫn lệnh, giảm thiểu độ trễ do nhánh gây ra trừ số lần lặp thấp. Điều này giả định việc triển khai vectơ tốt, cộng với đường dẫn hướng dẫn CPU & dự đoán nhánh [tốt], nhưng đó là những giả định khá đáng tin cậy cho một máy tính và máy tính để bàn hiện đại. Không biết về điện thoại thông minh mặc dù ..
boycy 24/215

27

Phụ thuộc vào việc bạn có thực sự cần ghép nối hai vectơ hay bạn muốn đưa ra sự xuất hiện của phép nối vì lợi ích của việc lặp lại. Hàm boost :: tham gia

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

sẽ cung cấp cho bạn điều này.

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;
}

nên cho bạn

1
2
3
4
5
6

Lưu ý boost :: tham gia không sao chép hai vectơ vào một thùng chứa mới mà tạo ra một cặp vòng lặp (phạm vi) bao trùm khoảng nhịp của cả hai container. Sẽ có một số chi phí hiệu năng nhưng có thể ít hơn là sao chép tất cả dữ liệu vào một thùng chứa mới trước.


1
Ý kiến ​​hay. Sau khi suy nghĩ một lúc, tôi nhận ra mục tiêu này cũng có thể được thực hiện mà không cần sử dụng các thư viện boost. Tôi đã đăng một câu trả lời giải thích làm thế nào.
Ronald Souza

11

Dựa trên câu trả lời của Kiril V. Lyadvinsky , tôi đã tạo ra một phiên bản mới. Đoạn mã này sử dụng mẫu và quá tải. Với nó, bạn có thể viết vector3 = vector1 + vector2vector4 += vector3. Hy vọng nó có thể giúp đỡ.

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
Bạn có nghĩa là để thêm các yếu tố của mỗi vector cho nhau? Hoặc bạn có nghĩa là để nối? Điều này đã rõ ràng nhưng trong 5 năm tới ..? Bạn không nên quá tải toán tử nếu ý nghĩa không rõ ràng.
SR

2
@SR Ý tôi là nói ngắn gọn. Tôi đã viết câu trả lời này 3 năm trước. Tôi vẫn biết ý nghĩa của nó. Không có vấn đề ở đó. Nếu C ++ có thể cung cấp quá tải của riêng nó, nó sẽ còn tốt hơn nữa. (và có ::được thực hiện;)
aloisdg chuyển đến codidact.com

Nói chung là không rõ ràng mà v1 + v2không đại diện cho bổ sung.
Apollys hỗ trợ Monica


Thay thế sẽ là sử dụng @như trong F #
aloisdg chuyển sang codidact.com

5

Theo hướng trả lời của Bradgonesurfing, nhiều lần người ta không thực sự cần ghép hai vectơ (O (n)), mà thay vào đó chỉ làm việc với chúng như thể chúng được nối (O (1)) . Nếu đây là trường hợp của bạn, nó có thể được thực hiện mà không cần thư viện Boost.

Bí quyết là tạo một proxy vector: một lớp bao bọc thao tác các tham chiếu đến cả hai vectơ, được xem bên ngoài như là một lớp duy nhất, liền kề nhau.

SỬ DỤNG

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

THỰC HIỆN

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(); };

LỢI ÍCH CHÍNH

Đó là O (1) (thời gian không đổi) để tạo ra nó và với sự phân bổ bộ nhớ bổ sung tối thiểu.

MỘT SỐ NHÂN VIÊN ĐỂ ĐƯỢC TƯ VẤN

  • Bạn chỉ nên đi cho nó nếu bạn thực sự biết những gì bạn đang làm khi xử lý các tài liệu tham khảo . Giải pháp này được dành cho mục đích cụ thể của câu hỏi được thực hiện, mà nó hoạt động khá tốt . Để sử dụng nó trong bất kỳ bối cảnh nào khác có thể dẫn đến hành vi không mong muốn nếu bạn không chắc chắn về cách thức tham chiếu hoạt động.
  • Trong ví dụ này, AB không cung cấp toán tử truy cập không const ([]). Vui lòng bao gồm nó, nhưng hãy nhớ: vì AB chứa các tham chiếu, để gán cho nó các giá trị cũng sẽ ảnh hưởng đến các phần tử gốc trong A và / hoặc B. Đây có phải là một tính năng mong muốn hay không, đó là một câu hỏi dành riêng cho ứng dụng Xem xét cẩn thận.
  • Mọi thay đổi được thực hiện trực tiếp cho A hoặc B (như gán giá trị, sắp xếp, v.v.) cũng sẽ "sửa đổi" AB. Điều này không hẳn là xấu (thực ra, nó có thể rất tiện dụng: AB không bao giờ cần phải cập nhật rõ ràng để giữ cho nó được đồng bộ hóa với cả A và B), nhưng đó chắc chắn là một hành vi người ta phải nhận thức được. Ngoại lệ quan trọng: để thay đổi kích thước A và / hoặc B thành sth lớn hơn có thể khiến chúng được phân bổ lại trong bộ nhớ (đối với nhu cầu không gian liền kề) và điều này sẽ vô hiệu hóa AB.
  • Bởi vì mọi quyền truy cập vào một phần tử đều được kiểm tra trước (cụ thể là "i <v1.size ()"), thời gian truy cập VecProxy, mặc dù không đổi, cũng chậm hơn một chút so với vectơ.
  • Cách tiếp cận này có thể được khái quát cho n vectơ. Tôi đã không thử, nhưng nó không phải là một vấn đề lớn.

2

Một biến thể đơn giản hơn chưa được đề cập:

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

Và sử dụng thuật toán hợp nhất:

#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

Nếu các vectơ của bạn được sắp xếp *, hãy xem set_union từ <Thuật toán>.

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

Có một ví dụ kỹ lưỡng hơn trong liên kết

* cảm ơn


4
Ngoài ra, nó không làm điều tương tự như một phụ lục thẳng - các yếu tố trong phạm vi đầu ra là duy nhất, có thể không phải là điều OP muốn (chúng thậm chí có thể không so sánh được). Đó chắc chắn không phải là cách hiệu quả nhất để làm điều đó.
Peter

-1

Tất cả các giải pháp đều đúng, nhưng tôi thấy dễ dàng hơn chỉ cần viết một hàm để thực hiện điều này. như thế này:

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

Bằng cách đó bạn có thể tránh được vị trí tạm thời như thế này:

ContainerInsert(vec, GetSomeVector());
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.