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