Mam n elementów. Dla przykładu, powiedzmy 7 elementów 1234567. Wiem, że jest ich 7! = 5040 możliwych permutacji tych 7 elementów.
Chcę mieć szybki algorytm składający się z dwóch funkcji:
f (liczba) odwzorowuje liczbę od 0 do 5039 na unikalną permutację, a
f '(permutacja) odwzorowuje permutację z powrotem na liczbę, z której została wygenerowana.
Nie obchodzi mnie zgodność między liczbą a permutacją, pod warunkiem, że każda permutacja ma swój własny unikalny numer.
Na przykład mógłbym mieć funkcje, w których
f(0) = '1234567'
f'('1234567') = 0
Najszybszym algorytmem, jaki przychodzi na myśl, jest wyliczenie wszystkich permutacji i utworzenie tabeli przeglądowej w obu kierunkach, tak aby po utworzeniu tabel f (0) było O (1) if ('1234567') było wyszukiwanie na łańcuchu. Jednak jest to głodne pamięci, szczególnie gdy n staje się duże.
Czy ktoś może zaproponować inny algorytm, który działałby szybko i bez wad pamięci?
Odpowiedzi:
Aby opisać permutację n elementów, widzisz, że dla pozycji, na której kończy się pierwszy element, masz n możliwości, więc możesz to opisać liczbą od 0 do n-1. Dla pozycji, na której kończy się następny element, masz jeszcze n-1 możliwości, więc możesz to opisać liczbą od 0 do n-2.
Et cetera, dopóki nie masz n liczb.
Jako przykład dla n = 5, rozważ permutację, która prowadzi
abcde
docaebd
.a
, pierwszy element, kończy się na drugiej pozycji, więc przypisujemy mu indeks 1 .b
kończy na czwartej pozycji, która byłaby indeksem 3, ale jest to trzecia pozostała pozycja, więc przypisujemy jej 2 .c
kończy się na pierwszej pozostałej pozycji, która zawsze wynosi 0 .d
kończy na ostatniej pozostającej pozycji, która (z tylko dwóch pozostałych pozycji) wynosi 1 .e
kończy się na jedynej pozostałej pozycji, indeksowanej na 0 .Mamy więc sekwencję indeksów {1, 2, 0, 1, 0} .
Teraz wiesz, że na przykład w liczbie binarnej „xyz” oznacza z + 2y + 4x. Dla liczby dziesiętnej
jest to z + 10y + 100x. Każda cyfra jest mnożona przez jakąś wagę, a wyniki są sumowane. Oczywistym wzorem w wadze jest oczywiście to, że waga wynosi w = b ^ k, gdzie b jest podstawą liczby, a k jest indeksem cyfry. (Zawsze będę liczył cyfry od prawej strony i zaczynając od indeksu 0 dla najbardziej prawej cyfry. Podobnie, gdy mówię o „pierwszej” cyfrze, mam na myśli skrajną prawą cyfrę.)
Powód dlaczego ciężary dla cyfr śledzić tego wzoru jest to, że największa liczba, które mogą być reprezentowane przez cyfry od 0 do k musi być dokładnie 1 niższy od najniższego numeru, który może być reprezentowany przez tylko za pomocą cyfr k + 1. W systemie dwójkowym 0111 musi być o jeden mniejszy niż 1000. W systemie dziesiętnym 099999 musi być o jeden mniejszy niż 100000.
Kodowanie do zmiennej o podstawie
Odstępy między kolejnymi liczbami wynoszące dokładnie 1 to ważna zasada. Zdając sobie z tego sprawę, możemy przedstawić naszą sekwencję indeksów za pomocą liczby o zmiennej podstawie . Podstawą dla każdej cyfry jest ilość różnych możliwości dla tej cyfry. Dla ułamka dziesiętnego każda cyfra ma 10 możliwości, w naszym systemie cyfra po prawej stronie miałaby 1 możliwość, a cyfra po lewej stronie miałaby n możliwości. Ale ponieważ skrajna prawa cyfra (ostatnia liczba w naszej sekwencji) zawsze wynosi 0, pomijamy ją. Oznacza to, że mamy podstawy od 2 do n. Ogólnie k-ta cyfra będzie miała podstawę b [k] = k + 2. Najwyższą dopuszczalną wartością dla cyfry k jest h [k] = b [k] - 1 = k + 1.
Nasza reguła dotycząca wag w [k] cyfr wymaga, aby suma h [i] * w [i], gdzie i przechodzi od i = 0 do i = k, była równa 1 * w [k + 1]. Podane okresowo, w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). Pierwsza waga w [0] powinna zawsze wynosić 1. Zaczynając od tego punktu mamy następujące wartości:
(Ogólną zależność w [k-1] = k! Można łatwo udowodnić za pomocą indukcji).
Liczba, którą otrzymamy z konwersji naszego ciągu będzie wówczas sumą s [k] * w [k], gdzie k biegnie od 0 do n-1. Tutaj s [k] jest k-tym (najbardziej po prawej, zaczynając od 0) elementem sekwencji. Jako przykład weźmy nasz {1, 2, 0, 1, 0}, z usuniętym skrajnym prawym elementem, jak wspomniano wcześniej: {1, 2, 0, 1} . Nasza suma to 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .
Zwróć uwagę, że jeśli weźmiemy maksymalną pozycję dla każdego indeksu, otrzymamy {4, 3, 2, 1, 0}, a to konwertuje do 119. Ponieważ wagi w naszym kodowaniu liczb zostały wybrane tak, aby nie pomijać wszystkie liczby, wszystkie liczby od 0 do 119 są prawidłowe. Jest ich dokładnie 120, czyli n! dla n = 5 w naszym przykładzie jest to dokładnie liczba różnych permutacji. Możesz więc zobaczyć nasze zakodowane liczby całkowicie określające wszystkie możliwe permutacje.
Dekodowanie na podstawie zmiennej
Dekodowanie jest podobne do konwersji do formatu binarnego lub dziesiętnego. Typowy algorytm jest następujący:
Dla naszego numeru o zmiennej podstawie:
To poprawnie dekoduje nasze 37 z powrotem do {1, 2, 0, 1} (
sequence
byłoby to{1, 0, 2, 1}
w tym przykładzie kodu, ale cokolwiek ... o ile odpowiednio indeksujesz). Musimy tylko dodać 0 na prawym końcu (pamiętaj, że ostatni element ma zawsze tylko jedną możliwość dla swojej nowej pozycji), aby odzyskać naszą pierwotną sekwencję {1, 2, 0, 1, 0}.Permutowanie listy przy użyciu sekwencji indeksów
Poniższy algorytm umożliwia permutowanie listy zgodnie z określoną sekwencją indeksów. Niestety jest to algorytm O (n²).
Wspólna reprezentacja permutacji
Normalnie permutacja nie jest przedstawiana tak nieintuicyjnie, jak to zrobiliśmy, ale po prostu przez bezwzględne położenie każdego elementu po zastosowaniu permutacji. Nasz przykład {1, 2, 0, 1, 0} for
abcde
tocaebd
jest zwykle reprezentowany przez {1, 3, 0, 4, 2}. Każdy indeks od 0 do 4 (lub ogólnie od 0 do n-1) występuje dokładnie raz w tej reprezentacji.Zastosowanie permutacji w tej formie jest łatwe:
Odwrócenie jest bardzo podobne:
Konwersja z naszej reprezentacji do wspólnej reprezentacji
Zauważ, że jeśli weźmiemy nasz algorytm do permutacji listy przy użyciu naszej sekwencji indeksów i zastosujemy ją do permutacji tożsamości {0, 1, 2, ..., n-1}, otrzymamy odwrotna permutacja, przedstawiona we wspólnej formie. ( W naszym przykładzie {2, 0, 4, 1, 3} ).
Aby uzyskać nieodwróconą premutację, stosujemy algorytm permutacji, który właśnie pokazałem:
Lub możesz po prostu zastosować permutację bezpośrednio, używając algorytmu odwrotnej permutacji:
Zauważ, że wszystkie algorytmy radzenia sobie z permutacjami we wspólnej formie to O (n), podczas gdy zastosowanie permutacji w naszej postaci to O (n²). Jeśli chcesz zastosować permutację kilka razy, najpierw przekonwertuj ją na wspólną reprezentację.
źródło
1234
, f (4) = {0, 2, 0, 0} = 1342. I f '(1423) = {0, 1 1, 0} = 3. Ten algorytm jest naprawdę inspirujący. Zastanawiam się, że to oryginalne dzieło z OP. Przez jakiś czas to studiowałem i analizowałem. I myślę, że to prawda :){1, 2, 0, 1, 0}
->{1, 3, 0, 4, 2}
? I wzajemnie? Czy to możliwe? ( nie dokonując konwersji między{1, 2, 0, 1, 0}
<-->{C, A, E, B, D}
, co wymaga O (n ^ 2).) Jeśli „nasz styl” i „wspólny styl” nie dają się zamieniać, to w rzeczywistości są to dwie różne oddzielne rzeczy, prawda? Dzięki xZnalazłem algorytm O (n), oto krótkie wyjaśnienie http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html
źródło
Złożoność można sprowadzić do n * log (n), patrz sekcja 10.1.1 ("Kod Lehmera (tabela inwersji)", str.232ff) książki fxtbook: http://www.jjj.de/fxt/ #fxtbook przejdź do sekcji 10.1.1.1 („Obliczenia z użyciem dużych tablic” s. 235), aby uzyskać informacje na temat szybkiej metody. Kod (na licencji GPL, C ++) znajduje się na tej samej stronie internetowej.
źródło
Problem rozwiązany. Jednak nie jestem pewien, czy po tych latach nadal potrzebujesz rozwiązania. LOL, właśnie dołączyłem do tej witryny, więc ... Sprawdź moją klasę permutacji Java. Możesz oprzeć się na indeksie, aby uzyskać permutację symbolu, lub podać permutację symbolu, a następnie uzyskać indeks.
Oto moja klasa premutacji
a oto moja klasa główna za pokazanie, jak korzystać z tej klasy.
Baw się dobrze. :)
źródło
Każdy element może znajdować się w jednej z siedmiu pozycji. Aby opisać położenie jednego elementu, potrzebne byłyby trzy bity. Oznacza to, że możesz przechowywać pozycje wszystkich elementów w wartości 32-bitowej. To dalekie od wydajności, ponieważ taka reprezentacja pozwoliłaby nawet wszystkim elementom znajdować się w tej samej pozycji, ale uważam, że maskowanie bitów powinno być stosunkowo szybkie.
Jednak przy więcej niż 8 pozycjach potrzebujesz czegoś fajniejszego.
źródło
Tak się składa, że jest to funkcja wbudowana w J :
źródło
Możesz kodować permutacje za pomocą algorytmu rekurencyjnego. Jeśli permutacja N (pewna kolejność liczb {0, .., N-1}) ma postać {x, ...}, zakoduj ją jako x + N * kodowanie (N-1) -permutacja reprezentowana przez „...” na liczbach {0, N-1} - {x}. Brzmi jak kęs, oto kod:
Ten algorytm to O (n ^ 2). Dodatkowe punkty, jeśli ktoś ma algorytm O (n).
źródło
Co za interesujące pytanie!
Jeśli wszystkie elementy są liczbami, możesz rozważyć konwersję ich z łańcuchów na liczby rzeczywiste. Wtedy byłbyś w stanie posortować wszystkie permutacje, porządkując je i umieszczając w tablicy. Potem byłbyś otwarty na dowolny z różnych algorytmów wyszukiwania.
źródło
Moja poprzednia odpowiedź była pochopna (usunięta), ale mam prawdziwą odpowiedź. Dostarcza go podobna koncepcja, czynnikadyczny i jest powiązany z permutacjami (moja odpowiedź dotyczy kombinacji, przepraszam za to zamieszanie). Nienawidzę po prostu publikować linków do Wikipedii, ale napisałem, że napisałem jakiś czas temu, jest z jakiegoś powodu niezrozumiały. W razie potrzeby mogę to później rozwinąć.
źródło
Jest o tym napisana książka. Przepraszam, ale nie pamiętam jego nazwy (prawdopodobnie znajdziesz ją na Wikipedii). ale w każdym razie napisałem implementację w Pythonie tego systemu wyliczania: http://kks.cabal.fi/Kombinaattori Część z nich jest po fińsku, ale po prostu skopiuj zmienne kodu i nazwy ...
źródło
Miałem dokładnie to pytanie i pomyślałem, że przedstawię moje rozwiązanie w Pythonie. To jest O (n ^ 2).
To całkiem proste; po wygenerowaniu czynnikadycznej reprezentacji liczby po prostu wybieram i usuwam znaki z ciągu. Usunięcie z ciągu jest powodem, dla którego jest to rozwiązanie O (n ^ 2).
Rozwiązanie Antoine jest lepsze pod względem wydajności.
źródło
Powiązane pytanie to obliczenie permutacji odwrotnej, permutacji, która przywróci permutowane wektory do pierwotnego porządku, gdy znana jest tylko tablica permutacji. Oto kod O (n) (w PHP):
Oprogramowanie David Spector Springtime
źródło