所提供算法的复杂度为O(n)。原因很简单,因为 unordered_map 和 unordered_set 都根据标准库的要求提供了恒定的插入、搜索和删除时间。因此,给定一个平均常数时间k,复杂度变为O(k*n),相当于k*O(n),最终具有O(n)的基本复杂度,因为常数k变得无关紧要。
为了证明这一点,以下示例将您的循环搜索简化为使用std::unordered_set 的最基本情况,这只不过是std::unordered_map,其中键映射到self。
bool check_for_cycle_short(const Node *p)
{
std::unordered_set<const Node*> mm;
for (; p && mm.insert(p).second; p = p->next);
return p != nullptr;
}
请注意,std::unordered_set<T>::insert 返回 iterator,bool 的 std::pair,后者指示插入是否实际发生,因为在插入之前未找到密钥。
现在考虑这个例子,它是O(n^2):
bool check_for_cycle_long(const Node *p)
{
for (; p; p = p->next)
{
for (const Node *q = p->next; q; q = q->next)
{
if (q == p)
return true;
}
}
return false;
}
这会用每个节点彻底搜索列表的其余部分,从而执行(n-1) + (n-2) + (n-3).... + (n-(n-1)) 比较。
示例
要查看这些操作,请考虑以下短程序,它加载一个包含 100000 个节点的链表,然后在最坏情况下检查两个循环(没有):
#include <iostream>
#include <iomanip>
#include <chrono>
#include <unordered_set>
struct Node
{
Node *next;
};
bool check_for_cycle_short(const Node *p)
{
std::unordered_set<const Node*> mm;
for (; p && mm.insert(p).second; p = p->next);
return p != nullptr;
}
bool check_for_cycle_long(const Node *p)
{
for (; p; p = p->next)
{
for (const Node *q = p->next; q; q = q->next)
{
if (q == p)
return true;
}
}
return false;
}
int main()
{
using namespace std::chrono;
Node *p = nullptr, **pp = &p;
for (int i=0; i<100000; ++i)
{
*pp = new Node();
pp = &(*pp)->next;
}
*pp = nullptr;
auto tp0 = steady_clock::now();
std::cout << std::boolalpha << check_for_cycle_short(p) << '\n';
auto tp1 = steady_clock::now();
std::cout << std::boolalpha << check_for_cycle_long(p) << '\n';
auto tp2 = steady_clock::now();
std::cout << "check_for_cycle_short : " <<
duration_cast<milliseconds>(tp1-tp0).count() << "ms\n";
std::cout << "check_for_cycle_long : " <<
duration_cast<milliseconds>(tp2-tp1).count() << "ms\n";
}
输出(MacBook Air 双核 i7 @ 2.2GHz)
false
false
check_for_cycle_short : 36ms
check_for_cycle_long : 7239ms
正如预期的那样,结果反映了我们的怀疑。