42 votes

Compter les swaps nécessaires pour convertir une permutation en une autre

Nous nous sommes donné deux séquences de minuscule latine des lettres de l'alphabet. Ils sont tous les deux de la même longueur et ont la même quantité de types de donnée de lettres (le premier a un nombre égal de t comme le deuxième et ainsi de sur). Nous sommes tenus de trouver le nombre minimum de swaps ("swap", nous entendons changer l'ordre des deux voisins de lettres) nécessaires à l' transformer la première séquence dans la seconde. Nous peut supposer que tous les deux séquences PEUVENT être transformés les uns dans les autres. Nous pourrions le faire avec la force brute, mais les séquences sont trop longs pour que.

Entrée:
La longueur des séquences (au moins 2, au plus 999999) et puis deux séquences.

Sortie:
Un entier représentant le nombre d'échanges nécessaires pour la séquences pour devenir le même.

Exemple:
{5, aaaaa aaaaa} doit de sortie {0},
{4, abcd, acdb} doit de sortie {2}.

La première chose qui m'est venue à l'esprit a été bubblesort. Nous pouvons simplement bubblesort la séquence de comptage de chaque échange. Le problème est: a) il est O(n^2) pire-cas b) je ne suis pas convaincu qu'il me donnerait le plus petit nombre pour chaque cas... Même l'optimisation de la bubblesort ne semble pas faire l'affaire. Nous avons pu mettre en œuvre le cocktail de tri qui permettrait de résoudre le problème avec les tortues - mais il me donner les meilleures performances? Ou peut-être il y a quelque chose de plus simple/rapide?

Cette question peut également être exprimé ainsi: Comment peut-on déterminer la distance d'édition entre deux chaînes de caractères lors de la seule opération autorisée est de transposition?

13voto

Borx Points 21

Concernant le nombre minimal de (pas nécessairement adjacents) swaps nécessaire pour convertir une permutation dans l'autre, la métrique que vous devez utiliser est le Cayley distance qui est essentiellement la taille de la permutation - le nombre de cycles.

Compter le nombre de cycles d'une permutation est une question triviale. Un exemple simple. Supposons que la permutation de 521634.

Si vous cochez la première position, vous avez 5, dans le 5ème vous avez 3 et dans le 3ème, vous avez 1, clôture le premier cycle. 2 est dans la 2ème position, il faire un cycle de lui-même et 4 et 6, le dernier cycle (4 est en 6ème position et 6 dans le 4ème). Si vous voulez convertir cette permutation dans l'identité de permutation (avec le nombre minimum de swaps de), vous devez réorganiser chaque cycle de façon indépendante. Le nombre total des swaps est la longueur de la permutation (6), moins le nombre de cycles (3).

Étant donné deux permutations, la distance entre eux est l'équivalent de la distance entre la composition de la première par l'inverse de la seconde et de l'identité (calculée comme indiqué ci-dessus). Par conséquent, la seule chose que vous devez faire est de composer la première permutation et de l'inverse de la deuxième et de compter le nombre de cycles dans le résultat. Toutes ces opérations sont O(n), de sorte que vous pouvez obtenir le nombre minimum de swaps dans le temps linéaire.

10voto

IVlad Points 20932

Voici une solution simple et efficace:

Laissez - Q[ s2[i] ] = the positions character s2[i] is on in s2. Laissez - P[i] = on what position is the character corresponding to s1[i] in the second string.

Pour construire Q et P:

for ( int i = 0; i < s1.size(); ++i )
    Q[ s2[i] ].push_back(i); // basically, Q is a vector [0 .. 25] of lists

temp[0 .. 25] = {0}
for ( int i = 0; i < s1.size(); ++i )
    P[i + 1] = 1 + Q[ s1[i] ][ temp[ s1[i] ]++ ];

Exemple:

    1234
s1: abcd
s2: acdb
Q: Q[a = 0] = {0}, Q[b = 1] = {3}, Q[c = 2] = {1}, Q[d = 3] = {2}
P: P[1] = 1, P[2] = 4 (because the b in s1 is on position 4 in s2), P[3] = 2
   P[4] = 3

