是否有一种有效的算法(在大O符号方面有效)找到交换次数以将置换P转换为身份置换I?交换不需要在相邻元素上,而是在任何元素上。
例如:
I = {0, 1, 2, 3, 4, 5}, number of swaps is 0
P = {0, 1, 5, 3, 4, 2}, number of swaps is 1 (2 and 5)
P = {4, 1, 3, 5, 0, 2}, number of swaps is 3 (2 with 5, 3 with 5, 4 with 0)
一个想法是写一个这样的算法:
int count = 0;
for(int i = 0; i < n; ++ i) {
for(; P[i] != i; ++ count) { // could be permuted multiple times
std::swap(P[P[i]], P[i]);
// look where the number at hand should be
}
}
但我不清楚这是否确实可以保证终止或者是否找到正确的掉期数量。它适用于上面的例子。我尝试在5个和12个数字上生成所有排列,它总是终止于那些。
这个问题出现在数值线性代数中。一些矩阵分解使用旋转,其有效地交换行以具有用于操纵的下一行的最大值,以避免被小数除法并提高数值稳定性。一些分解,例如LU分解可以在以后用于计算矩阵行列式,但如果排列的数量是奇数,则分解的行列式的符号与原始矩阵的符号相反。
编辑:我同意此问题与Counting the adjacent swaps required to convert one permutation into another类似。但我认为这个问题更为根本。通过将O(n)中的目标置换反转,在O(n)中构成置换,然后从那里找到交换的数量到同一性,可以将置换从一个转换为另一个。通过明确地将身份表示为另一种排列来解决这个问题似乎不是最理想的。此外,直到昨天,另一个问题是四个答案,其中只有一个答案(通过| \ / | ad)似乎有用,但该方法的描述似乎含糊不清。现在用户lizusek在那里提供了我的问题的答案。我不同意将此问题视为重复。
EDIT2 :正如用户rcgldr的评论所指出的那样,建议的算法实际上似乎是最优的,请参阅我对Counting the adjacent swaps required to convert one permutation into another的回答。
答案 0 :(得分:4)
我认为关键是要考虑cycle decomposition方面的排列。
这表示任何排列都是不相交循环的产物。
关键事实是:
您的算法始终在同一周期中交换元素,因此将正确计算所需的交换数量。
如果需要,你也可以在O(n)中通过计算循环分解并返回n减去找到的循环次数来做到这一点。
计算循环分解可以在O(n)中完成,方法是从第一个节点开始并按照排列,直到再次到达开始。标记所有访问过的节点,然后在下一个未访问的节点重新开始。
答案 1 :(得分:1)
我相信以下是真的:
如果S(x[0], ..., x[n-1])
是将x
转换为{0, 1, ..., n - 1}
所需的最小互换次数,则:
x[n - 1] == n - 1
,则S(x) == S(x[0],...,x[n-2])
(即,切断最后一个元素)x[-1] != n - 1
,则S(x) == S(x[0], ..., x[n-1], ..., x[i], ... x[n-2]) + 1
,x[i] == n - 1
。S({}) = 0
。这表明在S(x)
时间运行的计算O(n)
的直接算法:
int num_swaps(int[] x, int n) {
if (n == 0) {
return 0;
} else if (x[n - 1] == n - 1) {
return num_swaps(x, n - 1);
} else {
int* i = std::find(x, x + n, n - 1);
std::swap(*i, x[n - 1])
return num_swaps(x, n - 1) + 1;
}
}