【问题标题】:Compare two unsorted std::vector [closed]比较两个未排序的 std::vector [关闭]
【发布时间】:2022-01-20 03:20:03
【问题描述】:

比较两个未排序的std::vector的最佳方法是什么

std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {2, 3, 4, 5, 1};

我目前正在做的是

const auto is_similar = v1.size() == v2.size() && std::is_permutation(v1.begin(), v1.end(), v2.begin());

这里两个向量只有在两个向量的大小相等且包含相同元素时才相似

什么是更好的方法

  • 两个小的 std::vectors(大小远低于 50 个元素)
  • 两个大的 std::vectors

【问题讨论】:

  • 显然std::is_permutation 在向量中的元素数量可能是 O(n²),因此您可以通过对两个向量进行排序并检查结果的相等性来处理足够大的向量。
  • 什么是最好的方法... -- 没有“最好的方法”。它是好是坏或介于两者之间。有人可能会考虑将一个向量放入 unorderd_set 并与另一个向量进行检查是否“更好”。

标签: c++ c++11 vector


【解决方案1】:

什么是更好的方法

删除v1.size() == v2.size() &amp;&amp; 表达式,并将结束迭代器传递给std::is_permutation

您标记了 C++11,但对于那些可以使用 C++20 的人,我建议如下:

std::ranges::is_permutation(v1, v2)

如果您可以修改向量,那么对它们进行排序和比较相等性会越来越快。如果你不能修改,那么如果你能负担得起存储成本,你可以创建一个排序的副本。

【讨论】:

  • std::is_permutation 给出真值,即使一个向量大于另一个向量且相同元素重复。这就是我首先添加尺寸检查的原因
  • 使用 cppreference 中列出的重载 (3) 比大小检查更简洁 std::is_permutation(v1.begin(), v1.end(), v2.begin(), v2.end())
  • @Praveen 哦,我没有意识到您没有将结束迭代器传递给另一个范围。确实。在这种情况下,需要进行尺寸检查。
【解决方案2】:

std::is_permutation 对于大型阵列来说似乎非常非常慢。对于类似数组的64 K 元素,已经需要大约5 秒才能给出答案。而对于这种大小的数组,常规排序需要0.007 秒。下面我的代码中提供了时间。

我建议做以下事情 - 计算独立于元素顺序的任何简单(和快速)元素散列函数。如果两个数组的散列不相等,则它们不相似,换句话说,作为集合的两个数组不相等。只有当哈希值相同时,才进行常规排序并比较数组是否相等。

我的回答中建议的内容适用于大型数组,以加快计算速度,对于小型数组来说 std::is_permutation 可能就足够了。尽管此答案中的所有内容也适用于小型数组。

在下面的代码中实现了三个函数SimilarProd()SimilarSort()SimilarIsPermutation(),首先使用我的建议,先计算哈希函数然后排序。

作为一个与位置无关的散列函数,我对所有移动(添加到)某个固定随机 64 位值的元素进行常规乘积(乘积)。由于现代编译器(如 CLang 和 GCC)具有良好的 auto-vectorization 功能,这种应用于整数数组的计算将非常快速地计算出来,这些编译器使用 SIMD 指令来提升计算速度。

在下面的代码中,我对相似函数的所有三个实现进行了计时。看起来,如果数组64 K 的大小相似(相同的数字集),std::is_permutation() 需要5 秒,而散列方法和排序方法都需要0.007 秒。对于不相似的数组,std::is_permutation 非常快,低于0.00005 秒,而排序也快0.007 秒,散列快100x 倍,0.00008 秒。

所以结论是 std::is_permutation 对于大型相​​似数组非常慢,而对于不相似数组非常快。排序方法对于相似和不相似的速度相同。虽然哈希方法对于相似的速度很快,而对于不相似的方法则非常快。在不相似数组的情况下,哈希方法与 std::is_permutation 的速度大致相同,但对于相似数组来说是明显的胜利。

所以在三种方法中,哈希方法是明显的胜利。

在代码之后查看下面的时序。

更新。为了比较,刚才又添加了一种方法SimilarMap()。使用std::unordered_map 计算数组中每个整数的出现次数。它似乎比排序慢一点。所以仍然 Hash+Sort 方法是最快的。尽管对于非常大的数组,这种地图计数方法的性能应该优于排序速度。

Try it online!

#include <cstdint>
#include <array>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <unordered_map>