P a 2 des inversions (4 2 et 4 3), de sorte que c'est la réponse.

Cette solution est - O(n log n) parce que la construction P et Q peut être fait en O(n) et de fusion de tri peut compter inversions O(n log n).

8voto

gausskern Points 51

Ce que vous cherchez peut-être identique au "Kendall tau distance", qui est le (normalisé) la différence d'concordantes moins discordants paires. Voir Wikipédia, où il est affirmé que c'est l'équivalent du tri à bulles distance.

Dans R, les fonctions sont avialable non seulement pour le calcul de la protéine tau, par exemple

cor( X, method="kendall", use="pairwise" ) ,

mais aussi pour tester la significativité de la différence, par exemple

cor.test( x1, x2, method="kendall" ) ,

et ils sont même en mesure de prendre correctement en compte les liens.

Voir ici pour plus d'.

2voto

bits_international Points 13725

J'ai écrit une classe Permutation qui, entre autres choses peuvent retourner un certain nombre de transpositions nécessaires pour convertir une permutation dans l'identité. Ceci est fait par la création d'orbites (cycles) de comptage et de leurs longueurs. La terminologie est pris de Kostrikin A., I., "Introduction à l'Algèbre Linéaire I".

Comprend:

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
#include <iterator>

classe de Permutation:

class Permutation {
public:
    struct ei_element {    /* element of the orbit*/
        int e; /* identity index */
        int i; /* actual permutation index */
    };
    typedef std::vector<ei_element> Orbit; /* a cycle */

    Permutation( std::vector<int> const& i_vector);
    /* permute i element, vector is 0 indexed */
    int pi( int i) const { return iv[ i - 1]; }
    int i( int k) const { return pi( k); } /* i_k = pi(k) */
    int q() const { /* TODO: return rank = q such that pi^q = e */ return 0; }
    int n() const { return n_; }
    /* return the sequence 1, 2, ..., n */
    std::vector<int> const& Omega() const { return ev; }
    /* return vector of cycles */
    std::vector<Orbit> const& orbits() const { return orbits_; }
    int l( int k) const { return orbits_[ k].size(); } /* length of k-th cycle */
    int transpositionsCount() const;  /* return sum of all transpositions */
    void make_orbits();

    private:
    struct Increment {
        int current;
        Increment(int start) : current(start) {}
        int operator() () {
            return current++;
        }
    };
    int n_;
    std::vector<int> iv; /* actual permutation */
    std::vector<int> ev; /* identity permutation */
    std::vector<Orbit> orbits_;
};

Définitions:

Permutation::Permutation( std::vector<int> const& i_vector) : 
                                                      n_( i_vector.size()), 
                                                      iv( i_vector), ev( n_) {
        if ( n_) { /* fill identity vector 1, 2, ..., n */
            Increment g ( 1);
            std::generate( ev.begin(), ev.end(), g);
        }
}

/* create orbits (cycles) */
void Permutation::make_orbits() {
    std::set<int> to_visit( ev.begin(), ev.end()); // identity elements to visit
    while ( !to_visit.empty()) {
        /* new cycle */
        Orbit orbit;
        int first_to_visit_e = *to_visit.begin();
        to_visit.erase( first_to_visit_e);
        int k = first_to_visit_e; // element in identity vector
        /* first orbit element */
        ei_element element;
        element.e = first_to_visit_e;
        element.i = i( first_to_visit_e);
        orbit.push_back( element);
        /* traverse permutation until cycle is closed */
        while ( pi( k) != first_to_visit_e && !to_visit.empty()) {
            k = pi( k);
            ei_element element;
            element.e = k;
            element.i = pi( k);
            orbit.push_back( element);
            to_visit.erase( k);
        }
        orbits_.push_back( orbit);
    }
}

et:

/* return sum of all transpositions */
int Permutation::transpositionsCount() const {
    int count = 0;
    int k = 0;
    while ( k < orbits_.size()) {
        count += l( k++) - 1; /* sum += l_k - 1 */ 
    }
    return count;
}

utilisation:

/*
 * 
 */
