Permutazione veloce -> numero -> algoritmi di mappatura delle permutazioni


113

Ho n elementi. Ad esempio, diciamo 7 elementi, 1234567. So che ce ne sono 7! = 5040 permutazioni possibili di questi 7 elementi.

Voglio un algoritmo veloce che comprenda due funzioni:

f (numero) mappa un numero compreso tra 0 e 5039 a una permutazione univoca, e

f '(permutazione) mappa la permutazione di nuovo al numero da cui è stata generata.

Non mi interessa la corrispondenza tra numero e permutazione, a condizione che ogni permutazione abbia il proprio numero univoco.

Quindi, ad esempio, potrei avere funzioni dove

f(0) = '1234567'
f'('1234567') = 0

L'algoritmo più veloce che viene in mente è quello di enumerare tutte le permutazioni e creare una tabella di ricerca in entrambe le direzioni, in modo che, una volta create le tabelle, f (0) sarebbe O (1) e f ('1234567') sarebbe un ricerca su una stringa. Tuttavia, questo è affamato di memoria, in particolare quando n diventa grande.

Qualcuno può proporre un altro algoritmo che funzioni rapidamente e senza svantaggio di memoria?


Sebbene l'algoritmo di seguito sia molto completo, indichi correttamente che l'algoritmo più veloce è una tabella di ricerca. Non stai davvero parlando di "così tanta" memoria, anche se ovviamente dipende dal tuo sistema e dalla tua piattaforma. Ma se una tabella di ricerca sarà sufficiente e se questa è un'applicazione del mondo reale, usala. Veloce e semplice!
Kirk Broadhurst

14
Lo dici tu, ma n non deve diventare molto grande perché sia ​​sciocco. Per 12 elementi, 12! è 479.001.600 permutazioni. È una grande tabella di ricerca!
ijw

Non farti confondere da post diversi usa n per un significato diverso. Alcuni n stanno per la lunghezza della stringa, altri n stanno per il conteggio delle possibili permutazioni. Non confrontare ciecamente la grande nozione O. - Attenzione ai ritardatari - -
把 友情 留 在 无 盐

Risposte:


157

Per descrivere una permutazione di n elementi, vedi che per la posizione in cui finisce il primo elemento, hai n possibilità, quindi puoi descriverlo con un numero compreso tra 0 e n-1. Per la posizione in cui finisce l'elemento successivo, hai n-1 possibilità rimanenti, quindi puoi descriverlo con un numero compreso tra 0 e n-2.
Eccetera finché non hai n numeri.

Come esempio per n = 5, considera la permutazione che porta abcdea caebd.

  • a, il primo elemento, finisce alla seconda posizione, quindi gli assegniamo indice 1 .
  • bfinisce alla quarta posizione, che sarebbe indice 3, ma è la terza rimasta, quindi gli assegniamo 2 .
  • cfinisce alla prima posizione rimanente, che è sempre 0 .
  • dfinisce nell'ultima posizione rimasta, che (su due sole posizioni rimanenti) è 1 .
  • efinisce nell'unica posizione rimasta, indicizzata a 0 .

Quindi abbiamo la sequenza indice {1, 2, 0, 1, 0} .

Ora sai che, ad esempio, in un numero binario, "xyz" significa z + 2y + 4x. Per un numero decimale,
è z + 10y + 100x. Ogni cifra viene moltiplicata per un certo peso e i risultati vengono sommati. Il modello ovvio nel peso è ovviamente che il peso è w = b ^ k, con b la base del numero e k l'indice della cifra. (Conterò sempre le cifre da destra e partendo dall'indice 0 per la cifra più a destra. Allo stesso modo quando parlo della "prima" cifra intendo la cifra più a destra.)

Il motivo per cui i pesi delle cifre seguono questo schema è che il numero più alto che può essere rappresentato dalle cifre da 0 a k deve essere esattamente 1 inferiore al numero più basso che può essere rappresentato utilizzando solo la cifra k + 1. In binario, 0111 deve essere uno inferiore a 1000. In decimale, 099999 deve essere uno inferiore a 100000.

