420 likes | 972 Views
Applications of Recursion. (Walls & Mirrors - Chapter 5). Overview. Writing a Linked List Backwards Recursive Insert N Queens Problem Recognizing Simple Languages Infix, Prefix, and Postfix Expressions Translating Prefix Expressions into Postfix. Writing a Linked List Backwards.
E N D
Applications of Recursion (Walls & Mirrors - Chapter 5)
Overview • Writing a Linked List Backwards • Recursive Insert • N Queens Problem • Recognizing Simple Languages • Infix, Prefix, and Postfix Expressions • Translating Prefix Expressions into Postfix
Writing a Linked List Backwards For a singly-linked list, this is difficult to do iteratively, but easy to do recursively.
struct ListNode { int value; ListNode *next; }; ListNode *head; void writeBackwards( ListNode *pnode ) { if( pnode != NULL ) { writeBackwards( pnode -> next ); cout << pnode -> value << endl; } } 13 21 34 head pnode Writing a Linked List Backwards (Cont’d.) writeBackwards( head );
21 34 head Recursive Insert Let’s reconsider insertion into a sorted list -- recursively! insert( &head, 8 ); insert( &head, 13);
void insert( ListNode **pnode, const int newValue ) { if( *pnode = = NULL | | newValue < (*pnode) -> value ) { ListNode *newNode = new ListNode; newNode -> value = newValue; newNode -> next = *pnode; *pnode = newNode; } else . . . } 21 34 head 8 8 pnode newValue Recursive Insert: At Head of List
void insert( ListNode **pnode, const int newValue ) { if( *pnode = = NULL | | newValue < (*pnode) -> value ) { ListNode *newNode = new ListNode; newNode -> value = newValue; newNode -> next = *pnode; *pnode = newNode; } else insert( &( (*pnode) -> next), newValue ); } 8 21 34 head 13 pnode newValue Recursive Insert: After Head of List
Recursive Insert: Summary This code for recursive insertion into a singly-linked list has about 1/3 the number of statements required for the non-recursive solution. (6 statements vs. 17 statements)
N Queens Problem • 8 Queens Problem: Given an 8 x 8 chessboard, place 8 queens on the board so that no queen can attack any other. • Consider a generalization of this with N queens and a board of N x N squares. • We shall describe a way to solve this problem using a technique called backtracking.
Backtracking • Backtracking is a problem-solving strategy that, when it reaches an impasse, retraces its steps in reverse order before trying a new sequence of steps. • This is like organized, trial and error: • The same thing is never considered twice, and • If the domain of possible solutions is finite, we ensure that we eventually try all possible solutions.
Q Queen’s Possible Attacks A queen in chess can attack horizontally (left and right), vertically (up and down), and on both diagonals.
Q Q Q Q Q Q Q Q Q Q Q Q N Queens: N = 2 A search through all possible configurations yields no solution!
Q Q Q Q N Queens: N = 4 • One possible solution for N = 4 looks like this. • Observation: To place N queens on an N x N board so that none is under attack, each queen must be in its own row and column.
N Queens: Solution Strategy 1) Start with a queen in the 1st row and 1st column. 2) Place the 2nd queen in the 2nd column, at the first row where this queen will not be under attack. 3) Repeat step (2) with additional queens until the problem is solved or, it is not possible to place a queen in a column without being attacked. 4) If step (3) failed to place a queen in a column, then backup to the previous column and move that queen to the next, unattacked row. 5) Repeat steps (3) and (4) until the problem is solved or, there are no more possibilities for placing a queen in the 1st column. In this case, there is no solution.
Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Solving N Queens with N = 3 Nowhere to place 3rd queen: need to backtrack! 2 queens successfully placed
Q Q Q Solving N Queens with N = 3 (Cont’d.) Since the queen in the 2nd column is in the last row, there are no more alternatives for placing it. We need to backtrack to the 1st column:
Q Q Q Q Q Q Solving N Queens with N = 3 (Cont’d.) Nowhere to place 2nd queen: need to backtrack to the 1st column again!
Q Q Q Q Q Q Q Q Q Q Q Solving N Queens with N = 3 (Cont’d.) Nowhere to place 3rd queen: need to backtrack to 2nd column again.
Q Q Q Q Solving N Queens with N = 3 (Cont’d.) • Nowhere to place 2nd queen: need to backtrack to 1st column. However, since that queen is in the last row, there are no more possibilities to consider. • No solution to N Queens problem with N = 3.
Q Q Q Q Q Q Q Q Q Solving N Queens with N = 4 A solution to the N Queens problem with N = 4
Q Q Q Q Q Q Q Q Solving N Queens with N = 4 • By reflecting the preceding solution about its horizontal or vertical axis, we find an additional, alternative solution. • If the preceding algorithm is allowed to continue, it will find this, and all other solutions that may exist.
N Queens: C++ Implementation bool placeQueens( int currColumn ) { if( currColumn > BOARD_SIZE ) return true; bool queenPlaced = false; int row = 1; while( !queenPlaced && row <= BOARD_SIZE ) { if( isUnderAttack( row, currColumn ) ) row++; else { setQueen( row, currColumn ); queenPlaced = placeQueens( currColumn + 1 ); if( !queenPlaced ) { removeQueen( row, currColumn ); row++; } } } return queenPlaced; }
Recognizing Simple Languages • A language is a set of strings of symbols that adhere to the rules of a grammar. • A grammar is a set rules that define a language. • A recognizer is a mechanism that, based on a language’s grammar, determines whether a given string is in the language.
Grammar Notation X | Y means X or Y X Y means X followed by Y <word> means that word is a variable of the grammar. Example: Grammar for non-negative integers: <number> = <digit> | <number> <digit> <digit> = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 means that a number is either a digit or a number followed by a digit. I.e. it is is either one of {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} or a string of symbols from {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}.
Simple Languages: C++ Identifiers A valid C++ identifier is a string of letters and digits, where the first character is a letter, and the underscore _ counts as a letter. Grammar for C++ identifiers: <identifier> = <letter> | <identifier> < letter> | <identifier> < digit> <letter> = a | b | c | . . . | z | A | B | C | . . . | Z | _ <digit> = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Recognizer for C++ Identifiers bool isIdentifier( char str[ ], int strLen ) { if( strLen = = 0 ) return false; if( strLen = = 1 ) return isLetter( str[0] ); if( isLetter( str[ strLen – 1 ] ) | | isDigit( str[ strLen – 1 ] ) ) return isIdentifier( str, strLen – 1 ); else return false; }
Simple Languages: Palindromes • A palindrome is a string that reads the same from left to right as right to left. • NOON, DEED, RADAR, MADAM • ABLE WAS I ERE I SAW ELBA • Grammar for palindromes: <pal> = empty string | <ch> | a <pal> a | b <pal> b | . . . | Z <pal> Z <ch> = a | b | c | . . . | z | A | B | C | . . . | Z
Recognizer for Palindromes bool isPalindrome( char str[ ], int first, int last ) { int StrLen = last – first + 1; if( StrLen = = 0 ) return true; if( StrLen = = 1 ) return isChar( str[0] ); if( str[ first ] ! = str[ last ] ) return false; return isPalindrome( str, first + 1, last – 1 ); }
Simple Languages: AnBn • Consider strings of the form AnBn, for some n 0: empty string n = 0 AB n = 1 AABB n = 2 AAABBB n = 3 . . . • Grammar: <word> = empty string | A <word> B
Recognizer for AnBn bool isAnBn( char str[ ], int first, int last ) { if( last – first < 0 ) // str[ ] contains 0 characters return true; if( str[ first ] = = ‘A’ && str[ last ] = = ‘B’ ) return isAnBn( str, first + 1, last – 1 ); else return false; }
Infix Expressions • An infix expression is an algebraic expression in which each binary operator appears between its two operands. • Examples: a + b a + b * c (a + b) * c • Rules of precedence and parentheses are used to make infix expressions unambiguous.
Prefix and Postfix Expressions • A prefix expression is an algebraic expression in which each binary operator precedes its two operands: + a b + a * b c * + a b c • A postfix expression is an algebraic expression in which each binary operator follows its two operands: a b + a b c * + a b + c *
Grammars for Prefix and Postfix Expressions • Prefix Expression Grammar: <prefixExpr> = <identifier> | <operator> <prefixExpr> <prefixExpr> • Postfix Expression Grammar: <postfixExpr> = <identifier> | < postfixExpr> < postfixExpr> <operator> where <operator> = + | – | * | / <identifier> = a | b | c | . . . | z
Strategy for a Prefix Expression Recognizer 1) If the input string is an identifier, then return true. 2) Otherwise, if the initial part of the string is not an operator followed by a prefix expression, then return false. 3) If the initial part of the string is an operator followed by a prefix expression, then determine if the remainder of the string is a prefix expression. 4) If the remainder of the string is a prefix expression then return true, otherwise return false.
Strategy for a Prefix Expression Recognizer Suppose we had a function int endPrefix( char str[ ], int first, int last ) that, given an str[ ], would return the end of the prefix expression that begins at the first position of str[ ]. Then, str[ ] would consist of a valid prefix expression if the value returned by endPrefix( ) was the same as the end of the string. I.e. end = strlen( str ) – 1; endPrefix( str, 0, end ) = = end
Recognizer for Prefix Expressions int endPrefix( char str[ ], int first, int last ) { if( first < 0 | | first > last ) return –1; if( isLetter( str[ first ] ) ) return first; if( isOperator( str[ first ] ) ) { int firstEnd = endPrefix( str, first + 1, last ); if( firstEnd > –1 ) return endPrefix( str, firstEnd + 1, last ); return –1; } return –1; }
Translating Prefix Expressions into Postfix Recall: <prefixExpr> = <identifier> | <operator> <prefixExpr> <prefixExpr> Example: + a * b c <postfixExpr> = <identifier> | < postfixExpr> < postfixExpr> <operator> Example: a b c * +
Translating Prefix into Postfix (Cont’d.) Therefore, translating prefix into postfix requires translating <operator> <prefixExpr1> <prefixExpr2> into <prefixExpr1> <prefixExpr2> <operator> then <prefixExpr1> into < postfixExpr1> and <prefixExpr2> into < postfixExpr2>
Translating Prefix to Postfix: Example preToPost( “ + / a b – c d ” ) = = preToPost( “ / a b ” ) preToPost( “ – c d ” ) “+” = = preToPost( “a” ) preToPost( “b” ) “/” preToPost( “c” ) preToPost( “d” ) “–” “+” = = “a” “b” “/” “c” “d” “–” ‘+” = = “ a b / c d – + ”
Strategy for a Prefix to Postfix Translator Let’s define a function char *preToPost( char prefixExpr[ ], char postfixExpr[ ] ) that, given a prefix expression in prefixExpr[ ], stores the corresponding postfix expression in postfixExpr[ ]. (We will find it useful to return a pointer to the end of the prefix expression that begins at prefixExpr[0]. ) Now, a valid prefix expression can be translated into postfix as follows: char *postfixExpr = ‘\0’; preToPost( prefixExpr, postfixExpr );
Prefix to Postfix Translator char *preToPost( char prefixExpr[ ], char postfixExpr[ ] ) { if( isLetter( prefixExpr[0] ) ) { strncat( postfixExpr, prefixExpr, 1 ); return prefixExpr; } // else prefixExpr[0] is an Operator char *pend = preToPost( &prefixExpr[1], postfixExpr ); pend = preToPost( pend + 1, postfixExpr ); strncat( postfixExpr, prefixExpr, 1 ); return pend; }