Làm cách nào để tôi có thể sắp xếp hai vectơ theo cùng một cách, với tiêu chí chỉ sử dụng một trong các vectơ?


84

Làm cách nào để tôi có thể sắp xếp hai vectơ theo cùng một cách, với tiêu chí chỉ sử dụng một trong các vectơ?

Ví dụ: giả sử tôi có hai vectơ có cùng kích thước:

vector<MyObject> vectorA;
vector<int> vectorB;

Sau đó tôi sắp xếp vectorAbằng một số chức năng so sánh. Việc sắp xếp đó có thứ tự vectorA. Làm thế nào tôi có thể áp dụng cùng một cách sắp xếp lại vectorB?


Một tùy chọn là tạo một cấu trúc:

struct ExampleStruct {
    MyObject mo;
    int i;
};

và sau đó sắp xếp một vectơ có chứa nội dung của vectorAvectorBnén thành một vectơ duy nhất:

// vectorC[i] is vectorA[i] and vectorB[i] combined
vector<ExampleStruct> vectorC;

Đây có vẻ không phải là một giải pháp lý tưởng. Có các tùy chọn khác, đặc biệt là trong C ++ 11?


Bạn có thể cung cấp một ví dụ với một số đầu vào và đầu ra mong muốn tương ứng không? Tôi gặp rắc rối hiểu câu hỏi
Andy Prowl

Tôi nghĩ anh ấy muốn (một cách hiệu quả) sắp xếp nội dung vectorAvectorBcả theo nội dung của vectorB.
Mooing Duck

Tôi nghĩ rằng câu hỏi này gần như trùng lặp nếu không phải là một bản sao chính xác
Vịt kêu lên

Còn việc sắp xếp một vectơ thứ ba (của các chỉ số 0, ... vectorA.size ()), dựa trên các giá trị trong vectơA và "áp dụng" các chỉ số đó trên vectơB thì sao? Ví dụ như trong stackoverflow.com/a/10581051/417197
André

Cá nhân, tôi muốn có một vector<pair<MyObject, int>>. Sau đó, bạn sẽ không phải lo lắng về việc hai danh sách bị mất đồng bộ; một sắp xếp sắp xếp lại đồng thời cả hai tập dữ liệu. Và không có thêm cấu trúc nào để phải viết.
cHao

Câu trả lời:


117

Tìm một hoán vị sắp xếp

Đưa ra một std::vector<T>và một so sánh cho T's, chúng tôi muốn có thể tìm thấy hoán vị mà bạn sẽ sử dụng nếu bạn sắp xếp vectơ bằng cách sử dụng so sánh này.

template <typename T, typename Compare>
std::vector<std::size_t> sort_permutation(
    const std::vector<T>& vec,
    Compare& compare)
{
    std::vector<std::size_t> p(vec.size());
    std::iota(p.begin(), p.end(), 0);
    std::sort(p.begin(), p.end(),
        [&](std::size_t i, std::size_t j){ return compare(vec[i], vec[j]); });
    return p;
}

Áp dụng một hoán vị sắp xếp

Cho trước một std::vector<T>hoán vị và một hoán vị, chúng ta muốn có thể xây dựng một std::vector<T>hoán vị mới được sắp xếp lại theo hoán vị.

template <typename T>
std::vector<T> apply_permutation(
    const std::vector<T>& vec,
    const std::vector<std::size_t>& p)
{
    std::vector<T> sorted_vec(vec.size());
    std::transform(p.begin(), p.end(), sorted_vec.begin(),
        [&](std::size_t i){ return vec[i]; });
    return sorted_vec;
}

Tất nhiên, bạn có thể sửa đổi apply_permutationđể thay đổi vectơ mà bạn cung cấp cho nó thay vì trả lại một bản sao được sắp xếp mới. Cách tiếp cận này vẫn là độ phức tạp thời gian tuyến tính và sử dụng một bit cho mỗi mục trong vectơ của bạn. Về mặt lý thuyết, nó vẫn là độ phức tạp không gian tuyến tính; nhưng, trong thực tế, khi sizeof(T)lớn, việc giảm sử dụng bộ nhớ có thể rất đáng kể. ( Xem chi tiết )