Codifica in base variabile
La spaziatura tra i numeri successivi che è esattamente 1 è la regola importante. Rendendoci conto, possiamo rappresentare la nostra sequenza indice con un numero a base variabile . La base per ogni cifra è la quantità di diverse possibilità per quella cifra. Per i decimali ogni cifra ha 10 possibilità, per il nostro sistema la cifra più a destra avrebbe 1 possibilità e quella più a sinistra avrà n possibilità. Ma poiché la cifra più a destra (l'ultimo numero nella nostra sequenza) è sempre 0, la tralasciamo. Ciò significa che ci rimangono le basi da 2 a n. In generale, la k-esima cifra avrà base b [k] = k + 2. Il valore più alto consentito per la cifra k è h [k] = b [k] - 1 = k + 1.

La nostra regola sui pesi w [k] delle cifre richiede che la somma di h [i] * w [i], dove i va da i = 0 a i = k, sia uguale a 1 * w [k + 1]. Dichiarato in modo ricorrente, w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). Il primo peso w [0] dovrebbe sempre essere 1. Partendo da lì, abbiamo i seguenti valori:

k    h[k] w[k]    

0    1    1  
1    2    2    
2    3    6    
3    4    24   
...  ...  ...
n-1  n    n!  

(La relazione generale w [k-1] = k! È facilmente dimostrata per induzione.)

Il numero che otteniamo convertendo la nostra sequenza sarà quindi la somma di s [k] * w [k], con k che va da 0 a n-1. Qui s [k] è l'elemento k-esimo (più a destra, a partire da 0) della sequenza. Ad esempio, prendi il nostro {1, 2, 0, 1, 0}, con l'elemento più a destra rimosso come menzionato prima: {1, 2, 0, 1} . La nostra somma è 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .

Nota che se prendiamo la posizione massima per ogni indice, avremmo {4, 3, 2, 1, 0} e questo viene convertito in 119. Poiché i pesi nella nostra codifica numerica sono stati scelti in modo da non saltare qualsiasi numero, tutti i numeri da 0 a 119 sono validi. Ce ne sono esattamente 120, ovvero n! per n = 5 nel nostro esempio, precisamente il numero di diverse permutazioni. Quindi puoi vedere i nostri numeri codificati che specificano completamente tutte le possibili permutazioni.

La decodifica dalla decodifica a base variabile
è simile alla conversione in binario o decimale. L'algoritmo comune è questo:

int number = 42;
int base = 2;
int[] bits = new int[n];

for (int k = 0; k < bits.Length; k++)
{
    bits[k] = number % base;
    number = number / base;
}

Per il nostro numero a base variabile:

int n = 5;
int number = 37;

int[] sequence = new int[n - 1];
int base = 2;

for (int k = 0; k < sequence.Length; k++)
{
    sequence[k] = number % base;
    number = number / base;

    base++; // b[k+1] = b[k] + 1
}

Questo decodifica correttamente il nostro 37 di nuovo a {1, 2, 0, 1} ( sequencesarebbe {1, 0, 2, 1}in questo esempio di codice, ma qualunque cosa ... purché indicizzi in modo appropriato). Dobbiamo solo aggiungere 0 all'estremità destra (ricorda che l'ultimo elemento ha sempre una sola possibilità per la sua nuova posizione) per recuperare la nostra sequenza originale {1, 2, 0, 1, 0}.

Permutare un elenco utilizzando una sequenza indice
È possibile utilizzare l'algoritmo seguente per permutare un elenco in base a una sequenza indice specifica. Purtroppo è un algoritmo O (n²).

int n = 5;
int[] sequence = new int[] { 1, 2, 0, 1, 0 };
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
bool[] set = new bool[n];

for (int i = 0; i < n; i++)
{
    int s = sequence[i];
    int remainingPosition = 0;
    int index;

    // Find the s'th position in the permuted list that has not been set yet.
    for (index = 0; index < n; index++)
    {
        if (!set[index])
        {
            if (remainingPosition == s)
                break;

            remainingPosition++;
        }
    }

    permuted[index] = list[i];
    set[index] = true;
}

Rappresentazione comune delle permutazioni
Normalmente non rappresenteresti una permutazione in modo non intuitivo come abbiamo fatto, ma semplicemente dalla posizione assoluta di ogni elemento dopo che la permutazione è stata applicata. Il nostro esempio {1, 2, 0, 1, 0} per abcdeto caebdè normalmente rappresentato da {1, 3, 0, 4, 2}. Ogni indice da 0 a 4 (o in generale da 0 a n-1) si verifica esattamente una volta in questa rappresentazione.

