Hoán vị nhanh -> số -> thuật toán ánh xạ hoán vị


113

Tôi có n phần tử. Để làm ví dụ, giả sử 7 phần tử, 1234567. Tôi biết có 7! = 5040 hoán vị có thể có của 7 phần tử này.

Tôi muốn một thuật toán nhanh bao gồm hai hàm:

f (number) ánh xạ một số từ 0 đến 5039 thành một hoán vị duy nhất, và

f '(hoán vị) ánh xạ hoán vị trở lại số mà nó được tạo ra từ đó.

Tôi không quan tâm đến sự tương ứng giữa số và hoán vị, cung cấp mỗi hoán vị có số duy nhất của riêng nó.

Vì vậy, chẳng hạn, tôi có thể có các chức năng trong đó

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

Thuật toán nhanh nhất mà bạn nghĩ đến là liệt kê tất cả các hoán vị và tạo một bảng tra cứu theo cả hai hướng, do đó, khi các bảng được tạo ra, f (0) sẽ là O (1) và f ('1234567') sẽ là a tra cứu trên một chuỗi. Tuy nhiên, đây là bộ nhớ đói, đặc biệt khi n trở nên lớn.

Bất cứ ai có thể đề xuất một thuật toán khác sẽ hoạt động nhanh chóng và không có nhược điểm về bộ nhớ?


Mặc dù thuật toán dưới đây rất toàn diện, nhưng bạn chỉ ra một cách chính xác rằng thuật toán nhanh nhất là một bảng tra cứu. Bạn thực sự không nói về bộ nhớ 'nhiều như vậy', mặc dù tất nhiên nó phụ thuộc vào hệ thống và nền tảng của bạn. Nhưng nếu một bảng tra cứu sẽ là đủ, và nếu đây là một ứng dụng trong thế giới thực, hãy sử dụng nó. Nhanh chóng và đơn giản!
Kirk Broadhurst

14
Bạn nói vậy, nhưng n không cần phải quá lớn để nó ngớ ngẩn. Đối với 12 phần tử, 12! là 479.001.600 hoán vị. Đó là một bảng tra cứu lớn!
ijw

Đừng nhầm lẫn bởi các bài viết khác nhau sử dụng n với nghĩa khác nhau. Một số n tượng trưng cho độ dài chuỗi, một số n tượng trưng cho số lượng các hoán vị có thể xảy ra. Đừng mù quáng so sánh quan niệm O lớn. - Hãy cảnh báo những người đến muộn - -
把 友情 留 在 无 盐

Câu trả lời:


157

Để mô tả một hoán vị của n phần tử, bạn thấy rằng đối với vị trí mà phần tử đầu tiên kết thúc, bạn có n khả năng, vì vậy bạn có thể mô tả điều này với một số từ 0 đến n-1. Đối với vị trí mà phần tử tiếp theo kết thúc, bạn có n-1 khả năng còn lại, vì vậy bạn có thể mô tả điều này bằng một số từ 0 đến n-2.
Vân vân cho đến khi bạn có n số.

Ví dụ cho n = 5, hãy xem xét hoán vị mang abcdeđến caebd.

  • a, phần tử đầu tiên, kết thúc ở vị trí thứ hai, vì vậy chúng tôi gán nó chỉ mục 1 .
  • bkết thúc ở vị trí thứ tư, sẽ là chỉ số 3, nhưng nó là chỉ số thứ ba còn lại, vì vậy chúng tôi gán nó là 2 .
  • ckết thúc ở vị trí còn lại đầu tiên, luôn là 0 .
  • dkết thúc ở vị trí cuối cùng còn lại, mà (chỉ trong số hai vị trí còn lại) là 1 .
  • ekết thúc ở vị trí duy nhất còn lại, được lập chỉ mục là 0 .

Vì vậy, chúng ta có chuỗi chỉ mục {1, 2, 0, 1, 0} .

