// 15-puzzle problem (15 digital games) // PC/Ultraviolet IDs: 110802/10181, popularity: B, success rate: average level: 3 // verdict: accepted // submission date: 2011-06-22/Ultraviolet Run Time: 0.320 S // copyright (c) 2011, Qiu. Metaphysis # Yeah dot net // You can fool some of the people all of the time, and all of the people // some of the time, but you can not fool all of the people all of the time. // Abraham Lincoln, 16th president of us (1809-1865 ). //// for a given situation to determine whether there is a solution method can refer to the http://mathworld.wolfram.com/15Puzzle.html. // Set E to indicate the row where the empty slider is located. N indicates the number of Slide smaller than the current slider value after the current slider. N is: /// 15 15 // n = Σ ni = Σ Ni // I = 1 I = 2 // if n + E is an even number, the current situation is resolved, otherwise, no solution is available. In the following situations: /// 13 10 11 6 // 5 7 4 8 // 1 12 14 9 // 3 15 2 0 // then there are less than 13 and the slider appears later the number is 12, if the value is less than 10 and the number of the slider is 9, similarly, the N values of the other slider are 9 (11), 5 (6), respectively ), 4 (5), 4 (7), 3 (4), 3 (8), 0 (1), 3 (12), // 3 (14 ), 2 (9), 1 (3), 1 (15), 0 (2 ). The sum of all N values is n = 59, and the empty slider is in the 4th rows. Therefore, if E = // 4, N + E = 63 is an odd number, the above situation cannot be solved. /// [Depth-first search] // [see George T. heineman, Gary pollice, and Stanley selkow, algorithms in a // Nutshell, O 'Reilly media, Inc. 2008] /// the deep-Priority Search keeps searching for a viable State, and tries to find the path to the target State at one time. It does not access one state twice, // because some search trees contain a large number of chess and plane statuses, deep priority search is feasible only when the maximum search depth is fixed. // depth Priority Search maintains a stack, saves the status of the game surface that has not been accessed. During each iteration, the deep-first search pops up an unaccessed chess and plane status from the stack. Then, it starts to expand from the chess and plane status and calculates the subsequent chess and plane status based on the walk-through method. If the target chess scene is reached, the search // Suo is terminated. If it does not reach the upper limit, any subsequent chess and plane statuses in the closed set will be discarded. The remaining unaccessed chess and plane statuses are pushed into the stack, // then continue searching. The pseudocode is as follows: // search (initial, goal) // If (initial = goal) then return "solution" // initial. depth = 0 // open = new stack // closed = new set // insert (open, copy (initial) // while (open is not empty) do // n = POP (open) // insert (closed, n) // foreach valid move m at N do // next = state when playing m at N // If (closed doesn't contain next) Then // next. depth = n. depth + 1 // If (next = goal) then return "so Lution "// If (next. depth <maxdepth) Then // insert (open, next) // return "no solution" // end /// deep priority search is a blind search, the condition given by the question is that all solutions can be solved within 45 steps. The solution length should not exceed // 50 steps, and the maximum search depth is set to 50. The key of this algorithm is how to efficiently determine whether a chess and plane status has been accessed, because the whole // algorithm is used to search for an element in a closed set for most of the time, if one/more unique key values can be generated for each chess and plane state, in other words, if the two chess and plane States share the same key value, the two chess and plane states are equivalent, the equivalent meaning also // includes other situations, such as symmetry. In addition, the setting of search depth affects whether the solution can be obtained to a certain extent, because in some cases, a // status is only a few steps away from the final solution, because it reaches the maximum search depth and is placed in the closed set, it is impossible to extend the chess/surface status again, even if the deep search is accessed at an earlier level, it will not continue searching because the shape/State is already in the closed set. //// Considering the first question, it is equivalent to how to efficiently judge the status of two chess faces. Consider the value of the chessboard as a hexadecimal system. You can // use each slider as a digit and press it from left to right, the slider is made up of a hexadecimal number in the order from top to bottom. Then, the previously given Chess/Plane status can be expressed as an integer like this: /// 13*16 ^ 15 + 10*16 ^ 14 + 11*16 ^ 13 + 6*16 ^ 12 + 5*16 ^ 11 + 7*16 ^ 10 + // 4*16 ^ 9 + 8*16 ^ 8 + 1*16 ^ 7 + 12*16 ^ 6 + 14*16 ^ 5 + 9*16 ^ 4 + // 3*16 ^ 3 + 15*16 ^ 2 + 2*16 ^ 1 + 0*16 ^ 1 = dab657481ce93f20 (HEX) /// 15759879913263939360 (DEC) /// you can uniquely represent the situation by representing the Board as an integer. //// [Breadth-first search] //// the breadth-first search attempts to find a shortest path without repeated access. The breadth-first search ensures that if there is a path to the target/status, the path must be the shortest path. The only difference between deep-Priority Search and breadth-priority search is that the breadth-priority search uses the // queue to store open sets, while deep-priority search uses the stack. In each iteration, the breadth-first search takes an unaccessed status from the queue header, // then starts from this status and calculates the successor status. If the target status is reached, the search ends, any subsequent status in the closed set will be discarded. The remaining unaccessed chess and plane statuses will be placed at the end of the open set queue and then continue searching. The pseudo code is as follows: // search (initial, goal) // If (initial = goal) then return "solution" // open = new queue // closed = new set // insert (open, copy (initial) // while (open is not empty) do // n = head (open) // insert (closed, n) // foreach valid move m at N do // next = state when playing m at N // If (closed doesn't contain next) Then // If (next = goal) then return "solution" // insert (open, next) // return "No solu Tion "// end // [A * search for a problem] // [see George T. heineman, Gary pollice, and Stanley selkow, algorithms in a // Nutshell, O 'Reilly media, Inc. 2008] /// the breadth-first search can find an optimal solution (if any), but it may need to access a large number of nodes. It does not try to sort the candidate route //. On the contrary, the depth-first search method is to test the path as far as possible. However, the depth of the depth-first search must be limited, // otherwise, it may spend a lot of time on the path without any results. A * When searching, you can use heuristic information to intelligently adjust search policies. A * search is an iterative and ordered search that maintains an open set of chess and plane states. During each iteration, A * search // suo uses an evaluation function f * (N) to evaluate all the chess and plane states in the open set, and selects the smallest chess and plane states. Definition: // F * (n) = g * (N) + H * (N) // G * (N) estimate the shortest sequence from initial state to state n. // H * (N) estimates the shortest sequence from state n to target State. // F * (N) the shortest sequence for estimating the arrival of the target State starting from the initial state and going through state n. /// Asterisk * indicates that heuristic information is used (this algorithm has been widely accepted since the algorithm was developed in 1968). Therefore, f * (N ), // G * (N), H * (N) is an estimation of the actual overhead F (N), g (N), H (N, the actual overhead can only be known after the solution is obtained. The lower f * (N), the closer State N is to the target State. The most critical part of f * (N) is the heuristic calculation of h * (N). // because g * (N) can be used in the search process, it is calculated in depth by record status n. If H * (N) cannot accurately distinguish between the status in which value continues to be searched and the status in which value does not exist, then a * search will not perform better than a deep search. If H * (N) can be accurately estimated //, f * (N) can be used to obtain the minimum overhead. The pseudocode is as follows: // search (initial, goal) // initial. depth = 0 // open = new priorityqueue // closed = new set // insert (open, copy (initial) // while (open is not empty) do // n = Minimum (open) // insert (closed, n) // If (n = goal) then return "solution" // foreach valid move m at N do // next = state when playing m at N // next. depth = n. depth + 1 // If (closed contains next) Then // prior = state in closed matching Next // If (next. score <prior. score) Then // remove (closed, prior) // insert (open, next) // else // insert (open, next) // return "no solution" // end // [iteratively deepen a * (iterative-deepening A * search, Ida *) to search for problems] // [Refer to Richard E. KLF, "recent progress in the design and analysis of admissible // heuristic functions," proceeding, role action, reformulation, and approximation: // 4th International Symposium (SARA ), Lecture Notes in Computer Science #1864: // 45-51,200 0] /// Ida * depends on a series of gradually expanded and limited depth-first searches. For each subsequent iteration, the search depth limit will be added to the previous base. Ida * is much more efficient than separate deep or breadth-first searches, because the overhead value calculated each time is based on the real/cross-walk sequence rather than the estimation of heuristic functions. # Include <iostream> # include <cstring> # include <limits> # include <vector> # include <queue> # include <stack> # include <set> # include <map> using namespace STD; # define squares 4 // number of horizontal grids in the situation. # Define base 16 // converts the situation to the base number of an integer. # Define depthbound 30 // use the maximum search depth for deep-first search. # Define stepsbound 50 // Maximum length of the solution. # Define move_left (-1) # define move_right 1 # define functions (-squares) # define move_down squares # define move_none 0int Manhattan [squares * squares] [squares * squares] // pre-calculated Manhattan distance table. Int move [squares]; // move later. // A structure of the current chess and plane status data. Struct node {vector <int> state; // indicates the current checkerboard status. Int moves [stepsbound]; // walk sequence from initial status to this status. Int depth; // The current depth. Int score; // the score of the current node. Int blank; // space position .}; // Comparison function of the priority queue. The front-end of the priority queue with a smaller score. Bool operator <(node X, node y) {return X. score> Y. Score;} // calculates the absolute value. Int Absi (int x) {return x> = 0? X: (-x);} // determines whether a given situation can be solved. Bool solvable (vector <int> tiles) {int sum = 0, row; For (INT I = 0; I <tiles. size (); I ++) {int tile = tiles [I]; If (tile = 0) {ROW = (I/squares + 1); continue ;} for (int m = I; m <tiles. size (); m ++) if (tiles [m] <tile & tiles [m]! = 0) sum ++;} return! (Sum + row) % 2);} // obtain the subsequent steps in the current situation. Move_left = move the empty slider to the left, move_right = move the empty slider to the right, // move_up = move the empty slider up, And move_down = move the empty slider down. Void valid_moves (node & Current) {// obtain the next route, except the previous route that is returned to this status. Int last_move = move_none; If (current. depth) last_move = current. moves [current. depth-1]; memset (move, move_none, sizeof (MOVE); If (current. blank % squares> 0 & last_move! = Move_right) Move [0] = move_left; If (current. Blank % squares <(squares-1) & last_move! = Move_left) Move [1] = move_right; If (current. Blank/squares> 0 & last_move! = Move_down) Move [2] = move_up; If (current. Blank/squares <(squares-1) & last_move! = Move_up) Move [3] = move_down;} // converts the Board status to an integer. Unsigned long key (vector <int> & tiles) {unsigned long key = 0; For (INT I = 0; I <tiles. size (); I ++) Key = key * base + tiles [I]; Return key;} // The walk specified by the current move execution. Node execute (node & Current, int move) {node successor; // walk-through step setting. Memcpy (successor. moves, current. moves, sizeof (current. moves); successor. depth = current. depth + 1; successor. moves [current. depth] = move; // set the status. Move in the direction specified by move to switch the empty slider position. Successor. state = current. state; successor. blank = current. blank + move; successor. state [current. blank] = successor. state [successor. blank]; successor. state [successor. blank] = 0; return successor;} // because h * (N) is critical in the algorithm and is highly specific, it varies depending on the problem, therefore, it is difficult to find a proper H * (N) function. Here we use the sum of the Manhattan distance from each square to its target location, the distance from Manhattan is // The number of steps that need to be moved to reach the target State. G * (N) is the depth at which the status is reached. Here the following evaluation letter // number is used: F * (n) = g * (N) + 4/3 * H * (N), H * (N) indicates the distance between the Current State and the target State. You can also // calculate the distance between the Manhattan pairing state. In this experiment, the efficiency is higher than that of Manhattan, but it is lower than that of Manhattan distance multiplied by appropriate numbers. Reference: // [Bernard Bauer, the Manhattan pair distance heuristic for the 15-puzzle, 1994] int score (vector <int> & state, int depth) {int HN = 0; For (INT I = 0; I <state. size (); I ++) if (State [I]> 0) HN + = Manhattan [State [I]-1] [I]; return (depth + 4 * hn/3);} // determine whether the target State has been reached. Bool solved (vector <int> & State) {If (State [squares * squares-1]! = 0) return false; For (INT I = 0; I <squares * squares-1; I ++) if (State [I]! = (I + 1) return false; return true;} // locate the empty slider position in the status. Int find_blank (vector <int> & State) {for (INT I = 0; I <squares * squares; I ++) if (State [I] = 0) return I;} // [depth-first search] // unlike breadth-first search, stack is used to store open sets. The solution can be obtained quickly when the number of mobile steps is small (about 15 steps), but // as the number of mobile steps increases, the solution time and memory usage will increase greatly, therefore, this question is not an effective solution. Whether or not the depth limit of the solution can be obtained is related. If the selected depth is not large enough, the solution may not be obtained. If it is too large, the search time/space will multiply. Bool solve_puzzle_by_depth_first_search (vector <int> tiles, int directions []) {node copy; copy. state = tiles; copy. depth = 0; copy. blank = find_blank (tiles); memset (copy. moves, move_none, sizeof (copy. moves); // checks whether the current situation is resolved. If (solved (copy. State) {memcpy (directions, copy. Moves, sizeof (copy. Moves); Return true ;}// puts the initial state in the open set. Stack <node> open; // use the stack to store the open set for deep priority search. Open. Push (copy); // close the collection. Set <unsigned long> closed; while (! Open. Empty () {// handle the situation of open centralization and put the situation into closed centralization. Node current = open. top (); open. pop (); closed. insert (Key (current. state); // obtain the subsequent steps in this section. All the subsequent steps will be added to the open set. Valid_moves (current); For (INT I = 0; I <squares; I ++) {If (move [I] = move_none) continue; // execute steps in the current situation. Node successor = execute (current, move [I]); // if you have already accessed the node, try another method. If (closed. Find (Key (successor. State ))! = Closed. End () continue; // record the previous step in the solution. If the solution is found, exit immediately. Otherwise, add it to the open set within the limited // depth. If (solved (successor. State) {memcpy (directions, successor. Moves, sizeof (successor. Moves); Return true ;}// puts the current situation into an open set. If (successor. depth <depthbound) open. push (successor);} return false;} // [breadth-first search] // you can quickly obtain a solution when the number of mobile steps is small (about 15 steps, however, as the number of mobile steps increases, the time and memory used for the solution will be greatly increased. Therefore, it is not an effective solution for this question. Bool solve_puzzle_by_breadth_first_search (vector <int> tiles, int directions []) {node copy; copy. state = tiles; copy. depth = 0; copy. blank = find_blank (tiles); memset (copy. moves, move_none, sizeof (copy. moves); // checks whether the current situation is resolved. If (solved (copy. State) {memcpy (directions, copy. Moves, sizeof (copy. Moves); Return true ;}// puts the initial state in the open set. Queue <node> open; // search for open sets by queue storage in the breadth-first mode. Open. Push (copy); // close the collection. Set <unsigned long> closed; while (! Open. Empty () {// handle the situation of open centralization and put the situation into closed centralization. Node current = open. Front (); // breadth first. Open. Pop (); closed. insert (Key (current. State); // obtain the following method, and add the following method to the open set. Valid_moves (current); For (INT I = 0; I <squares; I ++) {If (move [I] = move_none) continue; // execute steps in the current situation. Node successor = execute (current, move [I]); // if you have already accessed the node, try another method. If (closed. Find (Key (successor. State ))! = Closed. End () continue; // record the previous step in the solution. If the solution is found, exit immediately. If (solved (successor. State) {memcpy (directions, successor. Moves, sizeof (successor. Moves); Return true ;}// puts the current situation into an open set. Open. push (successor);} return false;} // [A * search] // both deep-first search and width-first search are blind searches, and no pruning is performed on the search space, as a result, a large number of statuses must be detected. // A * the search criteria pass the evaluation to give priority to low-scoring situations (the lower the score, the closer it is to the target status ), this makes full use of the time, and searches are most likely to reach the target in the shortest time, which is more efficient than simply DFS and BFS, however, because rating calculation is required, the efficiency of heuristic functions is crucial for a * search. It is worth noting that even a poor start/release function can well trim the search space. For complex situations, an excellent heuristic function must be found to obtain the solution at a given time // and memory limit. If a complicated initial situation is given without an excellent heuristic function, A * search stores the generated // node. In most cases, the memory is exhausted due to the large number of status issues before the solution can be found. Bool solve_puzzle_by_a_star (vector <int> tiles, int directions []) {node copy; copy. state = tiles; copy. depth = 0; copy. blank = find_blank (tiles); copy. score = score (copy. state, 0); memset (copy. moves, move_none, sizeof (copy. moves); priority_queue <node> open; // A * search using the priority queue to store the open set. Open. Push (copy); Map <unsigned long, int> closed; while (! Open. Empty () {// Delete the node with the smallest evaluation value, marked as accessed. Node current = open. Top (); open. Pop (); // save the state feature value and State score to the closed set. Closed. insert (make_pair <unsigned long, int> (Key (current. State), current. Score); // if the target status is returned. If (solved (current. state) {memcpy (directions, current. moves, sizeof (current. moves); Return true;} // calculate the next steps to update the open and closed collections. Valid_moves (current); For (INT I = 0; I <squares; I ++) {If (move [I] = move_none) continue; // move the slider, evaluate the new status. Node successor = execute (current, move [I]); // score the current state based on the heuristic function. Successor. score = score (successor. state, successor. depth); // if the current status has been accessed, check whether the status can be reached at a lower cost. If no, continue; otherwise, put forward and process the status from the closure set. In the deep priority search, the closed collection may be entered due to a high score generated at the end of the stack. The same situation is generated at the bottom of the stack, and the score is relatively low, however, because a high score is already in a closed and concentrated state, the equivalent situation with a low score will not be // considered, which may lead to a "missing" solution for deep search. Map <unsigned long, int>: iterator it = closed. Find (Key (successor. State); If (it! = Closed. end () {If (successor. score> = (* it ). second) continue; closed. erase (IT) ;}// puts the current situation into open concentration. Open. push (successor);} return false;} // [IDA * search] // depth takes precedence over memory usage. However, because no pruning is performed, the search space is huge, A * although the search is pruned, the memory consumption is high for each node generated by the storage/storage. Ida * search combines the advantages of both. Ida * is essentially limiting the search depth by using heuristic functions on the depth-first search // algorithm. Bool solve_puzzle_by_iterative_deepening_a_star (vector <int> tiles, int directions []) {node copy; copy. state = tiles; copy. depth = 0; copy. blank = find_blank (tiles); memset (copy. moves, move_none, sizeof (copy. moves); // checks whether the current situation is resolved. If (solved (copy. state) {memcpy (directions, copy. moves, sizeof (copy. moves); Return true;} // sets the initial search depth to the initial score. Int depth_limit = 0, min_depth = score (copy. State, 0); While (true) {// get the score after iteration. If (depth_limit <min_depth) depth_limit = min_depth; elsedepth_limit ++; numeric_limits <int> T; min_depth = T. max (); // start a new depth-first search, and the depth is depth_limit. Stack <node> open; open. Push (copy); While (! Open. Empty () {node current = open. Top (); open. Pop (); // obtain the subsequent steps in this section. All subsequent steps will be added to the open set. Valid_moves (current); For (INT I = 0; I <squares; I ++) {If (move [I] = move_none) continue; // execute steps in the current situation. Node successor = execute (current, move [I]); // record the previous step in the solution. If the solution is found, exit immediately. Otherwise, add it to the open set within the limit depth. If (solved (successor. state) {memcpy (directions, successor. moves, sizeof (successor. moves); Return true;} // calculate the score of the current node. If the score is smaller than the limit, add it to the stack. Otherwise, find the minimum value that is exceeded. Successor. score = score (successor. state, successor. depth); If (successor. score <depth_limit) open. push (successor); else {If (successor. score <min_depth) min_depth = successor. score ;}}}return false;} void solve_puzzle (vector <int> tiles) {int moves [stepsbound]; // depth-first search. // Solve_puzzle_by_depth_first_search (tiles, moves); // search by width first. // Solve_puzzle_by_breadth_first_search (tiles, moves); // A * search. The solution length is between 30 and 50 steps, and the average length is 7 s. Ultraviolet (a) rt 1.004 S. // Solve_puzzle_by_a_star (tiles, moves); // ida * search. The solution length ranges from 30 to 50 steps. The average duration is 1.5 s. Ultraviolet (a) rt 0.320 S. Solve_puzzle_by_iterative_deepening_a_star (tiles, moves); // output steps. For (INT I = 0; I <stepsbound; I ++) {If (moves [I] = move_none) break; Switch (moves [I]) {Case move_left: cout <"L"; break; Case move_right: cout <"R"; break; Case move_up: cout <"U"; break; Case move_down: cout <"D"; break ;}} cout <Endl ;}// pre-calculate the Manhattan distance to fill out the table. Void cal_manhattan (void) {for (INT I = 0; I <squares * squares; I ++) for (Int J = 0; j <squares * squares; j ++) {int TMP = 0; TMP + = (Absi (I/squares-J/squares) + Absi (I % squares-J % squares )); manhattan [I] [J] = TMP;} int main (INT AC, char * AV []) {// calculate the Manhattan distance and fill in the table. Cal_manhattan (); int N, tile; vector <int> tiles; // indicates the slider for a given situation. Cin> N; while (n --) {// read the status. Tiles. clear (); For (INT I = 0; I <squares * squares; I ++) {CIN> tile; tiles. push_back (tile);} // determines whether there is a solution. If there is no solution, the corresponding information is output. If there is a solution, the corresponding algorithm is used to solve the problem. If (solvable (tiles) solve_puzzle (tiles); elsecout <"this puzzle is not solvable." <Endl;} return 0 ;}