【问题标题】:C++ sort vectors time complexityC++ 排序向量时间复杂度
【发布时间】:2017-05-28 04:31:50
【问题描述】:

假设我有一个带有 N 个向量的 vector<vector<int>> L,并且所有向量中的 ints 总数最多为 M。标准 C++ 排序 sort(L.begin(), L.end()) 的最紧凑时间复杂度是多少?

vector<int> 比较函数的运行时间最多为 O(M),因此一个明显的界限是 O(NM log N)。但是如果我们实现标准的归并排序,我们可以看到在每个 O(log N) 级别中最多完成 O(M) 个整数比较,因此运行时间是 O((N+M) log N)。这是因为比较两个长度为 A 和 B 的向量需要 O(min(A,B)) 时间。

C++ 标准是否保证运行时为 O((N+M) log N)?

【问题讨论】:

  • 仅供参考:如果您真的关心复杂性,请考虑使用基数排序。计数排序比比较排序提高了复杂性。

标签: c++ performance sorting vector c++-standard-library


【解决方案1】:

没有足够的信息。您还需要了解M 值在N 向量中的分布。有了它,就可以直接找到整体复杂性:

  1. std::sort 具有 O(N·log(N)) 比较的复杂性。

  2. std::vector 使用std::lexicographical_compare(v1, v2) 进行比较,其复杂度为O(min(v1.size(), v2.size())) 比较。

  3. int 比较的复杂度为O(1)

  4. 我们将让E(M, N) 成为MN 上的一个函数,它返回最小元素数均值在每对内部向量之间。

    • 例如,如果您有一个均匀分布,这是 微不足道地等于M/N
  5. 取产品: Big Oh = N·log(N)·E(M, N)·1
    • 对于均匀分布,这将是M·log(N)

您可以使用Discrete Probability Distribution theory 找出E(M, N) 函数对于MN 的任何分布是什么。


编辑 1:为了说明这点如何/为什么重要:考虑一个总是使我的向量看起来像这样的分布:

outer[0].size() == 1,
outer[1].size() == 1,
outer[2].size() == 1,
...,
outer[M-1].size() == (M - N + 1)

在这种情况下,E(M, N) = 1,因为对于任何一对元素,std::lexicographical_compare 将只有 一个 其他元素可供比较。因此,对于这个特定的发行版,我将总是具有O(N·log(N)) 的复杂度。但如果分布均匀,我将拥有O(M·log(N))


编辑 2:在您定义分布的注释之后,让我们尝试找到 E(M, N)

首先,请注意总共有T = (N choose 2) = N(N - 1)(1/2) 个不同的向量比较组合。

一个(并且只有一个)组合将进行 X = O((M - N + 2)(1/2)) 比较,并且有概率 P(X) = 1/T 发生。

所有其他组合都只需要比较1 (O(1)),因此这些情况的发生概率为P(1) = (T - 1)/T

求均值很简单:X·P(X) + 1·P(1)

鉴于此,WolframAlpha 说:E(M, N) = (M + (N - 2) N)/((N - 1) N)

将该函数乘以N log(N) 得到(M + (N - 2) N) log(N) / (N - 1),可以进一步简化为您正在寻找的Big Oh:O((M/N + N) log(N))

【讨论】:

  • 为什么我们对每对内向量都使用平均比较时间?不应该是 C++ 排序算法比较的每一对的平均比较时间吗?
  • 谢谢,我明白了。我只是想知道这种情况:N-2 个长度为 1 的向量,2 个长度为 (M-N+2)/2 的向量。显然,这也应该花费很少的时间。但是比较时间可以达到 (M-N+2)/2。这是否意味着 C++ 排序将花费 (M-N+2)/2 * N log N 时间?我认为我们需要知道排序算法所做的比较到底是什么......
  • 好吧,再说一遍,这需要一点概率论来理解。我会尽力解释。请在我输入答案时更新您的问题并进行编辑以给出分布。 :)
  • @Wakaka 希望这是有道理的!
  • 嗯,朗道符号通常可以被有意地“美化”,所以最后一句话没有多大意义。 O((M+(N-2)N)/(N-1)*log(N)) “是” O((N + M/N) log(N)) 反过来“是” O(( M+N) log(N)) 所以 OP 的猜测是正确的,只是不太具体。
【解决方案2】:

如果您的整数或多或少随机 1),大多数比较只需要比较每个向量的前几个整数(直到第一个不匹配),所以在实践中/平均而言

M(违反直觉)对算法复杂度没有任何影响

给你一些想法:即使你的向量有无限长度,并且最常出现的整数有 50% 的概率 p,你需要进行少于 2 次比较平均

k < ∑ i*p^i = p/(1-p)^2 | p=0.5 
k < ∑ i*0.5^i = 2;

对于其他概率,结果是:

60% -> k <  2.5
70% -> k <  3.4
80% -> k <  5.0
90% -> k < 10.0

请记住,所有这些数字都是整数比较的平均次数上限,并且与向量

1) 我所说的随机并不是指加密意义上的随机。这些数字甚至不必通过大多数随机数的质量测试。唯一的要求是它们不能以系统的方式形成相同的前缀(随着向量的长度而增长)。
除了恶意输入,我目前想不出一个不符合“或多或少随机”的现实示例,但可能还有其他原因。

【讨论】:

  • M 在这里很重要,因为Nfind 中很重要:一些比较可以提前结束,是的,但平均情况仍然是O(N/2) = O(N)。当M &gt;&gt; N 时,M 会变得很重要。
  • @Brian:不!考虑 N == 2,M == 1000(所以两个大小为 500 的向量)并让整数介于 0 和 10 之间。最后两个整数显着的概率是 0.1^500。如果让 M 增长,则新添加的整数产生差异的概率呈指数下降,而长度仅线性增长
  • 确实如此,但这只是因为您对整数施加了额外的约束。 OP 没有设置这样的约束,当我们移除它时,即使在那个例子中,M 也很重要。
  • @Brian:这就是为什么我说“如果你的整数或多或少是随机的”。如果您必须针对攻击者强化您的算法,那可以选择任意数字(例如,仅1),这当然无济于事。但是对于大多数其他应用程序,如果您假设比较提前终止,您可以更实际地估计算法的行为方式。这有点像哈希表的情况:最坏情况下的性能是 O(n),在某些情况下这很重要,但通常,你只是假设查找是 O(1)
  • 您能否在回答中更详细地说明整数“或多或少随机”的含义?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-30
  • 1970-01-01
  • 1970-01-01
  • 2011-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多