template <typename T>
void apply_permutation_in_place(
    std::vector<T>& vec,
    const std::vector<std::size_t>& p)
{
    std::vector<bool> done(vec.size());
    for (std::size_t i = 0; i < vec.size(); ++i)
    {
        if (done[i])
        {
            continue;
        }
        done[i] = true;
        std::size_t prev_j = i;
        std::size_t j = p[i];
        while (i != j)
        {
            std::swap(vec[prev_j], vec[j]);
            done[j] = true;
            prev_j = j;
            j = p[j];
        }
    }
}

Thí dụ

vector<MyObject> vectorA;
vector<int> vectorB;

auto p = sort_permutation(vectorA,
    [](T const& a, T const& b){ /*some comparison*/ });

vectorA = apply_permutation(vectorA, p);
vectorB = apply_permutation(vectorB, p);

Tài nguyên


4
Trong trình biên dịch của tôi, tôi đã phải thay đổi "So sánh & so sánh" thành "So sánh so sánh".
SmallChess

2
Tôi cũng cần bao gồm tiêu đề <numeric> để chạy hàm std :: iota (vs2012).
Smith,

1
" Tất nhiên, bạn có thể sửa đổi apply_permutationđể thay đổi vectơ mà bạn cung cấp cho nó hơn là trả lại một bản sao được sắp xếp mới. " Tôi sẽ thấy điều này, ít nhất về mặt khái niệm, là một bổ sung hữu ích cho câu trả lời. Bạn có thực hiện điều này theo cách giống như apply_permutationchính nó không?
ildjarn

2
@ildjarn Tôi đã cập nhật câu trả lời theo đề xuất của bạn.
Timothy Shields

2
Cảm ơn vì đoạn trích xuất sắc! Compare& comparetuy nhiên, khai báo sort_permutation phải là const. Nếu không, bạn không thể sử dụng std :: less <T> hoặc tương tự.
kotakotakota

9

Với range-v3 , thật đơn giản, hãy sắp xếp một chế độ xem zip:

std::vector<MyObject> vectorA = /*..*/;
std::vector<int> vectorB = /*..*/;

ranges::v3::sort(ranges::view::zip(vectorA, vectorB));

hoặc sử dụng rõ ràng phép chiếu:

ranges::v3::sort(ranges::view::zip(vectorA, vectorB),
                 std::less<>{},
                 [](const auto& t) -> decltype(auto) { return std::get<0>(t); });

Bản giới thiệu


Đã thử trên VS2017, không có gì được biên dịch hoặc hoạt động, bất kể tôi đã thử gì. Thư viện này hoạt động trên VS2019, GCC và LLVM.
Contango

4

Tôi muốn đóng góp với một tiện ích mở rộng mà tôi đã nghĩ ra. Mục đích là có thể sắp xếp nhiều vectơ cùng lúc bằng một cú pháp đơn giản.

sortVectorsAscending(criteriaVec, vec1, vec2, ...)

Thuật toán tương tự như thuật toán mà Timothy đề xuất nhưng sử dụng các mẫu khác nhau , vì vậy chúng ta có thể sắp xếp nhiều vectơ thuộc các loại tùy ý cùng một lúc.

Đây là đoạn mã:

template <typename T, typename Compare>
void getSortPermutation(
    std::vector<unsigned>& out,
    const std::vector<T>& v,
    Compare compare = std::less<T>())
{
    out.resize(v.size());
    std::iota(out.begin(), out.end(), 0);

    std::sort(out.begin(), out.end(),
        [&](unsigned i, unsigned j){ return compare(v[i], v[j]); });
}

template <typename T>
void applyPermutation(
    const std::vector<unsigned>& order,
    std::vector<T>& t)
{
    assert(order.size() == t.size());
    std::vector<T> st(t.size());
    for(unsigned i=0; i<t.size(); i++)
    {
        st[i] = t[order[i]];
    }
    t = st;
}

template <typename T, typename... S>
void applyPermutation(
    const std::vector<unsigned>& order,
    std::vector<T>& t,
    std::vector<S>&... s)
{
    applyPermutation(order, t);
    applyPermutation(order, s...);
}

template<typename T, typename Compare, typename... SS>
void sortVectors(
    const std::vector<T>& t,
    Compare comp,
    std::vector<SS>&... ss)
{
    std::vector<unsigned> order;
    getSortPermutation(order, t, comp);
    applyPermutation(order, ss...);
}

// make less verbose for the usual ascending order
template<typename T, typename... SS>
void sortVectorsAscending(
    const std::vector<T>& t,
    std::vector<SS>&... ss)
{
    sortVectors(t, std::less<T>(), ss...);
}

