如何使用仅使用其中一个向量的条件以相同方式对两个向量进行排序?


84

如何使用仅使用其中一个向量的条件以相同方式对两个向量进行排序?

例如,假设我有两个大小相同的向量:

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

然后,我vectorA使用一些比较功能进行排序。排序重新排序vectorA。如何将相同的重新排序应用于vectorB


一种选择是创建一个结构:

struct ExampleStruct {
    MyObject mo;
    int i;
};

然后排序包含的内容的矢量vectorAvectorB压缩成单个载体:

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

这似乎不是理想的解决方案。还有其他选择,尤其是在C ++ 11中吗?


您能否提供带有一些输入和相应所需输出的示例?我在理解这个问题时遇到了麻烦
Andy Prowl 2013年

我认为他想(有效地)对目录vectorAvectorB目录进行排序vectorB
Mooing Duck 2013年

我认为这个问题几乎是重复的,如果不是确切的重复的话
Mooing Duck 2013年

如何根据vectorA中的值对第三个向量(索引为0,... vectorA.size())进行排序,然后将这些索引“应用”到vectorB上呢?例如像stackoverflow.com/a/10581051/417197
安德烈·

就个人而言,我宁愿有一个vector<pair<MyObject, int>>。这样,您就不必担心这两个列表不同步;一种是同时对两组数据重新排序。而且,无需编写其他结构。
cHao 2013年

Answers:


117

查找排序排列

给定一个std::vector<T>和的比较T,我们希望能够找到您要使用此比较对向量进行排序的排列。

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

应用排序排列

给定一个std::vector<T>和排列,我们希望能够构建一个std::vector<T>根据排列重新排序的新排列。

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

当然,您可以修改apply_permutation以使给定的向量变异,而不是返回新的排序副本。这种方法仍然是线性时间复杂度,并且在向量中每项使用一位。从理论上讲,它仍然是线性空间复杂性;但是,实际上,当sizeof(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];
        }
    }
}

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

资源资源


4
在我的编译器中,我必须将“比较并比较”更改为“比较比较”。
SmallChess

2
我还需要包含标头<numeric>才能运行std :: iota函数(vs2012)。
史密斯

1
您当然可以进行修改apply_permutation以使给定的向量变异,而不是返回新的排序后的副本。 ”我会发现,至少从概念上讲,这是对答案的有益补充。您是否会以与apply_permutation自己相同的方式来实现呢?
ildjarn '16

2
@ildjarn我已经用您的建议更新了答案。
蒂莫西·希尔兹

2
感谢您的精彩摘录!但是sort_permutation声明Compare& compare应为const。否则,您将无法使用std :: less <T>或类似名称。
kotakotakota

9

使用range-v3,很简单,对zip视图进行排序:

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

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

或明确使用投影:

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

演示版


在VS2017上尝试过,无论我尝试什么,都无法编译或运行。该库可在VS2019,GCC和LLVM上使用。
康坦戈

4

我想为我提出的扩展内容做出贡献。目的是能够使用简单的语法同时对多个向量进行排序。

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

该算法与Timothy提出的算法相同,但使用可变参数模板,因此我们可以同时对任意类型的多个向量进行排序。

这是代码片段:

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

Ideone中进行测试。

我在这篇博客文章中对此进行了更好的解释。


3

使用置换进行就地排序

我将使用像Timothy这样的排列方式,尽管如果您的数据太大并且不想为已排序的向量分配更多的内存,则应就地进行处理。这是一个使用置换的O(n)(线性复杂度)就地排序的示例 :

诀窍是获取排列和反向排列,以了解在最后的排序步骤中将数据覆盖在何处。

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
虽然看起来是O(N ^ 2),但不是。内部while仅在未排序项目时执行。当内部while对数据进行排序时,它有点跳过外部while迭代...
MtCS 2014年

2
  1. 从各个向量中制作成对的向量。
    初始化对
    的向量添加到对的向量

  2. 制作自定义排序比较器:
    排序自定义对象的向量
    http://rosettacode.org/wiki/Sort_using_a_custom_comparator#C.2B.2B

  3. 对向量进行排序。

  4. 将成对的向量分离为单个向量。

  5. 将所有这些都放到函数中。

码:

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

参考对的向量?
Mooing Duck 2013年

我明白您的意思,但是成对的参考将很难奏效。
ruben2020 2013年

1
@ ruben2020:可以,但是这样做只是个创可贴。如果您有两个数据交织在一起,以至于排序一个数据应该对另一个数据进行排序,那么看来您真正拥有的是一个尚未集成的对象。
cHao 2013年

1

我假设vectorA和vectorB的长度相等。您可以创建另一个向量,我们称之为pos,其中:

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

然后,您可以使用pos对vectorB进行排序,即创建vectorBsorted,其中:

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

然后按与vectorA相同的索引排列对vectorBsorted进行排序。


0

我不确定这是否有效,但我会使用类似的方法。例如,要对两个向量进行排序,我将使用降序气泡排序方法和向量对。

对于降序气泡排序,我将创建一个需要向量对的函数。

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

之后,我会将您的2个向量值放入一对向量中。如果您能够同时添加值,请使用此值,然后调用气泡排序功能。

vector< pair<MyObject,int> > my_vector;

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

bubbleSort(my_vector);

如果要在将2个向量相加后使用值,则可以使用该值,然后调用冒泡排序函数。

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

我希望这有帮助。问候,坎纳


0

我最近写了一个适合stl算法的zip迭代器。它允许您生成如下代码:

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;

它包含在单个标头中,唯一的要求是C ++ 17。在GitHub签出

codereview上也有一篇文章,其中包含所有源代码。


0

根据蒂莫西·希尔兹的回答。
稍作调整,apply_permutaion就可以使用fold表达式将置换一次应用于不同类型的多个向量。

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