Applicare una permutazione in questa forma è facile:

int[] permutation = new int[] { 1, 3, 0, 4, 2 };

char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];

for (int i = 0; i < n; i++)
{
    permuted[permutation[i]] = list[i];
}

L'inversione è molto simile:

for (int i = 0; i < n; i++)
{
    list[i] = permuted[permutation[i]];
}

Conversione dalla nostra rappresentazione alla rappresentazione comune
Nota che se prendiamo il nostro algoritmo per permutare una lista usando la nostra sequenza indice e lo applichiamo alla permutazione di identità {0, 1, 2, ..., n-1}, otteniamo il permutazione inversa , rappresentata nella forma comune. ( {2, 0, 4, 1, 3} nel nostro esempio).

Per ottenere la premutazione non invertita, applichiamo l'algoritmo di permutazione che ho appena mostrato:

int[] identity = new int[] { 0, 1, 2, 3, 4 };
int[] inverted = { 2, 0, 4, 1, 3 };
int[] normal = new int[n];

for (int i = 0; i < n; i++)
{
    normal[identity[i]] = list[i];
}

Oppure puoi semplicemente applicare la permutazione direttamente, utilizzando l'algoritmo di permutazione inversa:

char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];

int[] inverted = { 2, 0, 4, 1, 3 };

for (int i = 0; i < n; i++)
{
    permuted[i] = list[inverted[i]];
}

Nota che tutti gli algoritmi per trattare le permutazioni nella forma comune sono O (n), mentre l'applicazione di una permutazione nella nostra forma è O (n²). Se è necessario applicare una permutazione più volte, convertirla prima nella rappresentazione comune.


6
In "Permutare una lista usando una sequenza indice", menzioni un algoritmo quadratico. Questo va sicuramente bene perché n sarà probabilmente molto piccolo. Questo può essere "facilmente" ridotto a O (nlogn) attraverso un albero delle statistiche dell'ordine ( pine.cs.yale.edu/pinewiki/OrderStatisticsTree ), cioè un albero rosso-nero che inizialmente conterrà i valori 0, 1, 2 , ..., n-1 e ogni nodo contiene il numero di discendenti al di sotto di esso. Con questo, si può trovare / rimuovere l'elemento k-esimo in tempo O (logn).
Dimitris Andreou

11
Questi sono indicati come codici lehmer. Questo link li spiega anche bene, keithschwarz.com/interesting/code/?dir=factoradic-permutation
mihirg

Questo algoritmo è fantastico, ma ho appena scoperto che diversi casi sono sbagliati. Prendi la stringa "123"; la quarta permutazione dovrebbe essere 231, ma secondo questo algoritmo, sarà 312. diciamo 1234, la quarta permutazione dovrebbe essere 1342, ma sarà erroneamente "1423". Correggimi se ho osservato male. Grazie.
Isaac Li

@IsaacLi, se ho ragione, f (4) = {2, 0, 0} = 231. Ed f '(312) = {1, 1, 0} = 3. Per 1234, f (4) = {0, 2, 0, 0} = 1342. E f '(1423) = {0, 1 1, 0} = 3. Questo algoritmo è davvero stimolante. Mi chiedo che sia il lavoro originale dell'OP. L'ho studiato e analizzato per un po '. E credo che sia corretto :)
Midnite

Come convertire da "nostra rappresentazione" a "rappresentazione comune", {1, 2, 0, 1, 0}-> {1, 3, 0, 4, 2}? E viceversa? È possibile? ( non convertendo tra {1, 2, 0, 1, 0}<--> {C, A, E, B, D}, che necessita di O (n ^ 2).) Se "il nostro stile" e "stile comune" non sono convertibili, sono in realtà due cose distinte, non è vero? Grazie x
midnite

18

Ho trovato un algoritmo O (n), ecco una breve spiegazione http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html

public static int[] perm(int n, int k)
{
    int i, ind, m=k;
    int[] permuted = new int[n];
    int[] elems = new int[n];

    for(i=0;i<n;i++) elems[i]=i;

    for(i=0;i<n;i++)
    {
            ind=m%(n-i);
            m=m/(n-i);
            permuted[i]=elems[ind];
            elems[ind]=elems[n-i-1];
    }

    return permuted;
}