Kiểm tra nó trong Ideone .

Tôi giải thích điều này tốt hơn một chút trong bài đăng trên blog này .


3

Sắp xếp tại chỗ sử dụng hoán vị

Tôi sẽ sử dụng một hoán vị như Timothy, mặc dù nếu dữ liệu của bạn quá lớn và bạn không muốn phân bổ thêm bộ nhớ cho vectơ đã sắp xếp, bạn nên thực hiện tại chỗ . Dưới đây là một ví dụ về sắp xếp tại chỗ O (n) (độ phức tạp tuyến tính) bằng cách sử dụng hoán vị :

Bí quyết là lấy hoán vị và hoán vị ngược để biết vị trí đặt dữ liệu bị ghi đè bởi bước sắp xếp cuối cùng.

template <class K, class T> 
void sortByKey(K * keys, T * data, size_t size){
    std::vector<size_t> p(size,0);
    std::vector<size_t> rp(size);
    std::vector<bool> sorted(size, false);
    size_t i = 0;

    // Sort
    std::iota(p.begin(), p.end(), 0);
    std::sort(p.begin(), p.end(),
                    [&](size_t i, size_t j){ return keys[i] < keys[j]; });

    // ----------- Apply permutation in-place ---------- //

    // Get reverse permutation item>position
    for (i = 0; i < size; ++i){
        rp[p[i]] = i;
    }

    i = 0;
    K savedKey;
    T savedData;
    while ( i < size){
        size_t pos = i;
        // Save This element;
        if ( ! sorted[pos] ){
            savedKey = keys[p[pos]];
            savedData = data[p[pos]];
        }
        while ( ! sorted[pos] ){
            // Hold item to be replaced
            K heldKey  = keys[pos];
            T heldData = data[pos];
            // Save where it should go
            size_t heldPos = rp[pos];

            // Replace 
            keys[pos] = savedKey;
            data[pos] = savedData;

            // Get last item to be the pivot
            savedKey = heldKey;
            savedData = heldData;

            // Mark this item as sorted
            sorted[pos] = true;

            // Go to the held item proper location
            pos = heldPos;
        }
        ++i;
    }
}

1
Mặc dù có vẻ như nó là O (N ^ 2), nhưng không phải vậy. Lệnh while bên trong chỉ được thực thi nếu mục không được sắp xếp. Như trong khi bên trong sắp xếp các dữ liệu, nó kinda bỏ qua bên ngoài trong khi lặp đi lặp lại ...
MTCS

2
  1. Tạo một vectơ của các cặp từ các vectơ riêng lẻ của bạn.
    khởi tạo vectơ của cặp
    Thêm vào vectơ của cặp

  2. Tạo bộ so sánh sắp xếp tùy chỉnh:
    Sắp xếp một vectơ của các đối tượng tùy chỉnh
    http://rosettacode.org/wiki/Sort_using_a_custom_comparator#C.2B.2B

  3. Sắp xếp các cặp vector của bạn.

  4. Tách các cặp vectơ của bạn thành các vectơ riêng lẻ.

  5. Đặt tất cả những thứ này vào một chức năng.

Mã:

std::vector<MyObject> vectorA;
std::vector<int> vectorB;

struct less_than_int
{
    inline bool operator() (const std::pair<MyObject,int>& a, const std::pair<MyObject,int>& b)
    {
        return (a.second < b.second);
    }
};

sortVecPair(vectorA, vectorB, less_than_int());

