поиск перестановок и комбинаций Java-строк - PullRequest
9 голосов
/ 04 февраля 2012

Я пишу приложение Android word. Мой код включает метод, который будет находить все комбинации строки и подстрок 7-буквенной строки с минимальной длиной 3. Затем сравните все доступные комбинации с каждым словом в словаре, чтобы найти все допустимые слова. Я использую рекурсивный метод. Вот код.

// Gets all the permutations of a string.
void permuteString(String beginningString, String endingString) {
    if (endingString.length() <= 1){
        if((Arrays.binarySearch(mDictionary, beginningString.toLowerCase() +   endingString.toLowerCase())) >= 0){
            mWordSet.add(beginningString + endingString);
        }
    }
    else
        for (int i = 0; i < endingString.length(); i++) {
            String newString = endingString.substring(0, i) + endingString.substring(i + 1);
            permuteString(beginningString + endingString.charAt(i), newString);
      }
}
// Get the combinations of the sub-strings. Minimum 3 letter combinations
void subStrings(String s){
    String newString = "";
    if(s.length() > 3){
        for(int x = 0; x < s.length(); x++){
            newString = removeCharAt(x, s);
            permuteString("", newString);
            subStrings(newString);
        }
    }
}

Приведенный выше код работает нормально, но когда я установил его на Nexus, я понял, что он работает слишком медленно. Это займет несколько секунд. Около 3 или 4 секунд, что недопустимо. Теперь я играл в некоторые словесные игры на своем телефоне, и они мгновенно вычисляют все комбинации строк, что заставляет меня поверить, что мой алгоритм не очень эффективен и его можно улучшить. Кто-нибудь может помочь?


public class TrieNode {
TrieNode a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
TrieNode[] children = {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z};
private ArrayList<String> words = new ArrayList<String>();

public void addWord(String word){
    words.add(word);
}
public ArrayList<String> getWords(){
    return words;
}
}

public class Trie {

static String myWord;
static String myLetters = "afinnrty";
static char[] myChars;
static Sort sort;
static TrieNode myNode = new TrieNode();
static TrieNode currentNode;
static int y = 0;
static ArrayList<String> availableWords = new ArrayList<String>();

public static void main(String[] args) {

    readWords();
    getPermutations();
}
public static void getPermutations(){
    currentNode = myNode;
    for(int x = 0; x < myLetters.length(); x++){
        if(currentNode.children[myLetters.charAt(x) - 'a'] != null){
            //availableWords.addAll(currentNode.getWords());
            currentNode = currentNode.children[myLetters.charAt(x) - 'a'];
            System.out.println(currentNode.getWords() + "" + myLetters.charAt(x));
        }
    }
    //System.out.println(availableWords);
}
public static void readWords(){
    try {
        BufferedReader in = new BufferedReader(new FileReader("c://scrabbledictionary.txt"));
        String str;
        while ((str = in.readLine()) != null) {
            myWord = str;
            myChars = str.toCharArray();
            sort = new Sort(myChars);
            insert(myNode, myChars, 0);
        }
        in.close();
    } catch (IOException e) {
    }
}
public static void insert(TrieNode node, char[] myChars, int x){    
    if(x >= myChars.length){
        node.addWord(myWord);
        //System.out.println(node.getWords()+""+y);
        y++;
        return;
    }
    if(node.children[myChars[x]-'a'] == null){
        insert(node.children[myChars[x]-'a'] = new TrieNode(), myChars, x=x+1);
    }else{
        insert(node.children[myChars[x]-'a'], myChars, x=x+1);
    }
}
}

Ответы [ 6 ]

16 голосов
/ 04 февраля 2012

В вашем текущем подходе вы просматриваете каждую перестановку каждой подстроки. Так что для "abc" вам нужно поискать "abc", "acb", "bac", "bca", "cab" и "cba". Если вы хотите найти все перестановки «перестановок», ваше число поисков составляет почти 500 000 000 , и это еще до того, как вы даже посмотрели его подстроки. Но мы можем уменьшить это до одного поиска, независимо от длины, путем предварительной обработки словаря.

Идея состоит в том, чтобы поместить каждое слово в словаре в некоторую структуру данных, где каждый элемент содержит набор символов и список всех слов, содержащих (только) эти символы. Например, вы могли бы построить двоичное дерево, в котором был бы узел, содержащий (отсортированный) набор символов "abd" и список слов ["bad", "dab"]. Теперь, если мы хотим найти все перестановки "dba", мы сортируем его, чтобы получить "abd", и ищем его в дереве, чтобы получить список.

Как указывал Бауман, пытается хорошо подходят для хранения данных такого рода. Прелесть в том, что время поиска зависит только от длины строки поиска - оно независимо от размера вашего словаря . Поскольку вы будете хранить довольно много слов, и большинство ваших строк поиска будут крошечными (большинство из них будут 3-символьными подстроками с самого низкого уровня вашей рекурсии), эта структура идеальна.

В этом случае пути вниз по вашей цепочке будут отражать наборы символов, а не сами слова. Так что, если весь ваш словарь был ["bad", "dab", "cab", "cable"], ваша структура поиска в итоге выглядела бы так:

Example trie

Существует немного компромисса между временем и пространством в том, как вы реализуете это. В простейшем (и самом быстром) подходе каждый Node содержит только список слов и массив Node[26] дочерних элементов. Это позволяет вам находить нужного вам ребенка в постоянное время, просто взглянув на children[s.charAt(i)-'a'] (где s - строка поиска, а i - текущая глубина в дереве).

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