public static int inv(int[] perm)
{
    int i, k=0, m=1;
    int n=perm.length;
    int[] pos = new int[n];
    int[] elems = new int[n];

    for(i=0;i<n;i++) {pos[i]=i; elems[i]=i;}

    for(i=0;i<n-1;i++)
    {
            k+=m*pos[perm[i]];
            m=m*(n-i);
            pos[elems[n-i-1]]=pos[perm[i]];
            elems[pos[perm[i]]]=elems[n-i-1];
    }

    return k;
}

1
Se capisco molto bene il tuo algoritmo. Stai trovando tutte le possibilità codificate (in questo caso dovrebbe essere n! Possibilità). Quindi mappi i numeri in base all'elemento codificato.
user3378649

Ho aggiunto una breve spiegazione sul mio blog.
Antoine Comeau

1
Questo è eccezionalmente pulito. Oggi ho escogitato lo stesso metodo da solo, ma non ho capito che potresti tralasciare due incarichi al contrario.
fuz

Non confrontare ciecamente la grande nozione O, poiché la n in questa risposta sta per non uguale ad altre risposte - come sottolinea @ user3378649 - denota una proporzione di complessità al fattoriale della lunghezza della stringa. Questa risposta è davvero meno efficiente.
把 友情 留 在 无 盐

Può essere adattato all'ordine lessicografico?
Gregory Morse il

7

La complessità può essere ridotta a n * log (n), vedere la sezione 10.1.1 ("Il codice Lehmer (tabella di inversione)", p.232ff) di fxtbook: http://www.jjj.de/fxt/ #fxtbook passa alla sezione 10.1.1.1 ("Calcolo con array di grandi dimensioni" p.235) per il metodo veloce. Il codice (GPLed, C ++) si trova sulla stessa pagina web.


5

Problema risolto. Tuttavia, non sono sicuro che tu abbia ancora bisogno della soluzione dopo questi anni. LOL, mi iscrivo a questo sito, quindi ... Controlla la mia classe di permutazione Java. Puoi basarti su un indice per ottenere una permutazione del simbolo, oppure dare una permutazione al simbolo e poi ottenere l'indice.

Ecco la mia classe di premutazione

/**
 ****************************************************************************************************************
 * Copyright 2015 Fred Pang fred@pnode.com
 ****************************************************************************************************************
 * A complete list of Permutation base on an index.
 * Algorithm is invented and implemented by Fred Pang fred@pnode.com
 * Created by Fred Pang on 18/11/2015.
 ****************************************************************************************************************
 * LOL this is my first Java project. Therefore, my code is very much like C/C++. The coding itself is not
 * very professional. but...
 *
 * This Permutation Class can be use to generate a complete list of all different permutation of a set of symbols.
 * nPr will be n!/(n-r)!
 * the user can input       n = the number of items,
 *                          r = the number of slots for the items,
 *                          provided n >= r
 *                          and a string of single character symbols
 *
 * the program will generate all possible permutation for the condition.
 *
 * Say if n = 5, r = 3, and the string is "12345", it will generate sll 60 different permutation of the set
 * of 3 character strings.
 *
 * The algorithm I used is base on a bin slot.
 * Just like a human or simply myself to generate a permutation.
 *
 * if there are 5 symbols to chose from, I'll have 5 bin slot to indicate which symbol is taken.
 *
 * Note that, once the Permutation object is initialized, or after the constructor is called, the permutation
 * table and all entries are defined, including an index.
 *
 * eg. if pass in value is 5 chose 3, and say the symbol string is "12345"
 * then all permutation table is logically defined (not physically to save memory).
 * It will be a table as follows
 *  index  output
 *      0   123
 *      1   124
 *      2   125
 *      3   132
 *      4   134
 *      5   135
 *      6   143
 *      7   145
 *      :     :
 *      58  542
 *      59  543
 *
 * all you need to do is call the "String PermGetString(int iIndex)" or the "int[] PermGetIntArray(int iIndex)"
 * function or method with an increasing iIndex, starting from 0 to getiMaxIndex() - 1. It will return the string
 * or the integer array corresponding to the index.
 *
 * Also notice that in the input string is "12345" of  position 01234, and the output is always in accenting order
 * this is how the permutation is generated.
 *
 * ***************************************************************************************************************
 * ====  W a r n i n g  ====
 * ***************************************************************************************************************
 *
 * There is very limited error checking in this class
 *
 * Especially the  int PermGetIndex(int[] iInputArray)  method
 * if the input integer array contains invalid index, it WILL crash the system
 *
 * the other is the string of symbol pass in when the object is created, not sure what will happen if the
 * string is invalid.
 * ***************************************************************************************************************
 *
 */
