создание вариаций без повторений / перестановок в java

Я должен генерировать все вариации без повторений, сделанных из цифр 0-9.

длина их может быть от 1 до 10. Я действительно не знаю, как его решить, особенно как избежать повторений.

пример: длина вариаций: 4 случайные изменения: 9856, 8753, 1243, 1234 etc. (но не 9985 - содержит повторение)

Я был бы очень благодарен, если кто-то может помочь мне с этой проблемой, особенно давая некоторый код и подсказки.

9 ответов


ключевое слово для поиска -перестановка. Существует обилие исходного кода, свободно доступного, который их выполняет.

Что касается того, чтобы сохранить его повторение свободным, я предлагаю простой рекурсивный подход: для каждой цифры у вас есть выбор принять его в свой вариант или нет, поэтому ваша рекурсия подсчитывает через цифры и разветвляется на два рекурсивных вызова, один, в котором цифра включена, один, в котором она исключена. Затем, после того, как вы достигли последней цифры, каждый рекурсия по существу дает вам (уникальный, отсортированный) список цифр, свободных от повторения. Затем вы можете создать все возможные перестановки этого списка и объединить все эти перестановки для достижения конечного результата.

(такой же, как duffymo сказал: Я не могу предоставить код для этого)

дополнительно Примечание: рекурсия основана на 0/1 (исключение, включение), который может быть непосредственно переведен в биты, следовательно, целые числа. Поэтому, чтобы получить все возможные комбинации цифр без фактически, выполняя рекурсию, вы можете просто использовать все 10-битные целые числа и перебирать их. Затем интерпретировать цифры таковы, что установленный бит соответствует, в том цифре в списке, что нужно переставляться.


вот мой Java-код. Не стесняйтесь спрашивать, если не понимаете. Главное здесь:

  1. вроде снова символьный массив. например: a1 a2 a3 b1 b2 b3 .... (a1 = a2 = a3)
  2. генерировать перестановку и всегда сохранять условие: индекс a1
import java.util.Arrays;

public class PermutationDup {

    public void permutation(String s) {
        char[] original = s.toCharArray();
        Arrays.sort(original);
        char[] clone = new char[s.length()];
        boolean[] mark = new boolean[s.length()];
        Arrays.fill(mark, false);
        permute(original, clone, mark, 0, s.length());
    }

    private void permute(char[] original, char[] clone, boolean[] mark, int length, int n) {
        if (length == n) {
            System.out.println(clone);
            return;
        }

        for (int i = 0; i < n; i++) {
            if (mark[i] == true) continue;
            // dont use this state. to keep order of duplicate character
            if (i > 0 && original[i] == original[i-1] && mark[i-1] == false) continue;
            mark[i] = true;
            clone[length] = original[i];
            permute(original, clone, mark, length+1, n);
            mark[i] = false;
        }

    }

    public static void main(String[] args) {
        PermutationDup p = new PermutationDup();
        p.permutation("abcab");
    }
}

Я создал следующий код для генерации перестановок, где порядок важен и без повторения. Он использует дженерики для перестановки любого типа объекта:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Permutations {

    public static <T> Collection<List<T>> generatePermutationsNoRepetition(Set<T> availableNumbers) {
        Collection<List<T>> permutations = new HashSet<>();

        for (T number : availableNumbers) {
            Set<T> numbers = new HashSet<>(availableNumbers);
            numbers.remove(number);

            if (!numbers.isEmpty()) {
                Collection<List<T>> childPermutations = generatePermutationsNoRepetition(numbers);
                for (List<T> childPermutation : childPermutations) {
                    List<T> permutation = new ArrayList<>();
                    permutation.add(number);
                    permutation.addAll(childPermutation);
                    permutations.add(permutation);
                }
            } else {
                List<T> permutation = new ArrayList<>();
                permutation.add(number);
                permutations.add(permutation);
            }
        }

        return permutations;
    }
}

представьте, что у вас была магическая функция - учитывая массив цифр, он вернет вам правильные перестановки.

как вы можете использовать эту функцию для создания нового списка перестановок только с одной дополнительной цифрой?

например,

если бы я дал вам функцию под названием permute_three(char[3] digits), и я говорю вам, что он работает только для цифр 0, 1, 2 как можно написать функцию, которая может переставлять 0, 1, 2, 3, используя данный ?