Bây giờ bạn biết rằng ví dụ trong một số nhị phân, 'xyz' có nghĩa là z + 2y + 4x. Đối với một số thập phân,
đó là z + 10y + 100x. Mỗi chữ số được nhân với một số trọng lượng và kết quả được tính tổng. Mô hình rõ ràng trong trọng số tất nhiên là trọng số là w = b ^ k, với b là cơ số của số và k là chỉ số của chữ số. (Tôi sẽ luôn đếm các chữ số từ bên phải và bắt đầu từ chỉ số 0 cho chữ số tận cùng bên phải. Tương tự như vậy khi tôi nói về chữ số 'đầu tiên', tôi muốn nói ở ngoài cùng bên phải.)

Các lý do tại sao các trọng cho các số theo mô hình này là số lượng cao nhất có thể được đại diện bởi các chữ số từ 0 đến k phải chính xác 1 thấp hơn so với số lượng thấp nhất có thể được đại diện bởi chỉ sử dụng chữ số k + 1. Trong hệ nhị phân, 0111 phải thấp hơn một dưới 1000. Trong hệ thập phân, 099999 phải thấp hơn 100000.

Mã hóa thành cơ số biến
Khoảng cách giữa các số tiếp theo chính xác là 1 là quy tắc quan trọng. Nhận ra điều này, chúng ta có thể biểu diễn chuỗi chỉ mục của mình bằng một số cơ sở biến đổi . Cơ số cho mỗi chữ số là số khả năng khác nhau của chữ số đó. Đối với số thập phân, mỗi chữ số có 10 khả năng, đối với hệ thống của chúng tôi, chữ số tận cùng bên phải sẽ có 1 khả năng và ngoài cùng bên trái sẽ có n khả năng. Nhưng vì chữ số tận cùng bên phải (số cuối cùng trong dãy số của chúng ta) luôn là 0 nên chúng ta bỏ nó đi. Điều đó có nghĩa là chúng ta còn lại với các cơ số 2 đến n. Nói chung, chữ số thứ k sẽ có cơ số b [k] = k + 2. Giá trị cao nhất cho phép của chữ số k là h [k] = b [k] - 1 = k + 1.

Quy tắc của chúng tôi về trọng số w [k] của các chữ số yêu cầu rằng tổng của h [i] * w [i], trong đó tôi đi từ i = 0 đến i = k, bằng 1 * w [k + 1]. Được phát biểu một cách lặp lại, w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). Trọng số đầu tiên w [0] phải luôn là 1. Bắt đầu từ đó, chúng ta có các giá trị sau:

k    h[k] w[k]    

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

(Quan hệ tổng quát w [k-1] = k! Dễ dàng được chứng minh bằng quy nạp.)

Số chúng ta nhận được từ việc chuyển đổi chuỗi của chúng ta sau đó sẽ là tổng của s [k] * w [k], với k chạy từ 0 đến n-1. Ở đây s [k] là phần tử thứ k (ngoài cùng bên phải, bắt đầu từ 0) của dãy. Ví dụ: lấy {1, 2, 0, 1, 0} của chúng tôi, với phần tử ngoài cùng bên phải bị loại bỏ như đã đề cập trước đây: {1, 2, 0, 1} . Tổng của chúng ta là 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .

Lưu ý rằng nếu chúng tôi chiếm vị trí tối đa cho mọi chỉ mục, chúng tôi sẽ có {4, 3, 2, 1, 0} và chuyển đổi thành 119. Vì trọng số trong mã hóa số của chúng tôi đã được chọn nên chúng tôi không bỏ qua bất kỳ số nào, tất cả các số từ 0 đến 119 đều hợp lệ. Có chính xác 120 trong số này, là n! với n = 5 trong ví dụ của chúng ta, chính xác là số các hoán vị khác nhau. Vì vậy, bạn có thể thấy các số được mã hóa của chúng tôi hoàn toàn chỉ định tất cả các hoán vị có thể có.