public class Permutation
{
    private boolean bGoodToGo = false;      // object status
    private boolean bNoSymbol = true;
    private BinSlot slot;                   // a bin slot of size n (input)
    private int nTotal;                     // n number for permutation
    private int rChose;                     // r position to chose
    private String sSymbol;                 // character string for symbol of each choice
    private String sOutStr;
    private int iMaxIndex;                  // maximum index allowed in the Get index function
    private int[] iOutPosition;             // output array
    private int[] iDivisorArray;            // array to do calculation

    public Permutation(int inCount, int irCount, String symbol)
    {
        if (inCount >= irCount)
        {
            // save all input values passed in
            this.nTotal = inCount;
            this.rChose = irCount;
            this.sSymbol = symbol;

            // some error checking
            if (inCount < irCount || irCount <= 0)
                return;                                 // do nothing will not set the bGoodToGo flag

            if (this.sSymbol.length() >= inCount)
            {
                bNoSymbol = false;
            }

            // allocate output storage
            this.iOutPosition = new int[this.rChose];

            // initialize the bin slot with the right size
            this.slot = new BinSlot(this.nTotal);

            // allocate and initialize divid array
            this.iDivisorArray = new int[this.rChose];

            // calculate default values base on n & r
            this.iMaxIndex = CalPremFormula(this.nTotal, this.rChose);

            int i;
            int j = this.nTotal - 1;
            int k = this.rChose - 1;

            for (i = 0; i < this.rChose; i++)
            {
                this.iDivisorArray[i] = CalPremFormula(j--, k--);
            }
            bGoodToGo = true;       // we are ready to go
        }
    }

    public String PermGetString(int iIndex)
    {
        if (!this.bGoodToGo) return "Error: Object not initialized Correctly";
        if (this.bNoSymbol) return "Error: Invalid symbol string";
        if (!this.PermEvaluate(iIndex)) return "Invalid Index";

        sOutStr = "";
        // convert string back to String output
        for (int i = 0; i < this.rChose; i++)
        {
            String sTempStr = this.sSymbol.substring(this.iOutPosition[i], iOutPosition[i] + 1);
            this.sOutStr = this.sOutStr.concat(sTempStr);
        }
        return this.sOutStr;
    }

    public int[] PermGetIntArray(int iIndex)
    {
        if (!this.bGoodToGo) return null;
        if (!this.PermEvaluate(iIndex)) return null ;
        return this.iOutPosition;
    }

    // given an int array, and get the index back.
    //
    //  ====== W A R N I N G ======
    //
    // there is no error check in the array that pass in
    // if any invalid value in the input array, it can cause system crash or other unexpected result
    //
    // function pass in an int array generated by the PermGetIntArray() method
    // then return the index value.
    //
    // this is the reverse of the PermGetIntArray()
    //
    public int PermGetIndex(int[] iInputArray)
    {
        if (!this.bGoodToGo) return -1;
        return PermDoReverse(iInputArray);
    }


    public int getiMaxIndex() {
    return iMaxIndex;
}

    // function to evaluate nPr = n!/(n-r)!
    public int CalPremFormula(int n, int r)
    {
        int j = n;
        int k = 1;
        for (int i = 0; i < r; i++, j--)
        {
            k *= j;
        }
        return k;
    }


//  PermEvaluate function (method) base on an index input, evaluate the correspond permuted symbol location
//  then output it to the iOutPosition array.
//
//  In the iOutPosition[], each array element corresponding to the symbol location in the input string symbol.
//  from location 0 to length of string - 1.

