【问题标题】:Algorithm to generating all permutations of a string with no adjacent characters生成没有相邻字符的字符串的所有排列的算法
【发布时间】:2016-11-12 16:15:08
【问题描述】:

假设我有ABCDEF。然后,有6个!重新排序该字符串的排列。现在,我只想处理没有相邻字符的排列。这意味着,我想查看满足这些约束的所有排列:

  • B 不在 A 或 C 旁边
  • C 不在 B 或 D 旁边
  • D 不在 C 或 E 旁边
  • E 不在 D 或 F 旁边

我对这个算法的处理方法是下面的伪代码:

//generate all 6! permutations
//check all permutations and see where B is next to A || C
    //remove all instances
//check all permutations and see where C is next to D
    //remove all instances
//check all permutations and see where D is next to E
    //remove all instances
//check all permutations and see where E is next to F 
    //remove all instances

但是,这些屏蔽操作变得非常低效并且花费了我很长时间,特别是如果我的字符串长度大于 6。我怎样才能更有效地做到这一点?我看到了这些类似的帖子,12,并希望提取一些可能对我有帮助的关键想法。但是,这也是蛮力检查。我想从一开始就只生成独特的模式,而不是生成所有东西并一一检查。

编辑:目前这是我用来生成所有排列的方法。

static String[] designs;
static int index;
protected static String[] generateDesigns(int lengthOfSequence, int numOfPermutations){
    designs = new String[numOfPermutations];
    StringBuilder str = new StringBuilder("1");
    for(int i = 2; i <= lengthOfSequence; i++)
        str.append(i);

    genDesigns("", str.toString()); //genDesigns(6) = 123456 will be the unique characters
    return designs;
}

//generate all permutations for lenOfSequence characters
protected static void genDesigns(String prefix, String data){
    int n = data.length();
    if (n == 0) designs[index++] = prefix;
    else {
        for (int i = 0; i < n; i++)
            genDesigns(prefix + data.charAt(i), data.substring(0, i) + data.substring(i+1, n));
    }
}

【问题讨论】:

  • 嗯,我们来粗略估计一下。给定 n 个字母的随机烫发,两个特定字母彼此相邻的概率为 2(n-1)/(n(n-1)) = 2/n。由于我们有 n-1 个这些约束,手动独立假设表明,对于大 n,随机烫发有效的概率约为 (1-2/n)^(n-1) ≈ 1/e^2 ≈ 0.135 .如果这是一个合理的估计,那么你就不能指望获得大的收益——有效的烫发太多了(七分之一或八分之一)!
  • 嗯,这里是用于推导有多少这样的排列的实际方程:“随机重排后邻居仍然是邻居的概率”,Amer。数学。月刊 87 (1980), 122-124
  • 相同的公式。很高兴知道有一个正式的推导(但我一点也不惊讶)。

标签: java string algorithm permutation


【解决方案1】:

典型的O(n!)算法伪代码生成长度为n的字符串的所有排列:

function permute(String s, int left, int right)
{
   if (left == right)
     print s
   else
   {
       for (int i = left; i <= right; i++)
       {
          swap(s[left], s[i]);
          permute(s, left + 1, right);
          swap(s[left], s[i]); // backtrack
       }
   }
}

字符串ABC对应的递归树看起来像[图片来自互联网]:

就在交换之前,检查是否可以交换满足给定的约束(检查s[left]s[i] 的新上一个和新下一个字符)。这也将切断递归树的许多分支。

【讨论】:

  • 你能添加一个具体的例子吗?这将有助于使答案更加完整。谢谢! :)
【解决方案2】:

这是一个相当简单的回溯解决方案,在将相邻字符添加到排列之前修剪搜索。

public class PermutationsNoAdjacent {

    private char[] inputChars;
    private boolean[] inputUsed;
    private char[] outputChars;
    private List<String> permutations = new ArrayList<>();

    public PermutationsNoAdjacent(String inputString) {
        inputChars = inputString.toCharArray();
        inputUsed = new boolean[inputString.length()];
        outputChars = new char[inputString.length()];
    }

    private String[] generatePermutations() {
        tryFirst();
        return permutations.toArray(new String[permutations.size()]);
    }

    private void tryFirst() {
        for (int inputIndex = 0; inputIndex < inputChars.length; inputIndex++) {
            assert !inputUsed[inputIndex] : inputIndex;
            outputChars[0] = inputChars[inputIndex];
            inputUsed[inputIndex] = true;
            tryNext(inputIndex, 1);
            inputUsed[inputIndex] = false;
        }
    }

    private void tryNext(int previousInputIndex, int outputIndex) {
        if (outputIndex == outputChars.length) { // done
            permutations.add(new String(outputChars));
        } else {
            // avoid previousInputIndex and adjecent indices
            for (int inputIndex = 0; inputIndex < previousInputIndex - 1; inputIndex++) {
                if (!inputUsed[inputIndex]) {
                    outputChars[outputIndex] = inputChars[inputIndex];
                    inputUsed[inputIndex] = true;
                    tryNext(inputIndex, outputIndex + 1);
                    inputUsed[inputIndex] = false;
                }
            }
            for (int inputIndex = previousInputIndex + 2; inputIndex < inputChars.length; inputIndex++) {
                if (!inputUsed[inputIndex]) {
                    outputChars[outputIndex] = inputChars[inputIndex];
                    inputUsed[inputIndex] = true;
                    tryNext(inputIndex, outputIndex + 1);
                    inputUsed[inputIndex] = false;
                }
            }
        }
    }

    public static void main(String... args) {
        String[] permutations = new PermutationsNoAdjacent("ABCDEF").generatePermutations();
        for (String permutation : permutations) {
            System.out.println(permutation);
        }
    }

}

它打印 90 个 ABCDEF 排列。我只引用开头和结尾:

ACEBDF
ACEBFD
ACFDBE
ADBECF
…
FDBEAC
FDBECA

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多