// make sure vectorA and vectorB are of the same size, before calling function
template <typename T, typename R, typename Compare>
sortVecPair(std::vector<T>& vecA, std::vector<R>& vecB, Compare cmp)
{

    std::vector<pair<T,R>> vecC;
    vecC.reserve(vecA.size());
    for(int i=0; i<vecA.size(); i++)
     {
        vecC.push_back(std::make_pair(vecA[i],vecB[i]);   
     }

    std::sort(vecC.begin(), vecC.end(), cmp);

    vecA.clear();
    vecB.clear();
    vecA.reserve(vecC.size());
    vecB.reserve(vecC.size());
    for(int i=0; i<vecC.size(); i++)
     {
        vecA.push_back(vecC[i].first);
        vecB.push_back(vecC[i].second);
     }
}

vectơ của các cặp đối chiếu?
Mooing Duck

Tôi hiểu ý bạn, nhưng các cặp tham chiếu sẽ không dễ dàng hoạt động.
ruben2020

1
@ ruben2020: Có thể , nhưng làm như vậy chỉ hỗ trợ vấn đề. Nếu bạn có hai phần dữ liệu đan xen nhau đủ để sắp xếp cái này sẽ sắp xếp cái kia, có vẻ như thứ bạn thực sự có là một đối tượng chưa được tích hợp.
cHao

1

Tôi giả sử rằng vectorA và vectorB có độ dài bằng nhau. Bạn có thể tạo một vectơ khác, hãy gọi nó là pos, trong đó:

pos[i] = the position of vectorA[i] after sorting phase

và sau đó, bạn có thể sắp xếp vectorB bằng cách sử dụng pos, tức là tạo vectorBsorted trong đó:

vectorBsorted[pos[i]] = vectorB[i]

và sau đó vectorBsorted được sắp xếp theo cùng một hoán vị của các chỉ số như vectorA là.


0

Tôi không chắc liệu điều này có hiệu quả không nhưng tôi sẽ sử dụng một cái gì đó như thế này. Ví dụ để sắp xếp hai vectơ, tôi sẽ sử dụng phương pháp sắp xếp bong bóng giảm dần và các cặp vectơ.

Đối với sắp xếp bong bóng giảm dần, tôi sẽ tạo một hàm yêu cầu một cặp vectơ.

void bubbleSort(vector< pair<MyObject,int> >& a)
{
    bool swapp = true;
    while (swapp) {
        int key;
        MyObject temp_obj;
        swapp = false;
        for (size_t i = 0; i < a.size() - 1; i++) {
            if (a[i].first < a[i + 1].first) {
                temp_obj = a[i].first;
                key = a[i].second;

                a[i].first = a[i + 1].first;
                a[i + 1].first = temp_obj;

                a[i].second = a[i + 1].second;
                a[i + 1].second = key;

                swapp = true;
            }
        }
    }
}

Sau đó, tôi sẽ đặt 2 giá trị vectơ của bạn thành một cặp vectơ. Nếu bạn có thể thêm các giá trị cùng lúc, hãy sử dụng giá trị này và gọi hàm sắp xếp bong bóng.

vector< pair<MyObject,int> > my_vector;

my_vector.push_back( pair<MyObject,int> (object_value,int_value));

bubbleSort(my_vector);

Nếu bạn muốn sử dụng các giá trị sau khi thêm vào 2 vectơ của mình, bạn có thể sử dụng giá trị này và gọi hàm sắp xếp bong bóng.

vector< pair<MyObject,int> > temp_vector;

for (size_t i = 0; i < vectorA.size(); i++) {
            temp_vector.push_back(pair<MyObject,int> (vectorA[i],vectorB[i]));
        }

bubbleSort(temp_vector);

Tôi hi vọng cái này giúp được. Trân trọng, Caner


0

Gần đây tôi đã viết một trình lặp zip thích hợp hoạt động với các thuật toán stl. Nó cho phép bạn tạo mã như thế này:

std::vector<int> a{3,1,4,2};
std::vector<std::string> b{"Alice","Bob","Charles","David"};

auto zip = Zip(a,b);
std::sort(zip.begin(), zip.end());

for (const auto & z: zip) std::cout << z << std::endl;

Nó được chứa trong một tiêu đề duy nhất và yêu cầu duy nhất là C ++ 17. Kiểm tra nó trên GitHub .

Ngoài ra còn có một bài đăng trên codereview chứa tất cả mã nguồn.


0

Dựa trên câu trả lời của Timothy Shields.
Với một chỉnh sửa nhỏ, apply_permutaionbạn có thể áp dụng hoán vị cho nhiều vectơ thuộc các loại khác nhau cùng một lúc bằng cách sử dụng biểu thức gấp.

template <typename T, typename... Ts>
void apply_permutation(const std::vector<size_t>& perm, std::vector<T>& v, std::vector<Ts>&... vs) {

    std::vector<bool> done(v.size());
    for(size_t i = 0; i < v.size(); ++i) {
        if(done[i]) continue;
        done[i] = true;
        size_t prev = i;
        size_t curr = perm[i];
        while(i != curr) {
            std::swap(v[prev], v[curr]);
            (std::swap(vs[prev], vs[curr]), ...);
            done[curr] = true;
            prev = curr;
            curr = perm[curr];
        }
    }
}
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.