我有一个填字游戏和一个可以用来解决它的单词列表(单词可以放置 多次,甚至不能 放置 一次 )。给定的填字游戏和单词列表始终有解决方案。
我搜索了有关如何解决此问题的线索,发现它是NP-Complete。我的最大填字游戏大小为250 x 250,列表的最大长度(可用于解决此问题的单词数量)为200。我的目标是通过蛮力/回溯来解决此大小的填字游戏,几秒钟(这是我的粗略估计,如果我错了,请纠正我)。
例:
可以用来解决填字游戏的给定单词列表:
给定的空填字游戏(X是无法填写的字段,需要填写空白字段):
解决方案:
现在,我目前的方法是将填字游戏表示为二维数组并搜索空白空间(填字游戏重复2次)。然后我根据长度将单词匹配到空白处,然后尝试将所有单词组合到具有相同长度的空白处。这种方法很快变得非常混乱,我迷失了实现的方法,还有没有更优雅的解决方案?
您拥有的基本想法非常明智:
这是一个很棒的计划。下一步是将其转换为设计。对于这样的小程序,我们可以直接使用伪代码。如其他答案所述,其要点是递归:
1 Draw a slot from the slot pool. 2 If slot pool is empty (all slots filled), stop solving. 3 For each word with correct length: 4 If part of the slot is filled, check conflict. 5 If the word does not fit, continue the loop to next word. // No conflict 6 Fill the slot with the word. // Try next slot (down a level) 7 Recur from step 1. 8 If the recur found no solution, revert (take the word back) and try next. // None of them works 9 If no words yield a solution, an upper level need to try another word. Revert (put the slot back) and go back.
以下是一个简短但完整的示例,我根据您的要求进行了整理。
剥猫的方法不止一种。我的代码交换了步骤1和2,并在一个填充循环中组合了步骤4至6。
关键点:
clone()
资源:
import java.awt.Point; import java.util.*; import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Stream; public class Crossword { public static void main ( String[] args ) { new Crossword( Arrays.asList( "5 4 4\n#_#_#\n_____\n#_##_\n#_##_\ntuna\nmusic\ncan\nhi".split( "\n" ) ) ); new Crossword( Arrays.asList( "6 6 4\n##_###\n#____#\n___#__\n#_##_#\n#____#\n##_###\nnice\npain\npal\nid".split( "\n" ) ) ); } private final int height, width; // Board size private final char[] board; // Current board state. _ is unfilled. # is blocked. other characters are filled. private final Set<String> words; // List of words private final Map<Point, Integer> vertical = new HashMap<>(), horizontal = new HashMap<>(); // Vertical and horizontal slots private String indent = ""; // For formatting log private void log ( String message, Object... args ) { System.out.println( indent + String.format( message, args ) ); } private Crossword ( List<String> lines ) { // Parse input data final int[] sizes = Stream.of( lines.get(0).split( "\\s+" ) ).mapToInt( Integer::parseInt ).toArray(); width = sizes[0]; height = sizes[1]; board = String.join( "", lines.subList( 1, height+1 ) ).toCharArray(); words = new HashSet<>( lines.subList( height+1, lines.size() ) ); // Find horizontal slots then vertical slots for ( int y = 0, size ; y < height ; y++ ) for ( int x = 0 ; x < width-1 ; x++ ) if ( isSpace( x, y ) && isSpace( x+1, y ) ) { for ( size = 2 ; x+size < width && isSpace( x+size, y ) ; size++ ); // Find slot size horizontal.put( new Point( x, y ), size ); x += size; // Skip past this horizontal slot } for ( int x = 0, size ; x < width ; x++ ) for ( int y = 0 ; y < height-1 ; y++ ) if ( isSpace( x, y ) && isSpace( x, y+1 ) ) { for ( size = 2 ; y+size < height && isSpace( x, y+size ) ; size++ ); // Find slot size vertical.put( new Point( x, y ), size ); y += size; // Skip past this vertical slot } log( "A " + width + "x" + height + " board, " + vertical.size() + " vertical, " + horizontal.size() + " horizontal." ); // Solve the crossword, horizontal first then vertical final boolean solved = solveHorizontal(); // Show board, either fully filled or totally empty. for ( int i = 0 ; i < board.length ; i++ ) { if ( i % width == 0 ) System.out.println(); System.out.print( board[i] ); } System.out.println( solved ? "\n" : "\nNo solution found\n" ); } // Helper functions to check or set board cell private char get ( int x, int y ) { return board[ y * width + x ]; } private void set ( int x, int y, char character ) { board[ y * width + x ] = character; } private boolean isSpace ( int x, int y ) { return get( x, y ) == '_'; } // Fit all horizontal slots, when success move to solve vertical. private boolean solveHorizontal () { return solve( horizontal, this::fitHorizontal, "horizontally", this::solveVertical ); } // Fit all vertical slots, report success when done private boolean solveVertical () { return solve( vertical, this::fitVertical, "vertically", () -> true ); } // Recur each slot, try every word in a loop. When all slots of this kind are filled successfully, run next stage. private boolean solve ( Map<Point, Integer> slot, BiFunction<Point, String, Boolean> fill, String dir, Supplier<Boolean> next ) { if ( slot.isEmpty() ) return next.get(); // If finished, move to next stage. final Point pos = slot.keySet().iterator().next(); final int size = slot.remove( pos ); final char[] state = board.clone(); /* Try each word */ indent += " "; for ( String word : words ) { if ( word.length() != size ) continue; /* If the word fit, recur. If recur success, done! */ log( "Trying %s %s at %d,%d", word, dir, pos.x, pos.y ); if ( fill.apply( pos, word ) && solve( slot, fill, dir, next ) ) return true; /* Doesn't match. Restore board and try next word */ log( "%s failed %s at %d,%d", word, dir, pos.x, pos.y ); System.arraycopy( state, 0, board, 0, board.length ); } /* No match. Restore slot and report failure */ indent = indent.substring( 0, indent.length() - 2 ); slot.put( pos, size ); return false; } // Try fit a word to a slot. Return false if there is a conflict. private boolean fitHorizontal ( Point pos, String word ) { final int x = pos.x, y = pos.y; for ( int i = 0 ; i < word.length() ; i++ ) { if ( ! isSpace( x+i, y ) && get( x+i, y ) != word.charAt( i ) ) return false; // Conflict set( x+i, y, word.charAt( i ) ); } return true; } private boolean fitVertical ( Point pos, String word ) { final int x = pos.x, y = pos.y; for ( int i = 0 ; i < word.length() ; i++ ) { if ( ! isSpace( x, y+i ) && get( x, y+i ) != word.charAt( i ) ) return false; // Conflict set( x, y+i, word.charAt( i ) ); } return true; } }
练习:您可以将递归重写为迭代。更快,并且可以支持更大的板。一旦完成,它可以转换为多线程并运行得更快。