При наличии трех элементов вся функция перестановки заменяется одним поиском, что снижает сложность с O (N! Log D) (где D - это размер ваш словарь, N размер вашей строки) до O (N log N) (так как вам нужно отсортировать символы; сам поиск - O (N) ).

РЕДАКТИРОВАТЬ: Я собрал (не проверено) реализацию этой структуры: http://pastebin.com/Qfu93E80

1 голос
/ 10 апреля 2015

Я не думаю, что добавление всех перестановок необходимо.Вы можете просто инкапсулировать строку в PermutationString:

public class PermutationString {

    private final String innerString;

    public PermutationString(String innerString) {
        this.innerString = innerString;
    }

    @Override
    public int hashCode() {
        int hash = 0x00;
        String s1 = this.innerString;
        for(int i = 0; i < s1.length(); i++) {
            hash += s1.charAt(i);
        }
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final PermutationString other = (PermutationString) obj;
        int nChars = 26;
        int[] chars = new int[nChars];
        String s1 = this.innerString;
        String s2 = other.innerString;
        if(s1.length() != s2.length()) {
            return false;
        }
        for(int i = 0; i < s1.length(); i++) {
            chars[s1.charAt(i)-'a']++;
        }
        for(int i = 0; i < s2.length(); i++) {
            chars[s2.charAt(i)-'a']--;
        }
        for(int i = 0; i < nChars; i++) {
            if(chars[i] != 0x00) {
                return false;
            }
        }
        return true;
    }

}

A PermutationString - это строка, но где два PermutationString s равны, если они имеют одинаковую частоту символов.Таким образом new PermutationString("bad").equals(new PermutationString("dab")).Это также верно для .hashCode(): если строки являются перестановками друг друга, они будут генерировать одинаковые .hashCode().

Теперь вы можете просто HashMap<PermutationString,ArrayList<String>> сделать следующее:

HashMap<PermutationString,ArrayList<String>> hm = new HashMap<PermutationString,ArrayList<String>>();
String[] dictionary = new String[] {"foo","bar","oof"};
ArrayList<String> items;
for(String s : dictionary) {
    PermutationString ps = new PermutationString(s);
    if(hm.containsKey(ps)) {
        items = hm.get(ps);
        items.add(s);
    } else {
        items = new ArrayList<String>();
        items.add(s);
        hm.put(ps,items);
    }
}

Итак, теперь мы перебираем все возможные слова в словаре, создаем PermutationString как ключ , и если ключ уже существует (это означает, что слово уже существуетс теми же частотами символов), мы просто добавляем к нему свое слово.В противном случае, мы добавляем новый ArrayList<String> с одним словом.

Теперь, когда мы заполнили hm всеми перестановками (но не так много ключей ), вы можете запросить:

hm.get(new PermutationString("ofo"));

Это вернет ArrayList<String> с "foo" и "oof".

Testcase :

HashMap<PermutationString, ArrayList<String>> hm = new HashMap<PermutationString, ArrayList<String>>();
String[] dictionary = new String[]{"foo", "bar", "oof"};
ArrayList<String> items;
for (String s : dictionary) {
    PermutationString ps = new PermutationString(s);
    if (hm.containsKey(ps)) {
        items = hm.get(ps);
        items.add(s);
    } else {
        items = new ArrayList<String>();
        items.add(s);
        hm.put(ps, items);
    }
}
Assert.assertNull(hm.get(new PermutationString("baa")));
Assert.assertNull(hm.get(new PermutationString("brr")));
Assert.assertNotNull(hm.get(new PermutationString("bar")));
Assert.assertEquals(1,hm.get(new PermutationString("bar")).size());
Assert.assertNotNull(hm.get(new PermutationString("rab")));
Assert.assertEquals(1,hm.get(new PermutationString("rab")).size());
Assert.assertNotNull(hm.get(new PermutationString("foo")));
Assert.assertEquals(2,hm.get(new PermutationString("foo")).size());
Assert.assertNotNull(hm.get(new PermutationString("ofo")));
Assert.assertEquals(2,hm.get(new PermutationString("ofo")).size());
Assert.assertNotNull(hm.get(new PermutationString("oof")));
Assert.assertEquals(2,hm.get(new PermutationString("oof")).size());
1 голос
/ 26 июля 2012
  static List<String> permutations(String a) {
    List<String> result=new LinkedList<String>();
    int len = a.length();
    if (len<=1){
      result.add(a);
    }else{
      for (int i=0;i<len; i++){
        for (String it:permutations(a.substring(0, i)+a.substring(i+1))){
          result.add(a.charAt(i)+it);
        }
      }
    }
    return result;
  }
1 голос
/ 04 февраля 2012

См. Здесь: Как найти список возможных слов из буквенной матрицы [Boggle Solver]

Идея кода в ответах такова:

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

Что ж, вы можете расширить свои словарные объекты массивом letters[], где letters[i] остается на время, которое i-я буква алфавита использовала в этом слове. Потребуется дополнительная память, не намного больше, чем сейчас.

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

Сложность: для предварительного расчета потребуется O (D * maxLen) и O (max (N, D)) для каждого запроса.

0 голосов
/ 04 февраля 2012

Используйте Trie

Вместо того, чтобы тестировать все N! возможности, вы следуете только деревьям префиксов, которые приводят к результату. Это значительно уменьшит количество проверяемых строк.

...