Giải mã từ cơ sở biến
Giải mã tương tự như chuyển đổi sang nhị phân hoặc thập phân. Thuật toán phổ biến là:

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

Đối với số cơ sở biến đổi của chúng tôi:

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
}

Điều này giải mã chính xác 37 của chúng tôi trở lại {1, 2, 0, 1} ( sequencesẽ là {1, 0, 2, 1}trong ví dụ mã này, nhưng bất cứ điều gì ... miễn là bạn lập chỉ mục phù hợp). Chúng ta chỉ cần thêm 0 vào cuối bên phải (hãy nhớ rằng phần tử cuối cùng luôn chỉ có một khả năng cho vị trí mới của nó) để lấy lại dãy ban đầu {1, 2, 0, 1, 0}.

Hoán vị danh sách bằng chuỗi chỉ mục
Bạn có thể sử dụng thuật toán dưới đây để hoán vị danh sách theo một chuỗi chỉ mục cụ thể. Thật không may, đó là một thuật toán 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;
}

Biểu diễn phổ biến của hoán vị
Thông thường bạn sẽ không biểu diễn hoán vị một cách không trực quan như chúng ta đã làm, mà chỉ đơn giản bằng vị trí tuyệt đối của mỗi phần tử sau khi hoán vị được áp dụng. Ví dụ của chúng tôi {1, 2, 0, 1, 0} cho abcdeto caebdthường được biểu thị bằng {1, 3, 0, 4, 2}. Mỗi chỉ mục từ 0 đến 4 (hoặc nói chung, 0 đến n-1) xảy ra đúng một lần trong biểu diễn này.

Áp dụng một hoán vị ở dạng này rất dễ dàng:

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

Đảo ngược nó rất giống nhau:

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

Chuyển đổi từ biểu diễn của chúng ta sang biểu diễn thông thường
Lưu ý rằng nếu chúng ta sử dụng thuật toán để hoán vị danh sách bằng cách sử dụng chuỗi chỉ mục và áp dụng nó cho hoán vị danh tính {0, 1, 2, ..., n-1}, chúng ta nhận được hoán vị nghịch đảo , biểu diễn ở dạng chung. ( {2, 0, 4, 1, 3} trong ví dụ của chúng tôi).

Để có được tiền đặt trước không đảo ngược, chúng tôi áp dụng thuật toán hoán vị mà tôi vừa trình bày:

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

Hoặc bạn có thể chỉ áp dụng hoán vị trực tiếp bằng cách sử dụng thuật toán hoán vị nghịch đảo:

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

Lưu ý rằng tất cả các thuật toán để xử lý các hoán vị ở dạng phổ biến là O (n), trong khi áp dụng một hoán vị ở dạng của chúng ta là O (n²). Nếu bạn cần áp dụng một hoán vị nhiều lần, trước tiên hãy chuyển nó thành biểu diễn chung.


6
Trong "Hoán vị danh sách bằng cách sử dụng chuỗi chỉ mục", bạn đề cập đến thuật toán bậc hai. Điều này chắc chắn là tốt vì n có thể sẽ rất nhỏ. Tuy nhiên, điều này có thể "dễ dàng" được giảm xuống thành O (nlogn), thông qua cây thống kê đơn hàng ( pine.cs.yale.edu/pinewiki/OrderStatisticsTree ), tức là cây màu đỏ-đen ban đầu sẽ chứa các giá trị 0, 1, 2 , ..., n-1, và mỗi nút chứa số lượng nút con bên dưới nó. Với điều này, người ta có thể tìm / loại bỏ phần tử thứ k trong thời gian O (logn).
Dimitris Andreou

11
Chúng được gọi là mã lehmer. Liên kết này cũng giải thích rõ về chúng, keithschwarz.com/interesting/code/?dir=factoradic-permutation
mihirg 30/12/12

