1

好的,真正的问题稍微复杂一些,因为它使用了我编写的一些类而不是字符串,但可以这样想象:如果你有一个包含 700 个 3 字母单词的列表,我怎样才能找到每个组合可以通过将其中的 5 个单词的首字母和尾字母连接在一起形成,即 CAT TOW WAR RAD DOG 会成功,但 CAT TOW WAR RAG GOD 也会成功,等等。

到目前为止,我有这样的事情:

for(int i = 0; i < list.size(); i++){
    String temp = chainMaker(list.get(i), list, used, temp);
}

public String chainMaker(String toAdd, ArrayList<String> unused,
    ArrayList<String> used, String result){

    if(result.size() >= 15)
        return result;
    else{
        if(result.canAdd(toAdd))
            result.add(toAdd);
        used.add(toAdd);

        return chainMaker(unused.remove(0), unused, used, result);
    }

    return result;
}

但这只会不断增加生产垃圾。我不擅长递归回溯,我认为如果我能让这个例子工作,我最终会理解这个概念。请聪明人帮帮我!

4

4 回答 4

0

由于您正在查看每个组合,因此您将需要查看树和图遍历算法。它们运行良好,允许您使用经过验证的算法。鉴于您有“字母链接”约束,您还需要关注在遍历中使用启发式或规则的遍历算法。我确信还有其他更高级的方法可以做到这一点,但是使用小型数据集树和图表可以工作。

于 2010-07-08T18:38:19.343 回答
0

这是一个 C++ 实现,但我认为逻辑很明显。有什么不明白的就问吧。

void letterChain(string stack[5], int k, const vector<string> &words)
{
    if ( k == 5 ) // have 5 words, print solution
    {
        for ( int i = 0; i < 5; ++i )
            cout << stack[i] << " ";
        cout << endl;

        return;
    }

    for ( int i = 0; i < words.size(); ++i ) // try to put a word at the current level of the stack
    {
        // if words[i] is already used, we cant use it again (if we can, just remove this part)
        bool used = false;
        for ( int j = 0; j < k; ++j )
            if ( stack[j] == words[i] )
                used = true;

        if ( !used ) // remove this if you can use a word multiple times
        {
            if ( k == 0 || words[i][0] == stack[k - 1][2] ) // if the letters match
            {
                stack[k] = words[i]; // add it to the stack
                letterChain(stack, k + 1, words); // recursive call
            }
        }
    }
}

int main()
{
    vector<string> words;
    words.push_back("CAT");
    words.push_back("TOW");
    words.push_back("WAR");
    words.push_back("RAD");
    words.push_back("DOG");
    words.push_back("GOD");
    words.push_back("RAG");
    words.push_back("RAR");

    string stack[5];

    letterChain(stack, 0, words);
}

CAT TOW WAR RAD DOG
CAT TOW WAR RAG GOD
CAT TOW WAR RAR RAD
CAT TOW WAR RAR RAG
TOW WAR RAD Dog God
TOW WAR RAG GOD
TOW WAR RAR RAD Dog
TOW WAR RAR RAG GOD
战争 RAR RAD DOG GOD
战争 RAR RAG GOD

如果您只能使用一个单词在列表中出现的次数,那么只需使用计数向量减去每次使用该单词的计数即可。

于 2010-07-08T18:52:25.080 回答
0

这是我在 Java 中的尝试:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class So3206795 {

  static void step(List<String> result, List<String> unused, String requiredStart) {
    if (result.size() == 3) {
      System.out.println(result);
      return;
    }

    for (int i = 0; i < unused.size(); i++) {
      String s = unused.get(i);

      if (s.startsWith(requiredStart)) {
        unused.remove(i); // will be put back later
        result.add(s);

        step(result, unused, s.substring(s.length() - 1));

        result.remove(result.size() - 1);
        unused.add(i, s);
      }
    }
  }

  public static void main(String[] args) {
    List<String> words = Arrays.asList("CAT", "TOW", "WAR", "RAG", "GOD", "ROR");
    // make a modifiable copy of the words
    List<String> unused = new ArrayList<String>(words);
    step(new ArrayList<String>(), unused, "");
  }
}
于 2010-07-08T19:00:42.920 回答
0

以下内容是基于我对您的帖子的误读。我会留下来娱乐你。真正的解决方案在帖子的末尾。


您确定要从 700 个对象的池中计算 5 个单词的所有可能性吗?

这是我的课程来解决这个问题:

public class SO3206795 {

    private static long ct;
    private static List<String[]> allPossibleWords(final Set<String> words, final String[] chain) {
        final List<String> usedWords = Arrays.asList(chain);
        final int offset = usedWords.lastIndexOf(null);
        List<String[]> wordsList;
        if (offset < 0) {
            wordsList = Collections.singletonList(chain);
            logCreated();
        } else {
            wordsList = new ArrayList<String[]>();
            for (final String word : words) {
                final String[] copy = Arrays.copyOf(chain, chain.length);
                if (!usedWords.contains(word)) {
                    copy[offset] = word;
                    wordsList.addAll(allPossibleWords(words, copy));
                }
            }
        }
        return wordsList;
    }

    private static List<String[]> getChains(final Set<String> words, final int length) {
        final List<String[]> tmpChain = new ArrayList<String[]>();
        final String[] chain = new String[length];
        tmpChain.addAll(allPossibleWords(words, chain));
        return tmpChain;
    }
    private static Set<String> getWords(final int count, final int letters) {
        final Set<String> set = new TreeSet<String>();
        final int[] arr = new int[letters];
        int tempCt = 0;
        while (tempCt++ < count) {
            set.add(nextWord(arr));
            for (int i = letters - 1; i >= 0; i--) {
                if (arr[i] == 25) {
                    arr[i] = 0;
                } else {
                    arr[i] = arr[i] + 1;
                    break;
                }
            }
        }
        return set;
    }

