Alogrithm - Word Ladder (Java)

分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击人工智能教程

package chimomo.learning.java.algorithm;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

/**
 * @author Created by Chimomo
 */
public class WordLadder {
    public static List<String> readWords(BufferedReader in) throws IOException {
        String oneLine;
        List<String> list = new ArrayList<>();

        while ((oneLine = in.readLine()) != null) {
            list.add(oneLine);
        }

        return list;
    }

    /**
     * Returns true if word1 and word2 are the same length and differ in only one character.
     *
     * @param word1 Word1
     * @param word2 Word2
     * @return True if word1 and word2 are the same length and differ in only one character, false otherwise.
     */
    private static boolean oneCharOff(String word1, String word2) {
        if (word1.length() != word2.length()) {
            return false;
        }

        int diffs = 0;
        for (int i = 0; i < word1.length(); i++) {
            if (word1.charAt(i) != word2.charAt(i)) {
                if (++diffs > 1) {
                    return false;
                }
            }
        }

        return diffs == 1;
    }

    private static <KeyType> void update(Map<KeyType, List<String>> m, KeyType key, String value) {
        List<String> list = m.get(key);
        if (list == null) {
            list = new ArrayList<>();
            m.put(key, list);
        }

        list.add(value);
    }

    /**
     * Computes a map in which the keys are words and values are lists of words
     * that differ in only one character from the corresponding key.
     * Uses a quadratic algorithm (with appropriate Map).
     *
     * @param theWords The words.
     * @return The map.
     */
    public static Map<String, List<String>> computeAdjacentWordsSlow(List<String> theWords) {
        Map<String, List<String>> adjWords = new HashMap<>();
        String[] words = new String[theWords.size()];
        theWords.toArray(words);

        for (int i = 0; i < words.length; i++) {
            for (int j = i + 1; j < words.length; j++) {
                if (oneCharOff(words[i], words[j])) {
                    update(adjWords, words[i], words[j]);
                    update(adjWords, words[j], words[i]);
                }
            }
        }

        return adjWords;
    }

    /**
     * Computes a map in which the keys are words and values are lists of words
     * that differ in only one character from the corresponding key.
     * Uses a quadratic algorithm (with appropriate map),
     * but speeds things up a little by maintaining an additional map that groups words by their length.
     *
     * @param theWords The words.
     * @return The map.
     */
    public static Map<String, List<String>> computeAdjacentWordsMedium(List<String> theWords) {
        Map<String, List<String>> adjWords = new HashMap<>();
        Map<Integer, List<String>> wordsByLength = new HashMap<>();

        // Group the words by their length.
        for (String w : theWords) {
            update(wordsByLength, w.length(), w);
        }

        // Work on each group separately.
        for (List<String> groupsWords : wordsByLength.values()) {
            String[] words = new String[groupsWords.size()];
            groupsWords.toArray(words);

            for (int i = 0; i < words.length; i++) {
                for (int j = i + 1; j < words.length; j++) {
                    if (oneCharOff(words[i], words[j])) {
                        update(adjWords, words[i], words[j]);
                        update(adjWords, words[j], words[i]);
                    }
                }
            }
        }

        return adjWords;
    }

    /**
     * Computes a map in which the keys are words and values are lists of words
     * that differ in only one character from the corresponding key.
     * Uses an efficient algorithm that is O(NlogN) with a TreeMap, or O(N) if a HashMap is used.
     *
     * @param words The words.
     * @return The map.
     */
    public static Map<String, List<String>> computeAdjacentWords(List<String> words) {
        Map<String, List<String>> adjWords = new TreeMap<>();
        Map<Integer, List<String>> wordsByLength = new TreeMap<>();

        // Group the words by their length.
        for (String w : words) {
            update(wordsByLength, w.length(), w);
        }

        // Work on each group separately.
        for (Map.Entry<Integer, List<String>> entry : wordsByLength.entrySet()) {
            List<String> groupsWords = entry.getValue();
            int groupNum = entry.getKey();

            // Work on each position in each group.
            for (int i = 0; i < groupNum; i++) {
                // Remove one character in specified position, computing representative.
                // Words with same representative are adjacent, so first populate a map.
                Map<String, List<String>> repToWord = new HashMap<>();

                for (String str : groupsWords) {
                    String rep = str.substring(0, i) + str.substring(i + 1);
                    update(repToWord, rep, str);
                }

                // And then look for map values with more than one string.
                for (List<String> wordClique : repToWord.values()) {
                    if (wordClique.size() >= 2) {
                        for (String s1 : wordClique) {
                            for (String s2 : wordClique) {
                                // Must be same string; equals not needed.
                                if (s1 != s2) {
                                    update(adjWords, s1, s2);
                                }
                            }
                        }
                    }
                }
            }
        }

        return adjWords;
    }