Thuật toán này thật tuyệt vời, nhưng tôi chỉ thấy một số trường hợp sai. Lấy chuỗi "123"; hoán vị thứ 4 nên là 231, nhưng theo thuật toán này, nó sẽ là 312. nói 1234, hoán vị thứ 4 nên là 1342, nhưng nó sẽ bị nhầm thành "1423". Sửa cho tôi nếu tôi quan sát sai. Cảm ơn.
Isaac Li

@IsaacLi, nếu tôi đúng, f (4) = {2, 0, 0} = 231. Và f '(312) = {1, 1, 0} = 3. Vì 1234, f (4) = {0, 2, 0, 0} = 1342. Và f '(1423) = {0, 1 1, 0} = 3. Thuật toán này thực sự truyền cảm hứng. Tôi tự hỏi nó là tác phẩm gốc từ OP. tôi đã nghiên cứu và phân tích nó trong một thời gian. Và tôi tin rằng nó là đúng :)
Midnite

Làm thế nào để chuyển đổi từ "đại diện của chúng tôi" thành "đại diện chung", {1, 2, 0, 1, 0}-> {1, 3, 0, 4, 2}? Và ngược lại? Nó có khả thi không? (bằng cách không chuyển đổi giữa {1, 2, 0, 1, 0}<--> {C, A, E, B, D}, cần O (n ^ 2).) Nếu "phong cách của chúng tôi" và "phong cách chung" không thể chuyển đổi, chúng thực tế là hai thứ riêng biệt khác nhau, phải không? Cảm ơn x
Midnite

18

Tôi đã tìm thấy một thuật toán O (n), đây là lời giải thích ngắn gọn 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
Nếu tôi hiểu rất rõ thuật toán của bạn. Bạn đang tìm tất cả các khả năng được mã hóa (Trong trường hợp này, nó phải là n! Khả năng). Sau đó, bạn lập bản đồ các số dựa trên mục được mã hóa.
dùng3378649

Tôi đã thêm một lời giải thích ngắn trên blog của mình.
Antoine Comeau

1
Điều này là đặc biệt gọn gàng. Hôm nay tôi đã tự mình nghĩ ra cùng một phương pháp, nhưng tôi nhớ rằng bạn có thể bỏ qua hai phép gán ngược lại.
fuz

Đừng so sánh một cách mù quáng khái niệm O lớn, vì n trong câu trả lời này không giống với một số câu trả lời khác - như @ user3378649 đã chỉ ra - biểu thị độ phức tạp theo tỷ lệ giai thừa của độ dài chuỗi. Câu trả lời này thực sự kém hiệu quả hơn.
把 友情 留 在 无 盐

Điều này có thể được điều chỉnh cho thứ tự từ vựng không?
Gregory Morse

7

Độ phức tạp có thể được giảm xuống n * log (n), xem phần 10.1.1 ("Mã Lehmer (bảng đảo ngược)", tr.232ff) của fxtbook: http://www.jjj.de/fxt/ #fxtbook bỏ qua phần 10.1.1.1 ("Tính toán với mảng lớn" tr.235) để biết phương pháp nhanh. Mã (GPLed, C ++) nằm trên cùng một trang web.


5

Vấn đề đã được giải quyết. Tuy nhiên, tôi không chắc bạn vẫn cần giải pháp sau những năm này. LOL - Liên minh huyền thoại, tôi mới tham gia trang web này, vì vậy ... Hãy kiểm tra Lớp hoán vị Java của tôi. Bạn có thể căn cứ vào một chỉ số để có một hoán vị ký hiệu, hoặc đưa ra một hoán vị ký hiệu sau đó lấy chỉ mục.

Đây là lớp dự bị của tôi