    private static void logCreated(){
        if(++ct%10000==0){
            System.out.println("Created "+ct+" chains");
        }
    }

    public static void main(final String[] args) {
        String[]usedArgs=args;
        if(args.length==1&&args[0].matches(".*\\D.*")){
            usedArgs=args[0].split("\\D+");
        };
        final int[] values = {10,3,5};
        for (int i = 0; i < usedArgs.length&&i<values.length; i++) {
            values[i] = Integer.parseInt( usedArgs[i]);
        }
        final SO3206795 thing = new SO3206795(values[0],values[1],values[2]);
        for (final String[] chain : thing.chains) {
            System.out.println(Arrays.toString(chain));
        }
    }

    private static String nextWord(final int[] arr) {
        final char[] ch = new char[arr.length];
        for (int i = 0; i < arr.length; i++) {
            final int j = arr[i];
            ch[i] = (char) (65 + j);
        }
        return new String(ch);
    }

    private static void printInfo(final int numberOfWords, final int wordLength, final int chainLength) {
        BigInteger words = BigInteger.valueOf(numberOfWords);
        for(int i = 1; i < chainLength; i++){
            words=words.multiply(BigInteger.valueOf(numberOfWords-i));
        }

        System.out.println(MessageFormat.format(
            "Creating {0} words of length {1}.\nCreating all possible chains of {2} words.\nThat''s {3} chains in total",
            numberOfWords, wordLength, chainLength, words.toString()));
    }

    Set<String>     words;

    List<String[]>  chains;
    public SO3206795(final int numberOfWords, final int wordLength, final int chainLength) {

        printInfo(numberOfWords,wordLength, chainLength);
        this.words = getWords(numberOfWords, wordLength);
        this.chains = getChains(this.words, chainLength);
    }

}

它有一个 main 方法,您可以使用最多三个参数调用它:

  • numberOfWords(将生成的单词数,默认:10)
  • wordLength(字长,默认:3)
  • chainLength(字链长度,默认:5)

但是,当我使用值 700、3、5 启动它时,调试输出是这样的:

Creating 700 words of length 3.
Creating all possible chains of 5 words.
That's 165680980516800 chains in total

这是相当多的,你不会这么说吗?这就是 700 * 699 * 698 * 697 * 696 的总和。现在,如果您使用自己的对象而不是字符串,假设您的对象每个大小只有 3 个字节,这意味着您将消耗

497042941550400 Bytes  or
485393497607 KB        or
474017087 MB           or
462907 GB              or
452 TB                 

我不了解您,但是恐怕这比我的机器的 RAM 多得多,而且不幸的是,您的对象不太可能每个只有 3 个字节(并且创建的列表和数组也需要一些重要的内存)。所以我不认为计算所有可能性是一个好主意,即使编码很有趣。

而且也需要很长时间。在我的机器上,创建 10000 个链大约需要 16 毫秒。因此,对于 165680980516800 条链,总持续时间为

2650895688268 ms      or
2650895688 sec        or
44181594 min          or
736359 hours          or
30681 days            or
84 years

这可能不会太长,因为Deep Thought 花了 7.5 万年才得出答案 42,但它可能仍然比你愿意等待的时间更长。

请:我的数学很糟糕,如果有人向我解释为什么这都是胡说八道,我会非常高兴,但我担心我的数字计算正确。


误会结束:

该死,我错过了字母链接约束。这是我更新的解决方案。将上面的 allPossibleWords 方法替换为这两种方法:

private static List<String[]> allPossibleWords(final Set<String> words, final String[] chain) {
    final List<String> usedWords = Arrays.asList(chain);
    final int offset = usedWords.lastIndexOf(null);
    List<String[]> wordsList;
    if (offset < 0) {
        wordsList = Collections.singletonList(chain);
        logCreated();
    } else {
        wordsList = new ArrayList<String[]>();
        for (final String word : words) {
            if (!usedWords.contains(word)&&(offset==chain.length-1||isLegalNeighbor(word,usedWords.get(offset+1)))) {
                final String[] copy = Arrays.copyOf(chain, chain.length);
                copy[offset] = word;
                wordsList.addAll(allPossibleWords(words, copy));
            }
        }
    }
    return wordsList;
}
private static boolean isLegalNeighbor(final String left, final String right) {
    return left.charAt(left.length()-1)==right.charAt(0);
}

此外,我们将用更随机的版本替换 getWords

private static Set<String> getWords(final int numberOfWords, final int wordLength) {
    final Set<String> set=new TreeSet<String>();
    final Random r = new Random();
    while(set.size()<numberOfWords){
        final char[] ch = new char[wordLength];
        for (int i = 0; i < ch.length; i++) {
            ch[i]=(char) (65+r.nextInt(26));
        }
        set.add(new String(ch));
    }
    return set;
}

现在我实际上得到了 200 个单词的合理执行时间,但是 700 个单词仍然会在看起来永远存在之后创建 OutOfMemoryError。

无论如何,这是pastebin 的完整解决方案

这是修正后的数学:

大约有 362559479 种可能的组合

700 * (699/26) * (698/26) * (697/26) * (696/26)

给定一个 3 字节的对象大小,这意味着内存消耗

1087678437 Bytes  or
1062185 KB        or
1037 MB           or
1 GB

在我的机器上,创建 10000 个带有字母链接的链大约需要 500 毫秒。因此,对于 362559479 条链,总持续时间为

181279739 ms      or
181279 sec        or
3021 min          or
50 hours          or
2 days            

这些仍然是令人印象深刻的数字,我想说

于 2010-07-09T00:25:53.117 回答