我有两个v1
类型的向量v2
和std::vector<std::string>
。两个向量都有唯一的值,如果两个向量的值相等,则它们应该相等,但与向量中出现的顺序值无关。
我假设最好使用两组std::unordered_set
类型的集,但我还是这样,所以有两个向量。
尽管如此,我认为对于所需的不区分顺序的比较,我将通过复制到两个operator==
中来使用std::unordered_set
中的std::unordered_set
。非常像这样:
bool oi_compare1(std::vector<std::string> const&v1,
std::vector<std::string> const&v2)
{
std::unordered_set<std::string> tmp1(v1.begin(),v1.end());
std::unordered_set<std::string> tmp2(v2.begin(),v2.end());
return tmp1 == tmp2;
}
在进行概要分析时,我注意到此函数消耗大量时间,因此我检查了文档并看到了O(n*n)
的复杂性。我很困惑,我期待O(n*log(n))
,例如对于以下天真的解决方案,我想到了:
bool oi_compare2(std::vector<std::string> const&v1,
std::vector<std::string> const&v2)
{
if(v1.size() != v2.size())
return false;
auto tmp = v2;
size_t const size = tmp.size();
for(size_t i = 0; i < size; ++i)
{
bool flag = false;
for(size_t j = i; j < size; ++j)
if(v1[i] == tmp[j]){
flag = true;
std::swap(tmp[i],tmp[j]);
break;
}
if(!flag)
return false;
}
return true;
}
为什么O(n*n)
的{{1}}复杂度以及我可以用于订单不敏感比较的内置函数?
编辑---- 基准
std::unordered_set
给予
#include <unordered_set>
#include <chrono>
#include <iostream>
#include <vector>
bool oi_compare1(std::vector<std::string> const&v1,
std::vector<std::string> const&v2)
{
std::unordered_set<std::string> tmp1(v1.begin(),v1.end());
std::unordered_set<std::string> tmp2(v2.begin(),v2.end());
return tmp1 == tmp2;
}
bool oi_compare2(std::vector<std::string> const&v1,
std::vector<std::string> const&v2)
{
if(v1.size() != v2.size())
return false;
auto tmp = v2;
size_t const size = tmp.size();
for(size_t i = 0; i < size; ++i)
{
bool flag = false;
for(size_t j = i; j < size; ++j)
if(v1[i] == tmp[j]){
flag = true;
std::swap(tmp[i],tmp[j]);
break;
}
if(!flag)
return false;
}
return true;
}
int main()
{
std::vector<std::string> s1{"1","2","3"};
std::vector<std::string> s2{"1","3","2"};
std::cout << std::boolalpha;
for(size_t i = 0; i < 15; ++i)
{
auto tmp1 = s1;
for(auto &iter : tmp1)
iter = std::to_string(i)+iter;
s1.insert(s1.end(),tmp1.begin(),tmp1.end());
s2.insert(s2.end(),tmp1.begin(),tmp1.end());
}
std::cout << "size1 " << s1.size() << std::endl;
std::cout << "size2 " << s2.size() << std::endl;
for(auto && c : {oi_compare1,oi_compare2})
{
auto start = std::chrono::steady_clock::now();
bool flag = true;
for(size_t i = 0; i < 10; ++i)
flag = flag && c(s1,s2);
std::cout << "ms=" << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() << " flag=" << flag << std::endl;
}
return 0;
}
->天真的方法更快。
对于这里的所有复杂性O(N * N)专家...
让我经历一下这种幼稚的方法。我在那里有两个循环。第一个循环从size1 98304
size2 98304
ms=844 flag=true
ms=31 flag=true
运行到N的大小。内部循环从j = i !!!!!!调用。到N。用语言来说,这意味着我叫内循环N次。但是由于j = i !!!!的起始索引,内部循环的复杂度为log(n)。如果您仍然不相信我可以根据基准计算复杂度,那么您会看到...
编辑2 --- 现场直播 https://wandbox.org/permlink/v26oxnR2GVDb9M6y
答案 0 :(得分:5)
由于unordered_set是使用hashmap构建的,因此比较lhs == rhs的逻辑将是:
对于哈希映射,最坏情况下以rhs为单位的项的单个查找时间复杂度将为O(n)。因此,最坏情况下的时间复杂度将为O(n ^ 2)。但是通常情况下,您的时间复杂度为O(n)。
答案 1 :(得分:2)
很抱歉告诉您,您对operator==
的基准测试有误。
oi_compare1
接受2个向量,并需要构建2个完整的unordered_set
实例,然后调用operator==
并再次销毁完整的束。
oi_compare2
也接受2个向量,并立即将它们用于大小比较。仅复制1个实例(从v2到tmp),这对于矢量而言性能更高。
查看文档:{{3}},我们可以看到预期的复杂性:
与N成比例的调用value_type上的operator ==,调用key_eq返回的谓词,以及调用hash_function返回的哈希器,在通常情况下,与最坏情况下的N2成正比,其中N是容器。
修改
有一个简单的算法,您可以遍历unordered_set
并在另一个中进行简单查找。没有哈希冲突,它将在自己的内部存储桶中找到每个元素,并比较它们的相等性,因为哈希不够。
假设您没有哈希冲突, 当存储值的存储桶大小不同时,或者存储桶的分配使用不同的计算来处理冲突时,这将不起作用。 unordered_set
的每个元素都有一个稳定的存储顺序。一个人可以在内部存储桶上循环,然后将元素2比2比较(一个元素的第一个与第二个元素的第一个,第二个元素与第二个元素的第二个……)。很好地给出了O(N)
。
假设您很不幸,并且每个元素都会产生相同的哈希值。 (称为hash flooding
)将导致元素列表不按顺序排列。为了进行比较,您必须检查每个元素是否存在于另一个元素中,从而导致O(N*N)
。
如果您将哈希值固定为始终返回相同的数字,则最后一个易于复制。以与另一组相反的顺序构建一组。