Algorithms and Data Structures I

Embed Size (px)

Citation preview

  • 7/25/2019 Algorithms and Data Structures I

    1/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 1/20

    Module 9: Recursion, Part II

    Supplemental material

    The Tower of Hanoi

    In the Tower of Hanoi puzzle:

    There are three stacks (towers), numbered 0, 1, 2.

    There areNdisks numbered 0, ..., N-1.=> 0is the smallest.

    Initially, the disks are placed in tower 0, in the order largest to smallest.=> Smallest on top.

    Goal: to move the disks to tower 1using only legal moves.

    What's a legal move?You can move only a disk at the top of a tower.You can move only one disk at a time.You cannot place a disk on top of a smaller one.

    We will solve the problem using (of course) recursion:

    There are three key steps in the recursion:

    Step 1: move disks 0, ..., N-2from tower 0 to tower 2

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/suppl/index.htmlhttp://www.seas.gwu.edu/~drum/cs1112/lectures/module9/suppl/index.html
  • 7/25/2019 Algorithms and Data Structures I

    2/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 2/20

    => Sub problem of smaller size.

    Step 2: move diskN-1from tower 0 to tower 1.

    Step 3: move disks 0, ..., N-2from tower 2 to tower 1.=> Sub problem of smaller size.

    In pseudocode (without base case):

    Algorithm towerOfHanoi (n, i, j)Input: Disks numbered 0, ..., n are to be moved from tower i to tower j

    1. // ... base case ...

    // First find the third tower, other than i and j: 2. k = otherTower (i, j)

    // Step 1: move disks 0,..,n1 from i to k3. towerOfHanoi (n1, i, k)

    // Step 2: move disk# n from i to j4. move (n, i, j)

    // Step 3: move disks 0,...,n1 from k to j5. towerOfHanoi (n1, k, j)

    The base case: move a single disk

  • 7/25/2019 Algorithms and Data Structures I

    3/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 3/20

    Here's the program: (source file)

    public class TowerOfHanoi {

    public static void main (String[] argv){

    // A 3disk puzzle:System.out.println ("3Disk solution: ");solveHanoi (2, 0, 1);

    // A 4disk puzzle:

    System.out.println ("4Disk solution: ");solveHanoi (3, 0, 1);

    }

    static void solveHanoi (int n, int i, int j){

    // Bottomout.if (n == 0) {

    // The smallest disk.move (0, i, j);return;

    }

    int k = other (i, j);solveHanoi (n1, i, k); // Step 1.move (n, i, j); // Step 2.solveHanoi (n1, k, j); // Step 3.

    }

    static void move (int n, int i, int j){

    // For now, we'll merely print out the move.System.out.println ("=> Move disk# " + n + " from tower " + i + " to tower " + j);

    }

    static int other (int i, int j)

    {// Given two towers, return the third.if ( (i == 0) && (j == 1) ) {

    return 2;}else if ( (i == 1) && (j == 0) ) {

    return 2;}else if ( (i == 1) && (j == 2) ) {

    return 0;}else if ( (i == 2) && (j == 1) ) {

    return 0;}

    else if ( (i == 0) && (j == 2) ) {return 1;

    }else if ( (i == 2) && (j == 0) ) {

    return 1;}

    // We shouldn't reach here.return 1;

    }

    }

    Note:

    The above program merely prints out the moves needed=> We don't actually maintain the state of each tower.

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/TowerOfHanoi.java
  • 7/25/2019 Algorithms and Data Structures I

    4/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 4/20

    Next, suppose we want to maintain the state of each tower:

    The ideal data structure is a stack.

    We'll use one stack for each tower.

    Here's the program: (source file)

    import java.util.*;

    public class TowerOfHanoi2 {

    // towers[0], towers[1] and towers[2] are the three stacks.static Stack[] towers;

    public static void main (String[] argv){

    // A 3disk puzzle:System.out.println ("3Disk solution: ");solveHanoi (2, 0, 1);

    // A 4disk puzzle:System.out.println ("4Disk solution: ");solveHanoi (3, 0, 1);

    }

    static void solveHanoi (int n, int i, int j){

    // Create the three stacks.towers = new Stack [3];for (int k=0; k=0; k) {

    towers[0].add (k);}

    // Print the initial stack.printTowers ();

    // Now solve recursively. Note: this is the method below.solveHanoiRecursive (n, i, j);

    }

    static void solveHanoiRecursive (int n, int i, int j){

    // Bottomout.

    if (n == 0) {move (0, i, j);return;

    }int k = other (i, j);solveHanoiRecursive (n1, i, k); // Step 1.move (n, i, j); // Step 2.solveHanoiRecursive (n1, k, j); // Step 3.

    }

    static void move (int n, int i, int j){

    // Pull out the top disk on stack i.int topVal = towers[i].pop();// Put it on stack j.towers[j].push (topVal);// Print status.System.out.println ("Towers after moving " + n + " from tower " + i + " to tower " + j);printTowers ();

    }

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/TowerOfHanoi2.java
  • 7/25/2019 Algorithms and Data Structures I

    5/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 5/20

    static int other (int i, int j){

    // ...}

    static void printTowers (){

    for (int i=0; i

  • 7/25/2019 Algorithms and Data Structures I

    6/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 6/20

    towers = new Stack [3];

    So, should this instead have been

    towers = new Stack [3];

    as one might intuitively expect?Unfortunately, that results in a compilation error.The reason is quite esoteric and has to do with the gory details of generic typesare handled in Java.Essentially, it boils down to this: a declaration like

    towers = new Stack [3];

    is unsafe because one can assign non-Integer stacks to towers, which is why the compiler doesn't allow it.Thus, we are left with using the slightly less unsafe

    towers = new Stack [3];

    which the compiler allows but warns against.Generic types in Java constitute a large and somewhat advanced topic, too complicated for this course.

    An application: disk backup schedules

    Suppose we have four disks (A, B, C and D) on which we wish to perform backups each day.=> This is a different meaning of disk(hard drive, removable media)

    Each day we need to choose a disk on which to backup=> The backup schedule.

    One option is to go round-robin

    Day 0 use disk ADay 1 use disk BDay 2 use disk CDay 3 use disk DDay 4 use disk ADay 5 use disk B...(and so on)

    With this system, we can only go backat most four days.We are not able to retrieve the state of the system from, say, 6 days ago.

    Another approach: stagger the disks:

    Day 0 use disk ADay 1 use disk BDay 2 use disk ADay 3 use disk CDay 4 use disk ADay 5 use disk BDay 6 use disk ADay 7 use disk DDay 8 use disk A...(and so on)

    With this approach, we'd have a distribution like Disk A 1 or 2 days before

    Disk B at most 4 days beforeDisk C at most 8 days beforeDisk D at most 16 days before

  • 7/25/2019 Algorithms and Data Structures I

    7/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 7/20

    This distribution allows a deeper reach into the past.

    The sequence above is exactly the Tower-of-Hanoi move sequence for disks numbered A, B, C and D.

    Recursion: a review

    First, let's review a simple example from Module 4:

    Recall the recursive computation ofpower:

    // Compute ab

    static int power (int a, int b){

    // Bottomout:if (b == 0) {

    return 1;}

    // Recursion:return (a * power (a, b1));

    }

    What's important:We must test for the bottom out case beforerecursing.The bottom-out case tests the value (or values) of theparameter(or parameters) that changes in therecursion.

    => These are the parameters that controlthe recursion.The recursive calls must change (usually decrease) the parameters that control the recursion.

    => Above, there is only one recursive call, but Tower of Hanoi has two.

    How recursion works:Each method call creates an activation recordon the system stack.The activation record consists of space allocated for the particular parameters and local variables for thatmethod call.Thus, a recursive call results in a new activation record for each such recursive call.

    => This is why each execution of the method retains its own parameters and local variables.The activation record also knows that when execution of a method call completes, execution returns to thecalling method just after the called-method was called.For recursion it's no different except that it's the same code involved in all method calls.

    The best way to understand recursion initially is to draw the stack picture and work through an example.

    Let's now review another example from Module 4:

    This is the permutation seating example where we print the arrangement:

    // The parameters to this method are:// numSpaces total number of remaining seats available// numRemaining total number of people left to seat// seats an array where seats[i]==0 if seat is available// person which person we need to seat

    static void printPermutations (int numSpaces, int numRemaining, int[] seats, int person)

    {// Bottomout case.if (numRemaining == 0) {

    // Print.System.out.println ( Arrays.toString(seats) );return;

    }

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module4/module4.html
  • 7/25/2019 Algorithms and Data Structures I

    8/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 8/20

    // Otherwise, nonbase case: look for an empty spot for "person"for (int i=0; i < seats.length; i++) {

    if (seats[i] == 0) {

    // Empty spot.seats[i] = person;

    // Recursively assign remaining, starting with person+1printPermutations (numSpaces1, numRemaining1, seats, person+1);

    // Important: we need to undo the seating for other trials.seats[i] = 0;

    }} //endfor

    }

    What's similar about this recursive method:The parameter that controls recursion is: numSpaces

    => This is what we check in the bottom-out case.Other parameters pass along information: personand seats.

    What's different (from the power()example):

    When we fill in the seatsarray, we un-dothis assignment after the recursive call:

    // Make some change to a parameter:seats[i] = person;

    // Recurse:printPermutations (numSpaces1, numRemaining1, seats, person+1);

    // Undo the change (restore the original) for future recursions:seats[i] = 0;

    Two types of recursion:

    Recursion where you don't un-do changes.=> Sometimes this is called tail recursion.

    Recursion where you need to un-do changes so that you can properly explore all possibilities=> Sometimes this is called recursion with backtracking.

    Many examples of tail-recursion can easily be written non-recursively (using iteration)=> For many of these examples (power, factorial, fibonacci), it's better to use iteration.

    However, when there's backtracking, it can be very difficult to avoid recursion.

    Maze construction and traversal: a (long) example of recursion with backtracking

    We will now examine a maze application in detail to illustrate recursion with backtracking:

    First, we will use recursion to create a maze like this:

  • 7/25/2019 Algorithms and Data Structures I

    9/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 9/20

    Then, we will use recursion to solve such a maze (by finding a path from a given start cell(e.g., the topleft) to agiven end cell(e.g., the bottom right):

    We will put a whole bunch of useful code in a class called Mazeand instead focus on usingthat class through itsmethods.

  • 7/25/2019 Algorithms and Data Structures I

    10/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 10/20

    We'll use this cell-numbering convention:Rows start numbering at 0, and increase downwards.Columns start at 0 and increase rightwards.

    Thus, the topleft cell is (0,0). The one to its right is (0,1) and the one directly below is (1,0).

    To start with, let's examine the methods in the class Maze:

    The constructor: we create an instance by specifying the number of rows and columns:

    Maze maze = new Maze (5, 5);

    Our examples will use square mazes.

    display(): call this method to display the maze:

    Maze maze = new Maze (5, 5);

    // Bring up the GUI:maze.display ();

    breakWall():Initially, the maze consists of complete cells. To actually create a walkable maze, we will "break walls"

    between neighboring cells.For example, we can break the wall between cells (3,4) and (2,4) as follows:

    Maze maze = new Maze (5, 5);

    // Note the use of the Coord class:Coord c1 = new Coord (3,4);Coord c2 = new Coord (2,4);maze.breakWall (c1, c2);

    maze.display ();

    Or, alternatively, with shorter code:

    Maze maze = new Maze (5, 5);

    // Create an instance directly in the method argument list:maze.breakWall (new Coord(3,4), new Coord (2,4));

    maze.display ();

    The Coordclass looks like this:

    public class Coord {

    public int row=1, col=1;

    public Coord (int r, int c){

    row = r;col = c;

    }

    public String toString (){

    return "[" + row + "," + col + "]";}

    }

    Thus, to access the stored coordinates:

    Coord c = new Coord (3,4);System.out.println ("Row=" + c.row + " Col=" + c.col);

  • 7/25/2019 Algorithms and Data Structures I

    11/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 11/20

    // Or, to print both using toString():System.out.println (c);

    A number of methods (in the class Maze) allow us to mark and un-mark cells as visited:markVisited (Coord c): mark cell cas visited.markUnVisited (Coord c): mark cell cas not visited.markAllUnvisited(): mark all cells as unvisited.isVisited (Coord c): see whether cell chas been visited.

    A number of methods related to fetching a list (array) of a cell's neighbors:Coord[] getUnvisitedClosedNeighbors (Coord c): get cell c's neighbors that are closed off from c(there'sa wall between) and haven't been visited yet.Coord[] getClosedNeighbors (Coord c): get neighbors of cthat share a wall (unbroken) with cwhethervisited or not.Coord[] getUnvisitedOpenNeighbors (Coord c): get those neighbors of cfor which we can walk throughto the neighbor (no wall) and which haven't been visited.

    Each of these methods return an arrayof Coordinstances.

    A few additional useful methods:copy(): make a full copy of the maze, as in:

    Maze m = new Maze (5,5);// ... do stuff: break walls etc ...Maze m2 = m.copy();// Now m2 is a copy of m. Making changes to m won't affect m2.

    setSolutionPath (LinkedList solutionPath): we will call this method once we have constructeda solution.

    We'll now write our first attempt:

    We will try to generate a maze path of a given path length.The key idea:

    Algorithm: recursiveGenerate (Coord c)1. if pathlength has been reached2. return

    // Otherwise ...3. c' = Pick a random neighbor of cell c4. recursiveGenerate (c')

    Here's the program: (source file)

    public class MazeMaker {

    // These variables will be used across methods:static int desiredPathLength;static Maze maze;

    public static void main (String[] argv){

    generateMaze (5, 5);}

    public static void generateMaze (int numRows, int numCols){

    maze = new Maze (numRows, numCols);

    // We want to generate a path of this length:desiredPathLength = numRows * numCols;

    // Initially, we'll start with the top left cell and mark that as visited.Coord start = new Coord (0, 0);maze.markVisited (start);

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/MazeMaker.java
  • 7/25/2019 Algorithms and Data Structures I

    12/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 12/20

    // Generate the maze path recursively.boolean found = recursiveGenerate (start, 1);

    if (! found) {System.out.println ("Could not create the whole maze");

    }maze.display();

    }

    static boolean recursiveGenerate (Coord c, int pathLength){

    // Bottom out condition 1: if (pathLength == desiredPathLength) {

    // Done. We've create a maze path of the desired length.return true;

    }

    // Bottom out condition 2: see if there's a neighbor to visit.Coord[] validNeighbors = maze.getUnvisitedClosedNeighbors (c);if ((validNeighbors == null) || (validNeighbors.length == 0)) {

    // There's no neighbor whose wall we can break. So quit trying.return false;

    }

    // Otherwise, pick a random neighbor and proceed.int i = UniformRandom.uniform (0, validNeighbors.length1);

    // Break the wall and mark the neighbor as visited.maze.breakWall (c, validNeighbors[i]);maze.markVisited (validNeighbors[i]);

    // Recurse.return recursiveGenerate (validNeighbors[i], pathLength+1);

    }

    }

    Next attempt:

    Simple tail recursion can get stuck.=> We need a way to "un-do" paths and try different neighbors.

    We will pick a neighbor to explore recursively=> If that doesn't work out, we'll try another.

    Here's the program: (source file)

    public class MazeMaker2 {

    static int desiredPathLength;static Maze maze;

    public static void main (String[] argv){

    generateMaze (5, 5);}

    public static void generateMaze (int numRows, int numCols){

    maze = new Maze (numRows, numCols);

    desiredPathLength = numRows * numCols;

    // Initially, we'll start with the top left cell.Coord start = new Coord (0, 0);maze.markVisited (start);

    // Generate the maze path recursively.

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/MazeMaker2.java
  • 7/25/2019 Algorithms and Data Structures I

    13/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 13/20

    boolean found = recursiveGenerate (start, 1);

    if (! found) {System.out.println ("Could not create the whole maze");

    }maze.display();

    }

    static boolean recursiveGenerate (Coord c, int pathLength){

    // Bottom out condition 1: if (pathLength == desiredPathLength) {

    return true;}

    // Bottom out condition 1: see if we're stuck.Coord[] validNeighbors = maze.getUnvisitedClosedNeighbors (c);if ((validNeighbors == null) || (validNeighbors.length == 0)) {

    return false;}

    // Otherwise, we have some neighbors to explore.// Permute the directions randomly.

    permute (validNeighbors);

    for (int i=0; i < validNeighbors.length; i++) {

    // Try neighbor i.maze.breakWall (c, validNeighbors[i]);maze.markVisited (validNeighbors[i]);

    boolean ok = recursiveGenerate (validNeighbors[i], pathLength+1);if (ok) {

    // If neighbor i worked out, great.return true;

    }

    // Otherwise, undo assignment to i.

    maze.fixWall (c, validNeighbors[i]);maze.markUnvisited (validNeighbors[i]);

    } // endfor

    // Couldn't make it work.return false;

    }

    static void permute (Coord[] coords){

    for (int i=0; i < coords.length; i++) {// Find a random element to place into ith place.

    int k = (int) UniformRandom.uniform (i, coords.length1);Coord temp = coords[i];coords[i] = coords[k];

    coords[k] = temp;}

    }

    }

    Note:

    The key idea is to try as many neighbors as needed:

    Algorithm: recursiveGenerate (Coord c)

    1. if pathlength has been reached2. return true3. endif

    // Otherwise ...4. for each neighbor c' of c5. found = recursiveGenerate (c')6. if found

  • 7/25/2019 Algorithms and Data Structures I

    14/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 14/20

    7. return true8. endif9. endfor

    // We reach here only if every neighbor did not work out. 10. return false

    Note how we un-do a broken wall:

    for (int i=0; i < validNeighbors.length; i++) {// Try neighbor i.

    maze.breakWall (c, validNeighbors[i]);maze.markVisited (validNeighbors[i]);

    // ... recursion here ...

    // To undo: repair the wall and mark as UNvisited.maze.fixWall (c, validNeighbors[i]);maze.markUnvisited (validNeighbors[i]);

    } // endfor

    A maze with choices:

    The above maze path has no choices=> There's only one way from the topleft to the end of the path.

    We will build upon this to create a maze with choices=> After generating the long path, we'll break some random walls.

    Here's the program: (source file)

    public class MazeMaker3 {

    // ... variables ...

    public static void main (String[] argv){

    generateMaze (5, 5);}

    public static void generateMaze (int numRows, int numCols){

    // ... create and generate singlepath maze ...

    // Break a few more walls, randomly.breakRandomWalls (maze, 10);

    maze.display();}

    static boolean recursiveGenerate (Coord c, int pathLength){

    // ... same as before ...}

    static void breakRandomWalls (Maze maze, int numWalls){

    for (int k=0; k < numWalls; k++) {// Create random coordinates, i.e., identify a random cell.int x = UniformRandom.uniform (0, maze.numRows1);int y = UniformRandom.uniform (0, maze.numCols1);Coord c = new Coord (x,y);

    // Get its neighbors that are separated by a wall.Coord[] validNeighbors = maze.getClosedNeighbors (c);if (validNeighbors != null) {

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/MazeMaker3.java
  • 7/25/2019 Algorithms and Data Structures I

    15/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 15/20

    // Pick one and break the wall.int m = UniformRandom.uniform (0, validNeighbors.length1);maze.breakWall (c, validNeighbors[m]);

    }}

    }

    }

    Let's turn our attention to solving the maze:

    We'll first try a simple idea:

    Algorithm: recursivelyFindPath (Coord c)1. if c is the end2. return true3. endif4. // Otherwise search further ...5. c' = pick a random "open" neighbor6. if c' is null

    // Stuck: couldn't find a path.7. return false8. endif

    9. return recursivelyFindPath (c')

    Here's the program: (source file)

    import java.util.*;

    public class MazeMaker4 {

    static int desiredPathLength;static Maze maze;

    public static void main (String[] argv)

    {Maze maze = generateMaze (5, 5);if (maze != null) {

    // We will seek a path from the topleft to the bottomright corner.Coord start = new Coord (0,0);Coord end = new Coord (4,4);solveMaze (maze, start, end);maze.display ();

    }else {

    System.out.println ("Maze creation did not work");}

    }

    // A path is a list of cells, i.e., a list of Coord instances. static LinkedList solutionPath;

    static void solveMaze (Maze maze, Coord start, Coord end){

    // We'll mark visited cells as we go along our path. Initially:maze.markAllUnvisited ();

    // Mark the start cell as visited.maze.markVisited (start);

    // Create the list.solutionPath = new LinkedList ();

    // Recursively find the path and fill in coord's into the list.recursivelyFindPath (maze, start, end);

    // Pass the path into the GUI.maze.setSolutionPath (solutionPath);

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/MazeMaker4.java
  • 7/25/2019 Algorithms and Data Structures I

    16/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 16/20

    }

    static boolean recursivelyFindPath (Maze maze, Coord c, Coord end){

    // First add the current cell into the path.solutionPath.add (c);

    // If we've reached the end, we're done.if ( (c.row == end.row) && (c.col == end.col) ) {

    return true;

    }

    // Otherwise, let's find a neighbor to explore.Coord[] validNeighbors = maze.getUnvisitedOpenNeighbors (c);

    if (validNeighbors == null) {// If there aren't any, we're done, but couldn't find a path.return false;

    }

    // Take the first one and explore from there.maze.markVisited (validNeighbors[0]);

    return recursivelyFindPath (maze, validNeighbors[0], end);

    }

    // ... Maze generation code ... same as before ...

    }

    A solution that works:

    When exploring a neighbor, we need a way to un-do (backtrack) if it doesn't lead to a solution.

    The idea is to try all possible neighbors, as many as needed:

    Algorithm: recursivelyFindPath (Coord c)1. if c is the end2. return true3. endif4. // Otherwise search further ...5. for each "open" neighbor c'6. found = recursivelyFindPath (c')7. if found8. add c' to path9. return true10. endif10. endfor11. return false

    Here's the program: (source file)

    import java.util.*;

    public class MazeMaker5 {

    // ... variables ... same as before (for generation) ...

    public static void main (String[] argv){

    // ... same ...

    }

    // A path is a list of cells, i.e., a list of Coord instances. static LinkedList solutionPath;

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/MazeMaker5.java
  • 7/25/2019 Algorithms and Data Structures I

    17/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 17/20

    static void solveMaze (Maze maze, Coord start, Coord end){

    // We'll mark visited cells as we go along our path. Initially:maze.markAllUnvisited ();

    // Mark the start cell as visited.maze.markVisited (start);

    // Create the list.solutionPath = new LinkedList ();

    // Recursively find the path and fill in coord's into the list.recursivelyFindPath (maze, start, end);

    // The start node gets added last. Why?solutionPath.addFirst (start);

    // Pass the path into the GUI.maze.setSolutionPath (solutionPath);

    }

    static boolean recursivelyFindPath (Maze maze, Coord c, Coord end){

    // If we've reached the end, we're done.

    if ( (c.row == end.row) && (c.col == end.col) ) {return true;}

    // Otherwise, let's find a neighbor to explore.Coord[] validNeighbors = maze.getUnvisitedOpenNeighbors (c);if (validNeighbors == null) {

    // If we couldn't find any neighbors to explore, we're stuck.return false;

    }

    // Try each neighbor, as many as needed.

    for (int i=0; i < validNeighbors.length; i++) {

    // Try neighbor i.maze.markVisited (validNeighbors[i]);boolean found = recursivelyFindPath (maze, validNeighbors[i], end);

    if (found) {// Notice that we add this to the front of the list, and only // after the solution has been found up to here. solutionPath.addFirst (validNeighbors[i]);return true;

    }

    // If neighbor i didn't work out, undo the visit and continue.maze.markUnvisited (validNeighbors[i]);

    }

    // If we reached here, then none of the neighbors worked out.return false;

    }

    }

    The N-Queens problem

    In this problem:

    We are given anN x Nchessboard andMqueens (whereM

  • 7/25/2019 Algorithms and Data Structures I

    18/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 18/20

    For example (N=8):

    Recall that a queen can attack any square in its row, column or diagonal, e.g.

    We will of course solve this recursively:

    This is another example of recursion with backtracking.

    Key ideas:

    We'll proceed columm-by-column (left to right).When solving for the current column, try to place a queen on each possible row, and solve the sub-

    problem (starting with the next column) recursively.

    Pseudocode:

    Algorithm: solve (n, c)Input: n = the number of queens to assign, c = the column to start with

    // ... Bottomout conditions ... (we'll do this later)

    1. for each row r2. if [r,c] is a nonattacked square3. place queen n on cell [r,c]

    4. found = solve (n1, c+1) // Assign remaining n1 recursively5. if (found)6. return true7. endif8. endif9. endfor

  • 7/25/2019 Algorithms and Data Structures I

    19/20

    1/15/2016 Algorithms and Data Structures I

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/module9.html 19/20

    // We reach here if none of the rows in column c worked out. 10. return false

    Thus, we try every row for a queen and try to solve the sub-problem with fewer queens.=> If if that gets solved, we can place the queen there and recurse back up

    The bottom-out conditions:Check if there are no more queens to assign

    => Problem gets solved.

    Check if there are no more columns to try=> No solution exists.

    We will build a class called ChessBoardwith methods that hide most of the messy board-manipulation details:addQueen (row,col): add a queen in square [row,col].removeQueen (row,col): remove the queen in square [row,col].boolean isForbidden (row,col): see if [row,col] is an attacked square.display(): to bring up a GUI with the board displayed.

    Here's the program: (source file)

    import java.awt.*;import java.util.*;

    public class NQueens {

    static ChessBoard board;

    public static void main (String[] argv){

    solveQueens (8, 8);}

    static void solveQueens (int numQueens, int size){

    board = new ChessBoard (size);

    boolean solutionExists = solve (numQueens, 0);

    if (! solutionExists) {System.out.println ("No solution for " + numQueens + " on a " + size + " x " + size + " board");return;

    }

    System.out.println ("Solution found for " + numQueens + " on a " + size + " x " + size + " board");

    System.out.println (board);board.display();

    }

    static boolean solve (int numQueens, int whichCol){

    // Bottomout condition 1: if (numQueens == 0) {

    // None to assign done.return true;

    }

    // Bottomout condition 2: if (whichCol >= board.size()) {

    // No columns left to try done.return false;

    }

    // Try every unforbidden spot in each row.for (int row=0; row < board.size(); row++) {

    if ( ! board.isForbidden(row,whichCol) ) {

    http://www.seas.gwu.edu/~drum/cs1112/lectures/module9/examples/NQueens.java
  • 7/25/2019 Algorithms and Data Structures I

    20/20

    1/15/2016 Algorithms and Data Structures I

    // Try this location.board.addQueen (row, whichCol);

    boolean solutionExists = solve (numQueens1, whichCol+1);

    if (solutionExists) {return true;

    }

    // Else, undoboard.removeQueen (row, whichCol);

    }

    }

    // Couldn't find a solution.return false;

    }

    }