...

как только вы решили это, что вы заметили? можете ли вы обобщить?


С помощью доллар все просто:

@Test
public void generatePermutations() {
    // digits is the string "0123456789"
    String digits = $('0', '9').join();

    // then generate 10 permutations
    for (int i : $(10)) {
        // shuffle, the cut (0, 4) in order to get a 4-char permutation
        System.out.println($(digits).shuffle().slice(4));
    }
}

код для этого похож на тот, который без дубликатов, с добавлением оператора if-else.Проверьте это код

В приведенном выше коде,измените цикл for следующим образом

for (j = i; j <= n; j++)
{

if(a[i]!=a[j] && !is_duplicate(a,i,j))              
    {
        swap((a+i), (a+j));
        permute(a, i+1, n);
        swap((a+i), (a+j)); 
    }
    else if(i!=j)  {}  // if no duplicate is present , do nothing           
    else permute(a,i+1,n);  // skip the ith character
}

bool is_duplicate(int *a,int i,int j) 
{
     if a[i] is present between a[j]...a[i] 
        return 1;
    otherwise
        return 0;

}

работал для меня


перестановка без повторения основана на теореме, что количество результатов является факториалом количества элементов (в данном случае чисел). В вашем случае 10! это 10*9*8*7*6*5*4*3*2*1 = 3628800. Доказательство того, почему это точно правильно, также является правильным решением для поколения. Ну и как. На первой позиции, т. е. слева, вы можете иметь 10 чисел, на второй позиции вы можете иметь только 9 чисел, потому что одно число находится на позиции слева, и мы не можем повторить то же самое число и т. д. (доказательство сделано математической индукцией). Итак, как получить первые десять результатов? По моим сведениям, самый простой способ - использовать циклический сдвиг. Это означает порядок сдвига номера влево на одну позицию (или вправо, если хотите) и номер, который переполнение поставить на пустое место. Это означает для первых десяти результатов:

10 9 8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2 1 10
8 7 6 5 4 3 2 1 10 9
7 6 5 4 3 2 1 10 9 8
6 5 4 3 2 1 10 9 8 7
5 4 3 2 1 10 9 8 7 6
...

первая строка является базовым образцом, поэтому хорошей идеей поместить его в набор перед поколением. Преимущество в том, что на следующем этапе вам придется решить ту же проблему, чтобы избежать нежелательных лжи.

на следующем шаге рекурсивно поверните только 10-1 чисел 10-1 раз и т. д. Это означает, что для первых 9 результатов на втором шаге:

10 9 8 7 6 5 4 3 2 1
10 8 7 6 5 4 3 2 1 9
10 7 6 5 4 3 2 1 9 8
10 6 5 4 3 2 1 9 8 7
10 5 4 3 2 1 9 8 7 6
...

etc, обратите внимание, что первая строка присутствует с предыдущего шага, поэтому ее нельзя добавлять в сгенерированный набор снова.

рекурсивно делает именно то, что описано выше. Возможно произвести все 3628800 комбинаций для 10!, потому что количество вложенности совпадает с количеством элементов в массиве (это означает, что в вашем случае для 10 чисел он задерживается около 5min. на мой компьютер) и вы должны иметь достаточно памяти, если вы хотите сохранить все комбинации в массив.

есть решение.

package permutation;

/** Class for generation amount of combinations (factorial)
 * !!! this is generate proper permutations without repeating and proper amount (počet) of rows !!!
 *
 * @author hariprasad
 */
public class TestForPermutationII {
  private static final String BUMPER = "*";
  private static int counter = 0;
  private static int sumsum = 0;
  // definitoin of array for generation
  //int[] testsimple = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  int[] testsimple = {1, 2, 3, 4, 5};
  private int ELEMNUM = testsimple.length;
  int[][] shuff;

  private String gaps(int len) {
    String addGap = "";
    for(int i=0; i <len; i++)
      addGap += "  ";
    return addGap;
  }

  /** Factorial computing */
  private int fact(int num) {
    if (num > 1) {
      return num * fact(num - 1);
    } else {
      return 1;
    }
  }