    private boolean PermEvaluate(int iIndex)
    {
        int iCurrentIndex;
        int iCurrentRemainder;
        int iCurrentValue = iIndex;
        int iCurrentOutSlot;
        int iLoopCount;

        if (iIndex >= iMaxIndex)
            return false;

        this.slot.binReset();               // clear bin content
        iLoopCount = 0;
        do {
            // evaluate the table position
            iCurrentIndex = iCurrentValue / this.iDivisorArray[iLoopCount];
            iCurrentRemainder = iCurrentValue % this.iDivisorArray[iLoopCount];

            iCurrentOutSlot = this.slot.FindFreeBin(iCurrentIndex);     // find an available slot
            if (iCurrentOutSlot >= 0)
                this.iOutPosition[iLoopCount] = iCurrentOutSlot;
            else return false;                                          // fail to find a slot, quit now

            this.slot.setStatus(iCurrentOutSlot);                       // set the slot to be taken
            iCurrentValue = iCurrentRemainder;                          // set new value for current value.
            iLoopCount++;                                               // increase counter
        } while (iLoopCount < this.rChose);

        // the output is ready in iOutPosition[]
        return true;
    }

    //
    // this function is doing the reverse of the permutation
    // the input is a permutation and will find the correspond index value for that entry
    // which is doing the opposit of the PermEvaluate() method
    //
    private int PermDoReverse(int[] iInputArray)
    {
        int iReturnValue = 0;
        int iLoopIndex;
        int iCurrentValue;
        int iBinLocation;

        this.slot.binReset();               // clear bin content

        for (iLoopIndex = 0; iLoopIndex < this.rChose; iLoopIndex++)
        {
            iCurrentValue = iInputArray[iLoopIndex];
            iBinLocation = this.slot.BinCountFree(iCurrentValue);
            this.slot.setStatus(iCurrentValue);                          // set the slot to be taken
            iReturnValue = iReturnValue + iBinLocation * this.iDivisorArray[iLoopIndex];
        }
        return iReturnValue;
    }


    /*******************************************************************************************************************
     *******************************************************************************************************************
     * Created by Fred on 18/11/2015.   fred@pnode.com
     *
     * *****************************************************************************************************************
     */
    private static class BinSlot
    {
        private int iBinSize;       // size of array
        private short[] eStatus;    // the status array must have length iBinSize

        private BinSlot(int iBinSize)
        {
            this.iBinSize = iBinSize;               // save bin size
            this.eStatus = new short[iBinSize];     // llocate status array
        }

        // reset the bin content. no symbol is in use
        private void binReset()
        {
            // reset the bin's content
            for (int i = 0; i < this.iBinSize; i++) this.eStatus[i] = 0;
        }

        // set the bin position as taken or the number is already used, cannot be use again.
        private void  setStatus(int iIndex) { this.eStatus[iIndex]= 1; }

        //
        // to search for the iIndex th unused symbol
        // this is important to search through the iindex th symbol
        // because this is how the table is setup. (or the remainder means)
        // note: iIndex is the remainder of the calculation
        //
        // for example:
        // in a 5 choose 3 permutation symbols "12345",
        // the index 7 item (count starting from 0) element is "1 4 3"
        // then comes the index 8, 8/12 result 0 -> 0th symbol in symbol string = '1'
        // remainder 8. then 8/3 = 2, now we need to scan the Bin and skip 2 unused bins
        //              current the bin looks 0 1 2 3 4
        //                                    x o o o o     x -> in use; o -> free only 0 is being used
        //                                      s s ^       skipped 2 bins (bin 1 and 2), we get to bin 3
        //                                                  and bin 3 is the bin needed. Thus symbol "4" is pick
        // in 8/3, there is a remainder 2 comes in this function as 2/1 = 2, now we have to pick the empty slot
        // for the new 2.
        // the bin now looks 0 1 2 3 4
        //                   x 0 0 x 0      as bin 3 was used by the last value
        //                     s s   ^      we skip 2 free bins and the next free bin is bin 4
        //                                  therefor the symbol "5" at the symbol array is pick.
        //
        // Thus, for index 8  "1 4 5" is the symbols.
        //
        //
        private int FindFreeBin(int iIndex)
        {
            int j = iIndex;

            if (j < 0 || j > this.iBinSize) return -1;               // invalid index

            for (int i = 0; i < this.iBinSize; i++)
            {
                if (this.eStatus[i] == 0)       // is it used
                {
                    // found an empty slot
                    if (j == 0)                 // this is a free one we want?
                        return i;               // yes, found and return it.
                    else                        // we have to skip this one
                        j--;                    // else, keep looking and count the skipped one
                }
            }
            assert(true);           // something is wrong
            return -1;              // fail to find the bin we wanted
        }

