9

Is there an efficient algorithm (efficient in terms of big O notation) to find number of swaps to convert a permutation P into identity permutation I? The swaps do not need to be on adjacent elements, but on any elements.

So for example:

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)

One idea is to write an algorithm like this:

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
    }
}

But it is not very clear to me whether that is actually guaranteed to terminate or whether it finds a correct number of swaps. It works on the examples above. I tried generating all permutation on 5 and on 12 numbers and it always terminates on those.

This problem arises in numerical linear algebra. Some matrix decompositions use pivoting, which effectively swaps row with the greatest value for the next row to be manipulated, in order to avoid division by small numbers and improve numerical stability. Some decompositions, such as the LU decomposition can be later used to calculate matrix determinant, but the sign of the determinant of the decomposition is opposite to that of the original matrix, if the number of permutations is odd.

EDIT: I agree that this question is similar to Counting the adjacent swaps required to convert one permutation into another. But I would argue that this question is more fundamental. Converting permutation from one to another can be converted to this problem by inverting the target permutation in O(n), composing the permutations in O(n) and then finding the number of swaps from there to identity. Solving this question by explicitly representing identity as another permutation seems suboptimal. Also, the other question had, until yesterday, four answers where only a single one (by |\/|ad) was seemingly useful, but the description of the method seemed vague. Now user lizusek provided answer to my question there. I don't agree with closing this question as duplicate.

EDIT2: The proposed algorithm actually seems to be rather optimal, as pointed out in a comment by user rcgldr, see my answer to Counting the adjacent swaps required to convert one permutation into another.

Community
  • 1
  • 1
the swine
  • 10,713
  • 7
  • 58
  • 100
  • @CPlusPlusOOAandD I don't have any book for that. It is barely relevant, as the decomposition can simply return how many permutations it did and no counting is required. I was interested in such an algorithm from theoretical point of view. If you are interested in learning about numerical methods, I suggest you get yourself the last edition of Numerical Recipes (http://www.nr.com/). – the swine Apr 06 '14 at 20:30
  • you can calculate lengths of cycles of factors of multiplication which will tell how many swaps are needed to convert it into identity – 4pie0 Apr 06 '14 at 20:46
  • 1
    Note that every swap places at least one element in it's proper location, so the efficiency is O(n) for n numbers. I'm not sure how to get more efficient than that. – rcgldr Apr 06 '14 at 20:48
  • 1
    possibly duplicate of [Minimum number of swaps needed to change Array 1 to Array 2?](http://stackoverflow.com/q/2987605) – Bernhard Barker Apr 06 '14 at 21:02
  • can we please reopen this so I can paste a complete C++ implementation that at the moment I was forced to put [here](http://stackoverflow.com/a/22902182/1141471) ? http://stackoverflow.com/a/22902182/1141471 – 4pie0 Apr 07 '14 at 11:58
  • @Lightness Races in Orbit can we? – 4pie0 Apr 09 '14 at 00:10
  • @privatedatapublicchannel2 don't worry about it, the answers are here for people who need them and that is what really matters. appreciate the support. – the swine Apr 09 '14 at 07:36

2 Answers2

8

I believe the key is to think of the permutation in terms of the cycle decomposition.

This expresses any permutation as a product of disjoint cycles.

Key facts are:

  1. Swapping elements in two disjoint cycles produces one longer cycle
  2. Swapping elements in the same cycle produces one fewer cycle
  3. The number of permutations needed is n-c where c is the number of cycles in the decomposition

Your algorithm always swaps elements in the same cycle so will correctly count the number of swaps needed.

If desired, you can also do this in O(n) by computing the cycle decomposition and returning n minus the number of cycles found.

Computing the cycle decomposition can be done in O(n) by starting at the first node and following the permutation until you reach the start again. Mark all visited nodes, then start again at the next unvisited node.

Peter de Rivaz
  • 33,126
  • 4
  • 46
  • 75
1

I believe the following are true:

If S(x[0], ..., x[n-1]) is the minimum number of swaps needed to convert x to {0, 1, ..., n - 1}, then:

  1. If x[n - 1] == n - 1, then S(x) == S(x[0],...,x[n-2]) (ie, cut off the last element)
  2. If x[-1] != n - 1, then S(x) == S(x[0], ..., x[n-1], ..., x[i], ... x[n-2]) + 1, where x[i] == n - 1.
  3. S({}) = 0.

This suggests a straightforward algorithm for computing S(x) that runs in O(n) time:

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;
  }
}
alecbz
  • 6,292
  • 4
  • 30
  • 50
  • The example code in the first post would be faster than this recursive method, although both are O(n). – rcgldr Apr 06 '14 at 23:11
  • Is this O(n) or more like O(n log n) with worst-case O(n^2/2) because of the `std::find()`? Probably the complexity depends on the number of swaps and the way the numbers are swapped. – the swine Apr 07 '14 at 09:03
  • @theswine You're right, this approach is probably O(n^2). Using a hash map as an inverted index for lookups instead of the `std::find` would probably get O(n). – alecbz Apr 07 '14 at 17:14