递归回溯问题

时间:2010-07-08 18:33:10

标签: java recursion backtracking

好吧,真正的问题稍微复杂一点,因为它使用了我编写的一些类而不是字符串,但可以想象如下:如果你有一个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 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分: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 DOG
  TOW WAR RAR RAD DOG
  TOW WAR RAR RAG GOD
  WAR RAR RAD DOG GOD
  WAR RAR RAG GOD DOG

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

答案 2 :(得分: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, "");
  }
}

答案 3 :(得分: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);
    }

}

它有一个主方法,你最多可以调用三个参数:

  • 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 took 7 1/2 million years可以提出答案42,但它可能仍然比您愿意等待的时间长。

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


误解的结束:

Darn,我错过了连接约束的字母。这是我更新的解决方案。用以下两种方法替换上面的方法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。

无论如何,这是complete solution on 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            

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