        //
        // this function is to help the PermDoReverse() to find out what is the corresponding
        // value during should be added to the index value.
        //
        // it is doing the opposite of int FindFreeBin(int iIndex) method. You need to know how this
        // FindFreeBin() works before looking into this function.
        //
        private int BinCountFree(int iIndex)
        {
            int iRetVal = 0;
            for (int i = iIndex; i > 0; i--)
            {
                if (this.eStatus[i-1] == 0)       // it is free
                {
                    iRetVal++;
                }
            }
            return iRetVal;
        }
    }
}
// End of file - Permutation.java

ed ecco la mia classe principale per mostrare come usare la classe.

/*
 * copyright 2015 Fred Pang
 *
 * This is the main test program for testing the Permutation Class I created.
 * It can be use to demonstrate how to use the Permutation Class and its methods to generate a complete
 * list of a permutation. It also support function to get back the index value as pass in a permutation.
 *
 * As you can see my Java is not very good. :)
 * This is my 1st Java project I created. As I am a C/C++ programmer for years.
 *
 * I still have problem with the Scanner class and the System class.
 * Note that there is only very limited error checking
 *
 *
 */

import java.util.Scanner;

public class Main
{
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args)
    {
        Permutation perm;       // declear the object
        String sOutString = "";
        int nCount;
        int rCount;
        int iMaxIndex;

        // Get user input
        System.out.println("Enter n: ");
        nCount = scanner.nextInt();

        System.out.println("Enter r: ");
        rCount = scanner.nextInt();

        System.out.println("Enter Symbol: ");
        sOutString = scanner.next();

        if (sOutString.length() < rCount)
        {
            System.out.println("String too short, default to numbers");
            sOutString = "";
        }

        // create object with user requirement
        perm = new Permutation(nCount, rCount, sOutString);

        // and print the maximum count
        iMaxIndex = perm.getiMaxIndex();
        System.out.println("Max count is:" + iMaxIndex);

        if (!sOutString.isEmpty())
        {
            for (int i = 0; i < iMaxIndex; i++)
            {   // print out the return permutation symbol string
                System.out.println(i + " " + perm.PermGetString(i));
            }
        }
        else
        {
            for (int i = 0; i < iMaxIndex; i++)
            {
                System.out.print(i + " ->");

                // Get the permutation array
                int[] iTemp = perm.PermGetIntArray(i);

                // print out the permutation
                for (int j = 0; j < rCount; j++)
                {
                    System.out.print(' ');
                    System.out.print(iTemp[j]);
                }

                // to verify my PermGetIndex() works. :)
                if (perm.PermGetIndex(iTemp)== i)
                {
                    System.out.println(" .");
                }
                else
                {   // oops something is wrong :(
                    System.out.println(" ***************** F A I L E D *************************");
                    assert(true);
                    break;
                }
            }
        }
    }
}
//
// End of file - Main.java

Divertiti. :)


4

Ogni elemento può essere in una delle sette posizioni. Per descrivere la posizione di un elemento, avresti bisogno di tre bit. Ciò significa che puoi memorizzare la posizione di tutti gli elementi in un valore a 32 bit. Questo è ben lungi dall'essere efficiente, poiché questa rappresentazione consentirebbe anche a tutti gli elementi di essere nella stessa posizione, ma credo che il mascheramento dei bit dovrebbe essere ragionevolmente veloce.

Tuttavia, con più di 8 posizioni avrai bisogno di qualcosa di più elegante.


Ciò presuppone che all'OP non importi se l'enumerazione va effettivamente da 0 a 5039, giusto? Se va bene, questa sembra un'ottima soluzione.
Troubadour

4

Questa sembra essere una funzione incorporata in J :

   A. 1 2 3 4 5 6 7
0
   0 A. 1 2 3 4 5 6 7
1 2 3 4 5 6 7

   ?!7
5011
   5011 A. 1 2 3 4 5 6 7