bool SimilarProd(std::vector<int> const & a, std::vector<int> const & b) {
    using std::size_t;
    using u64 = uint64_t;
    if (a.size() != b.size())
        return false;
    u64 constexpr randv = 0x6A7BE8CD0708EC4CULL;
    size_t constexpr num_words = 8;
    std::array<u64, num_words> prodA = {}, prodB = {};
    std::fill(prodA.begin(), prodA.end(), 1);
    std::fill(prodB.begin(), prodB.end(), 1);
    for (size_t i = 0; i < a.size() - a.size() % num_words; i += num_words)
        for (size_t j = 0; j < num_words; ++j) {
            prodA[j] *= (randv + u64(a[i + j])) | 1;
            prodB[j] *= (randv + u64(b[i + j])) | 1;
        }
    for (size_t i = a.size() - a.size() % num_words; i < a.size(); ++i) {
        prodA[0] *= (randv + u64(a[i])) | 1;
        prodB[0] *= (randv + u64(b[i])) | 1;
    }
    for (size_t i = 1; i < num_words; ++i) {
        prodA[0] *= prodA[i];
        prodB[0] *= prodB[i];
    }
    if (prodA[0] != prodB[0])
        return false;
    auto a2 = a, b2 = b;
    std::sort(a2.begin(), a2.end());
    std::sort(b2.begin(), b2.end());
    return a2 == b2;
}

bool SimilarSort(std::vector<int> a, std::vector<int> b) {
    if (a.size() != b.size())
        return false;
    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());
    return a == b;
}

bool SimilarIsPermutation(std::vector<int> const & a, std::vector<int> const & b) {
    return a.size() == b.size() && std::is_permutation(a.begin(), a.end(), b.begin());
}

bool SimilarMap(std::vector<int> const & a, std::vector<int> const & b) {
    if (a.size() != b.size())
        return false;
    std::unordered_map<int, int> m;
    for (auto x: a)
        ++m[x];
    for (auto x: b)
        --m[x];
    for (auto const & p: m)
        if (p.second != 0)
            return false;
    return true;
}

void Test() {
    using std::size_t;
    auto TimeCur = []{ return std::chrono::high_resolution_clock::now(); };
    auto const gtb = TimeCur();
    auto Time = [&]{ return std::chrono::duration_cast<
        std::chrono::microseconds>(TimeCur() - gtb).count() / 1000000.0; };
    std::mt19937_64 rng{123};
    auto RandV = [&](size_t n) {
        std::vector<int> v(n);
        for (size_t i = 0; i < v.size(); ++i)
            v[i] = rng() % (1 << 30);
        return v;
    };
    size_t constexpr n = 1 << 16;
    auto a = RandV(n), b = a, c = RandV(n);
    std::shuffle(b.begin(), b.end(), rng);

    std::cout << std::boolalpha << std::fixed;
    double tb = 0;

    tb = Time();    std::cout << "Prod "
        << SimilarProd(a, b) << " time " << (Time() - tb) << std::endl;
    tb = Time();    std::cout << "Sort "
        << SimilarSort(a, b) << " time " << (Time() - tb) << std::endl;
    tb = Time();    std::cout << "IsPermutation "
        << SimilarIsPermutation(a, b) << " time " << (Time() - tb) << std::endl;
    tb = Time();    std::cout << "Map "
        << SimilarMap(a, b) << " time " << (Time() - tb) << std::endl;


    tb = Time();    std::cout << "Prod "
        << SimilarProd(a, c) << " time " << (Time() - tb) << std::endl;
    tb = Time();    std::cout << "Sort "
        << SimilarSort(a, c) << " time " << (Time() - tb) << std::endl;
    tb = Time();    std::cout << "IsPermutation "
        << SimilarIsPermutation(a, c) << " time " << (Time() - tb) << std::endl;
    tb = Time();    std::cout << "Map "
        << SimilarMap(a, c) << " time " << (Time() - tb) << std::endl;
}

int main() {
    Test();
}

输出:


Prod true time 0.009208
Sort true time 0.008080
IsPermutation true time 4.436632
Map true time 0.010382

Prod false time 0.000082
Sort false time 0.008750
IsPermutation false time 0.000036
Map false time 0.016390

【讨论】:

    猜你喜欢
    • 2019-01-30
    • 1970-01-01
    • 2021-11-05
    • 2016-07-28
    • 1970-01-01
    • 2016-10-22
    • 1970-01-01
    • 1970-01-01
    • 2018-04-19
    相关资源
    最近更新 更多