int main(int argc, char** argv) {
                       //1, 2, 3, 4, 5, 6, 7, 8       identity (e)
    int permutation[] = {2, 3, 4, 5, 1, 7, 6, 8}; //  actual (i)
    std::vector<int> vp( permutation, permutation + 8);

    Permutation p( vp);
    p.make_orbits();
    int k = p.orbits().size();
    std::cout << "Number of cycles:" << k << std::endl;

    for ( int i = 0; i < k; ++i) {
        std::vector<Permutation::ei_element> v = p.orbits()[ i];
        for ( int j = 0; j < v.size(); ++j) {
            std::cout << v[ j].e << "," << v[ j].i << " | ";
        }
        std::cout << std::endl;
    }

    std::cout << "Steps needed to create identity permutation: " 
                                                << p.transpositionsCount();
    return 0;
}

sortie:

Nombre de cycles:3

1,2 | 2,3 | 3,4 | 4,5 | 5,1 |

6,7 | 7,6 |

8,8 |

Les étapes nécessaires à la création de l'identité de permutation: 5

SUCCÈS (temps total: 82ms)


coliru

2voto

the swine Points 4204

La conversion de permutation de l'un à l'autre peuvent être convertis à un problème similaire (Nombre de swaps dans une permutation) par inversion de la cible de permutation en O(n), de la composition des permutations en O(n) et ensuite trouver le nombre de swaps de là, à une identité, d'une permutation. Donnée:

int P1[] = {0, 1, 2, 3}; // abcd
int P2[] = {0, 2, 3, 1}; // acdb

// we can follow a simple algebraic modification
// (see http://en.wikipedia.org/wiki/Permutation#Product_and_inverse):
// P1 * P = P2                   | premultiply P1^-1 *
// P1^-1 * P1 * P = P1^-1 * P2
// I * P = P1^-1 * P2
// P = P1^-1 * P2
// where P is a permutation that makes P1 into P2.
// also, the number of steps from P to identity equals
// the number of steps from P1 to P2.

int P1_inv[4];
for(int i = 0; i < 4; ++ i)
    P1_inv[P1[i]] = i;
// invert the first permutation O(n)

int P[4];
for(int i = 0; i < 4; ++ i)
    P[i] = P2[P1[i]];
// chain the permutations

int num_steps = NumSteps(P, 4); // will return 2
// now we just need to count the steps

Pour compter les étapes, un algorithme simple peut être mis au point, tels que:

int NumSteps(int *P, int n)
{
    int count = 0;
    for(int i = 0; i < n; ++ i) {
        for(; P[i] != i; ++ count) // could be permuted multiple times
            swap(P[P[i]], P[i]); // look where the number at hand should be
    }
    // count number of permutations

    return count;
}

Ce sont toujours les swaps d'un élément à un endroit où il devrait être dans l'identité de permutation, par conséquent, à chaque étape, elle annule et compte un swap. Maintenant, à condition que le nombre de swaps qu'elle renvoie est en effet minimum, le moteur d'exécution de l'algorithme est délimitée par elle et est assuré de terminer (au lieu de rester coincé dans une boucle infinie). Il sera exécuté en O(m) swaps ou O(m + n) itérations de boucle où l' m est le nombre de swaps (le count retournée) et n est le nombre d'éléments dans la séquence (4). Notez que m < n est toujours vrai. Par conséquent, il doit être supérieure à O(n log n) solutions, comme la limite supérieure est O(n - 1) de swaps ou d' O(n + n - 1) d'itérations de boucle ici, qui est à la fois pratiquement O(n) (facteur constant de 2 omis dans le dernier cas).

L'algorithme ne fonctionne que pour les valides permutations, il sera en boucle à l'infini pour les séquences avec des valeurs en double et faire sortir des limites d'un tableau de l'accès (et crash) pour les séquences avec les valeurs autres que d' [0, n). Un test complet de cas peut être trouvé ici (construit avec Visual Studio 2008, l'algorithme lui-même devrait être assez portable). Il génère toutes les permutations possibles des longueurs de 1 à 32 et contrôles contre les solutions, généré avec la largeur de la première recherche (BFS) semble fonctionner pour toutes les permutations des longueurs de 1 à 12, puis il devient assez lent, mais je suppose qu'il va juste continuer à travailler.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X