更好的订购
我建议使用colexicographical order,因为在这种情况下您不必提供对象的总数。像这样订购你的配对:
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: …
0&1, 0&2, 1&2, 0&3, 1&3, 2&3, 0&4, 1&4, 2&4, 3&4, 0&5, 1&5, 2&5, 3&5, …
您将能够将此列表扩展到无限长,因此您可以知道任何对的索引而无需知道项目的数量。这样做的好处是,当您将新项目添加到数据结构中时,您只需要附加到您的数组,而不是重新定位现有条目。当您标记问题 C++ 时,我已将索引调整为从零开始,因此我假设您将使用从零开始的索引。我在下面的所有答案都假定这种顺序。
您还可以像这样可视化 colex 排序:
a: 0 1 2 3 4 5 …
b:
1 0
2 1 2 index of
3 3 4 5 a&b
4 6 7 8 9
5 10 11 12 13 14
6 15 16 17 18 19 20
⋮ ⋮ ⋱
与单个索引配对
让我们首先将一对转换为单个索引。诀窍在于,对于每一对,您查看第二个位置并想象在该位置具有较少数字的所有对。因此,例如对于2&4
您首先计算第二个数字小于 4 的所有对的配对。这是从一组 4 中选择两个项目的可能方法的数量(即数字 0 到 3),因此您可以表达这作为二项式系数 4C2。如果你评估它,你最终会得到 4(4−1)/2=6。为此,您添加第一个数字,因为这是具有较低索引但第二位具有相同数字的对数。因为2&4
这是 2,所以 的总指数2&4
是 4(4−1)/2+2=8。
通常,对于 a & b对,索引将为b ( b -1)/2+ a。
int index_from_pair(int a, int b) {
return b*(b - 1)/2 + a;
}
单个索引配对
将单个索引i变回一对数字的一种方法是增加b直到b ( b +1)/2 > i ,即b的下一个值将导致索引大于i的情况。然后你可以找到a作为差a = i -<i>b( b -1)/2。这种通过一次增加一个b的方法涉及使用循环。
pair<int, int> pair_from_index(int i) {
int a, b;
for (b = 0; b*(b + 1)/2 <= i; ++b)
/* empty loop body */;
a = i - b*(b - 1)/2;
return make_pair(a, b);
}
您还可以将b ( b -1)/2 = i解释为二次方程,您可以使用平方根来求解。您需要的真正b是作为该二次方程的正解得到的浮点b的底。由于这种方法中的舍入错误可能会导致您遇到问题,因此您可能需要检查是否b ( b +1)/2 > i。如果不是这种情况,请像在循环方法中那样增加b 。一旦有了b , a的计算就保持不变。
pair<int, int> pair_from_index(int i) {
int b = (int)floor((sqrt(8*i + 1) + 1)*0.5);
if (b*(b + 1)/2 <= i) ++b; // handle possible rounding error
int a = i - b*(b - 1)/2;
return make_pair(a, b);
}
顺序访问
请注意,您只需将索引转回成对即可随机访问您的列表。当迭代所有对时,一组嵌套循环更容易。所以而不是
for (int = 0; i < n*(n - 1)/2; ++i) {
pair<int, int> ab = pair_from_index(i);
int a = ab.first, b = ab.second;
// do stuff
}
你最好写
for (int i = 0, b = 1; b != n; ++b) {
for (int a = 0; a != b; ++a) {
// do stuff
++i;
}
}