/**
 ****************************************************************************************************************
 * 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

và đây là Lớp chính của tôi để hiển thị cách sử dụng lớp.

/*
 * 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

Chúc vui vẻ. :)


4

Mỗi phần tử có thể ở một trong bảy vị trí. Để mô tả vị trí của một phần tử, bạn sẽ cần ba bit. Điều đó có nghĩa là bạn có thể lưu trữ vị trí của tất cả các phần tử trong một giá trị 32 bit. Điều đó còn lâu mới hiệu quả, vì cách biểu diễn này thậm chí sẽ cho phép tất cả các phần tử ở cùng một vị trí, nhưng tôi tin rằng việc che dấu bit sẽ nhanh hợp lý.

Tuy nhiên, với hơn 8 vị trí, bạn sẽ cần một thứ gì đó tiện lợi hơn.


Điều này giả định rằng OP không quan tâm nếu việc liệt kê thực sự đi từ 0 đến 5039, phải không? Nếu điều đó ổn thì đây có vẻ là một giải pháp tuyệt vời.
Troubadour

4

Đây là một hàm tích hợp sẵn trong 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

Bạn có thể mã hóa hoán vị bằng thuật toán đệ quy. Nếu một hoán vị N (một số thứ tự của các số {0, .., N-1}) có dạng {x, ...} thì mã hóa nó thành x + N * mã hóa của (N-1) -permutation được biểu thị bằng "..." trên các số {0, N-1} - {x}. Nghe có vẻ hấp dẫn, đây là một số mã:

// 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]++;
  }
}

Thuật toán này là O (n ^ 2). Thưởng điểm nếu ai có thuật toán O (n).


1

Thật là một câu hỏi thú vị!

Nếu tất cả các phần tử của bạn là số, bạn có thể muốn xem xét chuyển đổi chúng từ chuỗi thành số thực. Sau đó, bạn sẽ có thể sắp xếp tất cả các hoán vị bằng cách sắp xếp chúng theo thứ tự và đặt chúng vào một mảng. Sau đó, bạn sẽ được mở cho bất kỳ thuật toán tìm kiếm nào ngoài kia.


1

Tôi đã vội vàng trong câu trả lời trước đây của mình (đã bị xóa), mặc dù vậy tôi vẫn có câu trả lời thực sự. Nó được cung cấp bởi một khái niệm tương tự, thừa số và có liên quan đến hoán vị (câu trả lời của tôi liên quan đến kết hợp, tôi xin lỗi vì sự nhầm lẫn đó). Tôi ghét chỉ đăng các liên kết wikipedia, nhưng tôi viết lại tôi đã làm cách đây một thời gian là không thể hiểu được vì một số lý do. Vì vậy, tôi có thể mở rộng về điều này sau nếu được yêu cầu.


1

Có một cuốn sách viết về điều này. Xin lỗi, nhưng tôi không nhớ tên của nó (bạn sẽ tìm thấy nó khá có thể từ wikipedia). nhưng dù sao thì tôi cũng đã viết một bản triển khai python của hệ thống liệt kê đó: http://kks.cabal.fi/Kombinaattori Một số bằng tiếng Phần Lan, nhưng chỉ cần sao chép mã và biến tên ...


0

Tôi đã có câu hỏi chính xác này và nghĩ rằng tôi sẽ cung cấp giải pháp Python của mình. Đó là 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

Nó khá thẳng về phía trước; sau khi tạo biểu diễn theo nhân tử của số, tôi chỉ cần chọn và xóa các ký tự khỏi chuỗi. Việc xóa khỏi chuỗi là lý do tại sao đây là giải pháp O (n ^ 2).

Giải pháp của Antoine tốt hơn cho hiệu suất.


-1

Một câu hỏi liên quan là tính toán hoán vị nghịch đảo, một hoán vị sẽ khôi phục các vectơ đã hoán vị về thứ tự ban đầu khi chỉ có mảng hoán vị được biết. Đây là mã O (n) (trong 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

Phần mềm David Spector Springtime

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.