7 6 4 5 1 3 2
   A. 7 6 4 5 1 3 2
5011

2

Puoi codificare le permutazioni utilizzando un algoritmo ricorsivo. Se una N-permutazione (un certo ordine dei numeri {0, .., N-1}) è della forma {x, ...} allora codificala come x + N * la codifica di (N-1) -permutazione rappresentata da "..." sui numeri {0, N-1} - {x}. Sembra un boccone, ecco un codice:

// perm[0]..perm[n-1] must contain the numbers in {0,..,n-1} in any order.
int permToNumber(int *perm, int n) {
  // base case
  if (n == 1) return 0;

  // fix up perm[1]..perm[n-1] to be a permutation on {0,..,n-2}.
  for (int i = 1; i < n; i++) {
    if (perm[i] > perm[0]) perm[i]--;
  }

  // recursively compute
  return perm[0] + n * permToNumber(perm + 1, n - 1);
}

// number must be >=0, < n!
void numberToPerm(int number, int *perm, int n) {
  if (n == 1) {
    perm[0] = 0;
    return;
  }
  perm[0] = number % n;
  numberToPerm(number / n, perm + 1, n - 1);

  // fix up perm[1] .. perm[n-1]
  for (int i = 1; i < n; i++) {
    if (perm[i] >= perm[0]) perm[i]++;
  }
}

Questo algoritmo è O (n ^ 2). Punti bonus se qualcuno ha un algoritmo O (n).


1

Che domanda interessante!

Se tutti i tuoi elementi sono numeri, potresti prendere in considerazione la possibilità di convertirli da stringhe a numeri effettivi. Quindi saresti in grado di ordinare tutte le permutazioni mettendole in ordine e inserendole in un array. Dopodiché, sarai aperto a uno qualsiasi dei vari algoritmi di ricerca disponibili.


1

Sono stato frettoloso nella mia risposta precedente (cancellata), però ho la risposta effettiva. È fornito da un concetto simile, il fattoriale , ed è correlato alle permutazioni (la mia risposta è relativa alle combinazioni, mi scuso per quella confusione). Odio pubblicare solo link di wikipedia, ma ho scritto che ho fatto un po 'di tempo fa è incomprensibile per qualche motivo. Quindi, posso ampliarlo più tardi se richiesto.


1

C'è un libro scritto su questo. Scusa, ma non ricordo il nome (lo troverai molto probabilmente da wikipedia). ma comunque ho scritto un'implementazione python di quel sistema di enumerazione: http://kks.cabal.fi/Kombinaattori Alcuni sono in finlandese, ma copia semplicemente il codice e le variabili del nome ...


0

Avevo questa domanda esatta e ho pensato di fornire la mia soluzione Python. È O (n ^ 2).

import copy

def permute(string, num):
    ''' generates a permutation '''
    def build_s(factoradic): # Build string from factoradic in list form
        string0 = copy.copy(string)
        n = []
        for i in range(len(factoradic)):
            n.append(string0[factoradic[i]])
            del string0[factoradic[i]]
        return n

    f = len(string)
    factoradic = []
    while(f != 0): # Generate factoradic number list
        factoradic.append(num % f)
        num = (num - factoradic[-1])//f
        f -= 1

    return build_s(factoradic)

s = set()
# Print 120 permutations of this string
for i in range(120):
    m = permute(list('abcde'), i)
    s.add(''.join(m))

print(len(s)) # Check that we have 120 unique permutations

È abbastanza semplice; dopo aver generato la rappresentazione fattoriale del numero, seleziono e rimuovo i caratteri dalla stringa. L'eliminazione dalla stringa è il motivo per cui questa è una soluzione O (n ^ 2).

La soluzione di Antoine è migliore per le prestazioni.


-1

Una domanda correlata è il calcolo della permutazione inversa, una permutazione che ripristinerà i vettori permutati all'ordine originale quando è noto solo l'array di permutazioni. Ecco il codice O (n) (in PHP):

// Compute the inverse of a permutation
function GetInvPerm($Perm)
    {
    $n=count($Perm);
    $InvPerm=[];
    for ($i=0; $i<$n; ++$i)
        $InvPerm[$Perm[$i]]=$i;
    return $InvPerm;
    } // GetInvPerm

David Spector Springtime Software

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.