  /** Cyclic shift position to the left */  
  private int[] lShiftPos(int[] arr, int pos) {
    int[] work = new int[ELEMNUM];
    int offset = -1;
    for (int jj = 0; jj < arr.length; jj++) {
      if (jj < pos) {
        work[jj] = arr[jj];
      } else if (jj <= arr.length - 1) {
        if (jj == pos) {
          offset = arr[pos]; // last element
        }
        if (jj != (arr.length - 1)) {
          work[jj] = arr[jj + 1];
        } else {
          work[jj] = offset;
        }
      }
    }
    return work;
  }

  private String printBuff(int[] buffer) {
    String res = "";
    for (int i= 0; i < buffer.length; i++) {
      if (i == 0) 
        res += buffer[i];
      else
        res += ", " + buffer[i];
    }
    return res;
  };

  /** Recursive generator for arbitrary length of array */
  private String permutationGenerator(int pos, int level) {
    String ret = BUMPER;
    int templen = counter;
    int[] work = new int[ELEMNUM];
    int locsumread = 0;
    int locsumnew = 0;
    //System.out.println("\nCalled level: " + level);

    for (int i = 0; i <= templen; i++) {
      work = shuff[i];
      sumsum++;
      locsumread++;
      for (int ii = 0; ii < pos; ii++) {
        counter++;
        sumsum++;
        locsumnew++;
        work = lShiftPos(work, level); // deep copy
        shuff[counter] = work;
      }
    }

    System.out.println("locsumread, locsumnew: " + locsumread + ", " + locsumnew);
    // if level == ELEMNUM-2, it means no another shift
    if (level < ELEMNUM-2) {
      ret = permutationGenerator(pos-1, level+1);
      ret = "Level " + level + " end.";
      //System.out.println(ret);
    }
    return ret;
  }

  public static void main(String[] argv) {
    TestForPermutationII test = new TestForPermutationII();
    counter = 0;
    int len = test.testsimple.length;
    int[] work = new int[len];

    test.shuff = new int[test.fact(len)][];

    //initial
    test.shuff[counter] = test.testsimple;
    work = test.testsimple; // shalow copy

    test.shuff = new int[test.fact(len)][];
    counter = 0;
    test.shuff[counter] = test.testsimple;
    test.permutationGenerator(len-1, 0);

    for (int i = 0; i <= counter; i++) {
      System.out.println(test.printBuff(test.shuff[i]));
    }

    System.out.println("Counter, cycles: " + counter + ", " + sumsum);
  }
}

интенсивность (количество циклов) алгоритма-сумма неполных факториалов числа членов. Таким образом, существует навес, когда частичное множество снова считывается для создания следующего подмножества, поэтому интенсивность:

n! + n!/2! + n!/3! + ... + n!(n-2)! + n!(n-1)!


есть одно решение, которое не от меня, но оно очень приятное и сложное.

    package permutations;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author Vladimir Hajek
 *
 */
public class PermutationSimple {
    private static final int MAX_NUMBER = 3;

    Set<String> results = new HashSet<>(0);

    /**
     * 
     */
    public PermutationSimple() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @param availableNumbers
     * @return
     */
    public static List<String> generatePermutations(Set<Integer> availableNumbers) {
        List<String> permutations = new LinkedList<>();

        for (Integer number : availableNumbers) {
            Set<Integer> numbers = new HashSet<>(availableNumbers);
            numbers.remove(number);

            if (!numbers.isEmpty()) {
                List<String> childPermutations = generatePermutations(numbers);
                for (String childPermutation : childPermutations) {
                    String permutation = number + childPermutation;
                    permutations.add(permutation);
                }
            } else {
                permutations.add(number.toString());
            }
        }

        return permutations;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Set<Integer> availableNumbers = new HashSet<>(0);

        for (int i = 1; i <= MAX_NUMBER; i++) {
            availableNumbers.add(i);
        }

        List<String> permutations = generatePermutations(availableNumbers);
        for (String permutation : permutations) {
            System.out.println(permutation);
        }

    }
}

Я думаю, это отличное решение.


краткое полезное знание индексации перестановок

создайте метод, который генерирует правильную перестановку, учитывая значение индекса между {0 и N! -1} для "нулевого индексирования" или {1 и N!} для "одного индексированного".

создайте второй метод, содержащий "цикл for", где нижняя граница равна 1, а верхняя-N!. например.. "ибо (i; i