C++ 如何使这个匹配算法运行得更快?
我有两个指向数据结构X的指针列表,算法非常简单: 它在第一个列表A上循环,并尝试在列表B中找到第一个匹配元素。要求每个列表中至少有50k个元素:C++ 如何使这个匹配算法运行得更快?,c++,performance,c++11,performance-testing,matching,C++,Performance,C++11,Performance Testing,Matching,我有两个指向数据结构X的指针列表,算法非常简单: 它在第一个列表A上循环,并尝试在列表B中找到第一个匹配元素。要求每个列表中至少有50k个元素: #include <iostream> #include <memory> #include <chrono> #include <vector> #include <algorithm> #include <string> struct X { std::string
#include <iostream>
#include <memory>
#include <chrono>
#include <vector>
#include <algorithm>
#include <string>
struct X {
std::string field_1;
std::string field_2;
std::string field_3;
std::string field_4;
X(std::string f1, std::string f2, std::string f3, std::string f4)
: field_1(f1)
, field_2(f2)
, field_3(f3)
, field_4(f4)
{};
bool equal(const std::shared_ptr<X>& x) {
return (x->field_1 == field_1) &&
(x->field_2 == field_2) &&
(x->field_3 == field_3) &&
(x->field_4 == field_4);
};
X *match = nullptr;
};
typedef std::shared_ptr<X> X_ptr;
class Timer
{
public:
Timer(std::string name) : beg_(clock_::now()), name_(name) {}
~Timer() {
std::cout << "Elapsed(" << name_ << "): " << elapsed() << std::endl;
}
void reset() { beg_ = clock_::now(); }
double elapsed() const {
return std::chrono::duration_cast<second_>
(clock_::now() - beg_).count();
}
private:
typedef std::chrono::high_resolution_clock clock_;
typedef std::chrono::duration<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> beg_;
std::string name_;
};
std::string random_string(size_t length)
{
auto randchar = []() -> char
{
const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const size_t max_index = (sizeof(charset) - 1);
return charset[rand() % max_index];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
}
int main()
{
Timer t("main");
std::vector <X_ptr> list_A;
std::vector <X_ptr> list_B;
const int MAX_ELEM = 50000;
list_A.reserve(MAX_ELEM);
list_B.reserve(MAX_ELEM);
{
Timer t("insert");
for (int i = 0; i < MAX_ELEM; i++) {
list_A.push_back(X_ptr(new X{ random_string(2), random_string(2), random_string(2), random_string(2) }));
list_B.push_back(X_ptr(new X{ random_string(2), random_string(2), random_string(2), random_string(2) }));
}
}
{
Timer t("match");
std::for_each(list_A.begin(), list_A.end(), [list_B](X_ptr& a) {
auto found_b = std::find_if(list_B.begin(), list_B.end(), [a](const X_ptr& b) {
return a->equal(b);
});
if (found_b != list_B.end()) {
a->match = found_b->get();
std::cout << "match OK \n";
}
});
}
}
如果您能想出任何其他方法来优化它以使其运行更快,我们将不胜感激。您可以使用以下方法:
std::sort(list_B.begin(), list_B.end(), deref_less<X>);
{
Timer t("match");
for (const auto& a : list_A) {
auto it = std::lower_bound(list_B.begin(), list_B.end(), a, deref_less<X>);
if (it != list_B.end() && **it == *a) {
a->match = it->get();
std::cout << "match OK \n";
}
}
}
.您使用的是向量,因此每次对列表B的查找都会进行,其中n是B中的元素数。这意味着如果m是列表A中的元素数,则总算法为Om*n。因此,如果m和n A的大小相似,则有一个On^2算法。这对于任何大的n来说都太慢了。要解决此问题,请将列表B转换为无序的映射,您可以将此作为此算法的一部分进行转换,因为映射键中的元素是列表B中的一个元素,其值为任意值,例如0。然后,您可以使用“在地图上查找”在O1时间内对地图执行查找。因此,您的算法将继续运行,比^2上的算法要好得多 比如说
std::unordered_map< X_ptr, int > value_map;
Time r t("match");
std::for_each(list_B.begin(), list_B.end(), [&](X_ptr& b) {
value_map[b] = 0;
});
std::for_each(list_A.begin(), list_A.end(), [value_map](X_ptr& a) {
auto found_b = value_map.find( a );
if ( found_b != value_map.end() )
{
a->match = found_b->first.get();
std::cout << "match OK \n";
}
});
}
新版本:
Elapsed(insert): 0.0719907
Elapsed(match): 0.0388562
Elapsed(main): 0.130884
列表顺序是固定的还是可以在使用前对其进行排序?请在使用前对列表进行稳定排序。然后您可以在列表b上使用二进制搜索,而不是“如果”查找。@MatthiasB,是的,它可以排序。但是哪个字段更适合codereview.stackexchange.com在这种情况下,最大的好处是不必遍历整个列表进行搜索,一种方法是以可以排序的方式对其进行结构化,然后使用二进制搜索。通过优化字符串比较,可以获得进一步的收益,例如,在分配字符串时生成哈希代码,然后只比较哈希。使用哈希表键作为查找表对于编码工具箱来说是一个有用的技巧。
Elapsed(insert): 0.0758608
Elapsed(match): 182.899
Elapsed(main): 182.991
Elapsed(insert): 0.0719907
Elapsed(match): 0.0388562
Elapsed(main): 0.130884