    /**
     * Find most changeable word: the word that differs in only one character with the most words.
     * Return a list of these words, in case of a tie.
     *
     * @param adjacentWords Adjacent words.
     * @return A list of these words.
     */
    public static List<String> findMostChangeable(Map<String, List<String>> adjacentWords) {
        List<String> mostChangeableWords = new ArrayList<>();
        int maxNumberOfAdjacentWords = 0;

        for (Map.Entry<String, List<String>> entry : adjacentWords.entrySet()) {
            List<String> changes = entry.getValue();

            if (changes.size() > maxNumberOfAdjacentWords) {
                maxNumberOfAdjacentWords = changes.size();
                mostChangeableWords.clear();
            }
            if (changes.size() == maxNumberOfAdjacentWords) {
                mostChangeableWords.add(entry.getKey());
            }
        }

        return mostChangeableWords;
    }

    public static void printMostChangeables(List<String> mostChangeable, Map<String, List<String>> adjacentWords) {
        for (String word : mostChangeable) {
            System.out.print(word + ":");
            List<String> adjacents = adjacentWords.get(word);

            for (String str : adjacents) {
                System.out.println(" " + str);
            }

            System.out.println(" (" + adjacents.size() + " words)");
        }
    }

    public static void printHighChangeables(Map<String, List<String>> adjacentWords, int minWords) {
        for (Map.Entry<String, List<String>> entry : adjacentWords.entrySet()) {
            List<String> words = entry.getValue();

            if (words.size() >= minWords) {
                System.out.print(entry.getKey() + " )" + words.size() + "):");
                for (String w : words) {
                    System.out.print(" " + w);
                }
                System.out.println();
            }
        }
    }

    /**
     * After the shortest path calculation has run,
     * computes the list that contains the sequence of word changes to get from first to second.
     *
     * @param prev   The prev map.
     * @param first  The first word.
     * @param second The second word.
     * @return The list that contains the sequence of word changes to get from first to second.
     */
    public static List<String> getChainFromPreviousMap(Map<String, String> prev, String first, String second) {
        LinkedList<String> result = new LinkedList<>();

        if (prev.get(second) != null) {
            for (String str = second; str != null; str = prev.get(str)) {
                result.addFirst(str);
            }
        }

        return result;
    }

    /**
     * Runs the shortest path calculation from the adjacency map,
     * returning a list that contains the sequence of words changes to get from first to second.
     *
     * @param adjacentWords Adjacent words.
     * @param first         The first word.
     * @param second        The second word.
     * @return A list that contains the sequence of words changes to get from first to second.
     */
    public static List<String> findChain(Map<String, List<String>> adjacentWords, String first, String second) {
        Map<String, String> previousWord = new HashMap<>();
        Queue<String> q = new LinkedList<>();
        q.add(first);

        while (!q.isEmpty()) {
            String current = q.element();
            q.remove();
            List<String> adj = adjacentWords.get(current);

            if (adj != null) {
                for (String adjWord : adj) {
                    if (previousWord.get(adjWord) == null) {
                        previousWord.put(adjWord, current);
                        q.add(adjWord);
                    }
                }
            }
        }

        previousWord.put(first, null);

        return getChainFromPreviousMap(previousWord, first, second);
    }

    /**
     * Runs the shortest path calculation from the original list of words,
     * returning a list that contains the sequence of word changes to get from first to second.
     * Since this calls computeAdjacentWords,
     * it is recommended that the user instead call computeAdjacentWords once and then call other findChar for each word pair.
     *
     * @param words  The words.
     * @param first  The first word.
     * @param second The second word.
     * @return A list that contains the sequence of word changes to get from first to second.
     */
    public static List<String> findChain(List<String> words, String first, String second) {
        Map<String, List<String>> adjacentWords = computeAdjacentWords(words);
        return findChain(adjacentWords, first, second);
    }

    // Test program.
    public static void main(String[] args) throws IOException {
        long start, end;

        FileReader fin = new FileReader("dict.txt");
        BufferedReader bin = new BufferedReader(fin);
        List<String> words = readWords(bin);
        System.out.println("Read the words..." + words.size());
        Map<String, List<String>> adjacentWords;

        // Compute adjacent words.
        start = System.currentTimeMillis();
        adjacentWords = computeAdjacentWords(words);
        end = System.currentTimeMillis();
        System.out.println("Elapsed time FAST: " + (end - start));

        // Compute adjacent words medium.
        start = System.currentTimeMillis();
        adjacentWords = computeAdjacentWordsMedium(words);
        end = System.currentTimeMillis();
        System.out.println("Elapsed time MEDIUM: " + (end - start));

        // Compute adjacent words slow.
        start = System.currentTimeMillis();
        adjacentWords = computeAdjacentWordsSlow(words);
        end = System.currentTimeMillis();
        System.out.println("Elapsed time SLOW: " + (end - start));

        // printHighChangeables( adjacentWords, 15 );

        // Find most changeable.
        System.out.println("Adjacents computed...");
        List<String> mostChangeable = findMostChangeable(adjacentWords);
        System.out.println("Most changeable computed...");
        printMostChangeables(mostChangeable, adjacentWords);

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        for (; ; ) {
            System.out.println("Enter two words: ");
            String w1 = in.readLine();
            String w2 = in.readLine();

            List<String> path = findChain(adjacentWords, w1, w2);
            System.out.print(path.size() + "...");
            for (String word : path) {
                System.out.print(" " + word);
            }
            System.out.println();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值