Data Structures & Algorithms
Stack & Recursion
                            Today‘s lecture
▪ Stacks
   ► Practical applications
        - Stacks in validating expressions
        - Infix to Postfix conversion & evaluation
              - Algorithms implementing stack
                  - To convert any Infix expression into its corresponding Postfix
                    expression
                  - To evaluate any postfix expression
              - Postfix/Prefix forms do not rely on operator priorities, a tie
                breaker,
                or delimiters
                  - Easier evaluation of expressions
                  - Unambiguous expression tree
       - Adding very large integer numbers
▪ Recursion
                                     Lecture 7: Stack & Recursion                    2
        Another stack application example:
            Adding very large integers
▪ Consider adding very large numbers
   ► Since the largest magnitude of integers is limited (32
     bits), so we are not able to perform following:
       18,274,364,583,929,273,748,459,595,684,373
                           +
                8,129,498,165,026,350,236
 because integer variables cannot hold such large values
▪ The problem can be solved if we
  ►   treat these numbers as strings of numerals,
  ►   store the numbers corresponding to these numerals on two
      stacks, and
  ►   then perform addition by popping numbers from the stacks
                           Lecture 7: Stack & Recursion       3
                     Pseudocode
addingLargeNumbers()
  ► Read the numerals of the first number and store the
     numbers corresponding to them on one stack;
  ► Read the numerals of the second number and store
     the numbers corresponding to them on another
     stack;
  ► carry = 0;
  ► while at least one stack is not empty
       - pop a number from each nonempty stack and
         add them to carry;
       - push the unit part on the result stack;
       - store carry in carry;
  ► push carry on the result stack if it is not zero;
  ► pop numbers from the result stack and display
     them;                 Lecture 7: Stack & Recursion   4
Lecture 7: Stack & Recursion   5
                            Today‘s lecture
▪ Stacks
   ► Practical applications
        - Stacks in validating expressions
        - Infix to Postfix conversion & evaluation
              - Algorithms implementing stack
                  - To convert any Infix expression into its corresponding Postfix
                    expression
                  - To evaluate any postfix expression
              - Postfix/Prefix forms do not rely on operator priorities, a tie
                breaker,
                or delimiters
                  - Easier evaluation of expressions
                  - Unambiguous expression tree
       - Adding very large integer numbers
▪ Recursion
                                     Lecture 7: Stack & Recursion                    6
                   Recursive thinking
▪ Recursion is a problem-solving approach that can be
  used to generate simple solutions to certain kinds of
  problems that would be difficult to solve in other ways
▪ Recursion splits a problem into one or more simpler
  versions of itself
  ►   These sub-problems may also be divided into further
      smaller sub-sub-problems
▪ A recursive call is a function call in which the called
  function is the same as the one making the call
                           Lecture 7: Stack & Recursion     7
                   Recursive Definitions
▪ Recursive algorithm
  ►   Algorithm that finds the solution to a given problem by
      reducing the problem to smaller versions of itself
  ►   Has one or more base cases
  ►   Implemented using recursive functions
▪ Recursive function
  ►   Function that calls itself
                              Lecture 7: Stack & Recursion      8
                  Recursive Definitions
▪ Base case
  ►   Case in recursive definition in which the solution is
      obtained directly
  ►   Stops the recursion
▪ General case
  ►   Case in recursive definition in which a smaller version of
      itself is called
  ►   Must eventually be reduced to a base case
                             Lecture 7: Stack & Recursion          9
           Outline of a recursive function
if (answer is known)
    provide the answer                             Base case
else
   make a recursive call to                        Recursive case
   solve a smaller version
   of the same problem
                         Lecture 7: Stack & Recursion               10
              Tracing a recursive function
▪ Recursive function
  ►   May have unlimited copies of itself
  ►   Every recursive call has
        - its own code
        - own set of parameters
        - own set of local variables
▪ After completing recursive call
  ►   Control goes back to calling environment
  ►   Recursive call must execute completely before control
      goes back to previous call
  ►   Execution in previous call begins from point immediately
      following recursive call
                              Lecture 7: Stack & Recursion       11
                How Recursion Works?
▪ Recursive function call is handled like any other
  function call
▪ Each recursive call has an activation record or a stack
  frame
  ►   Stores values of parameters and local variables
▪ When base case is reached, return is made to previous
  call
  ►   Recursion “unwinds”
                            Lecture 7: Stack & Recursion    12
                   Factorial definition
4! = 4 * 3 * 2 * 1
n! = n * (n-1) * … * 1
4! = 4 * 3!
n! = n * (n-1)!
factorial(n) = n * factorial(n-1)              // recursive step
factorial(1) = 1                               // base step
     1                𝑖𝑓𝑛 = 0
n! =
     𝑛∗𝑛−1!           𝑖𝑓 𝑛 > 0
                            Lecture 7: Stack & Recursion           13
                 Recursive factorial function
int fact(int num)
{
    if(num == 0)
     return 1;
    else
     return num * fact(num – 1);
}
                            Lecture 7: Stack & Recursion   14
    Recursive factorial function                              fact(4) = 24
                                   num = 4;
                                        since num != 0       return = 4 * 6
int fact(int num)                       return 4*fact(3);
                                     fact(3)                 fact(3) = 6
{
                                     num = 3;                 return = 3 * 2
    if(num == 0)                          since num != 0
     return 1;                            return 3*fact(2);
    else                               fact(2)                  fact(2) = 2
                                       num = 2;                  return = 2 * 1
     return num * fact(num – 1);            since num != 0
}                                           return 2*fact(1);
                                         fact(1)                   fact(1) = 1
                                          num = 1;                 return = 1 * 1
Recursive factorial trace                    since num != 0
                                             return 1*fact(0);
                                            fact(0)                 fact(0) = 1
                                            num = 0;                 return = 1
                                               since num == 0
                     15
                                               return 1;
               Another recursive definition
Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, ...
                  0                                           𝑓𝑜𝑟 𝑛 == 0
                  1                                           𝑓𝑜𝑟 𝑛 == 1
     𝑓𝑖𝑏 𝑛 =
                  𝑓𝑖𝑏 𝑛 − 2        + 𝑓𝑖𝑏 𝑛 − 1               𝑓𝑜𝑟 𝑛 > 2
  fib(n) = fib(n-1) + fib(n-2)                        Recursive case
  fib(0) = 0                                          Base case
  fib(1) = 1
                           Lecture 7: Stack & Recursion                    16
                 Recursive Fibonacci repeats computations
int fib (int n)
{
        if (n == 0) {
            return 0; }
        else if (n == 1) {
           return 1; }
        else {
           return fib (n-1) + fib (n-2); }
    }
                                             Lecture 7: Stack & Recursion   17
              Evaluating Exponents Recursively
double power (double x, unsigned int n) // raise x to the power n
{
if (n == 0)
      return 1.0;
    else
      return x * power(x,n-1);
}
Using this definition, the value of x4 can be computed in the
following way:
    x4 = x · x3 = x · (x · x2)    = x · (x · (x · x1))              = x · (x · (x · (x · x0)))
       = x · (x · (x · (x · 1))) = x · (x · (x · (x))) = x · (x · (x · x))
       = x · (x · x · x)= x · x · x · x
                                     Lecture 7: Stack & Recursion                                18
                                     Implementation using loop
       The function power() can also be implemented
       differently, without using any recursion, as in the
       following loop:
       double nonRecPower(double x, unsigned int n) {
       double result = 1;
       for (result = x; n > 1; --n)
               result *= x;
                      return result;
       }
                     Do we gain anything by using recursion
                     instead of a loop?
M. Shahzad: Data Structures & Algorithms       Lecture 7: Stack & Recursion   19
                                           Recursion or iteration?
        ▪ The recursive version seems to be more intuitive
          because it is similar to the original definition of the
          power function. The definition is simply expressed in
          C++ without losing the original structure of the
          definition
        ▪ The recursive version increases program
          readability, improves self-documentation, and
          simplifies coding
        ▪ In the example, the code of the nonrecursive version is
          not substantially larger than in the recursive version,
          but for most recursive implementations, the code is
          shorter than it is in the non-recursive implementations
M. Shahzad: Data Structures & Algorithms           Lecture 7: Stack & Recursion   20
                                           Recursive vs iteration
        ▪ In iteration, a loop repetition condition determines
          whether to repeat the loop body or exit from the loop
        ▪ In recursion, the condition usually tests for a base case
        ▪ You can always write an iterative solution to a problem
          that is solvable by recursion
        ▪ Recursive code may be simpler than an iterative
          algorithm and thus easier to write, read, and debug
M. Shahzad: Data Structures & Algorithms           Lecture 7: Stack & Recursion   21
                When to use recursion?
▪ If recursive and iterative algorithms are of similar
  efficiency
  ►   Prefer iteration over recursion
▪ If the problem is inherently recursive and a recursive
  algorithm is less complex to write than an iterative
  algorithm
  ►   Prefer recursion over iteration
                             Lecture 7: Stack & Recursion   22
                 Efficiency of recursion
▪ Recursive methods often have slower execution times
  when compared to their iterative counterparts
  ►   Because the overhead for loop repetition is smaller than
      the overhead for a method call and return
▪ If it is easier to conceptualize an algorithm using
  recursion, then you should code it as a recursive
  method
  ►   Because sometimes the reduction in efficiency does not
      outweigh the advantage of readable code that is easy to
      debug
                            Lecture 7: Stack & Recursion         23
                  Pitfalls of recursion
▪ One pitfall of recursion is infinite regress, i.e. a chain of
  recursive calls that never stops
  ►   E.g. if you forget the base case. Make sure you have
      enough base cases
▪ Recursion can be less efficient than an iterative
  implementation. This can happen because there is
  recalculation of intermediate results
▪ There can be extra memory use because recursive
  calls must be stored on the execution stack
                            Lecture 7: Stack & Recursion          24
    Function calls & recursive implementation
▪ The state of each function, including main(), is characterized
   ► by the contents of all local variables,
   ► by the values of the function’s parameters, and
   ► by the return address indicating where to restart in the
     calling function
▪ The data area containing all this information is called an
  activation record or a stack frame and is allocated on the
  run-time stack
   ► exists for as long as a function owning it is executing
▪ This record is a private pool of information for the function, a
  repository that stores all information necessary for its proper
  execution and how to return to where it was called from
                             Lecture 7: Stack & Recursion            25
           Activation Record / Stack Frame
▪ If a function is called either by
  main() or by another function,
  then its stack frame is created
  on the run-time stack
▪ The run-time stack always
  reflects the current state of the
  function
  ►   E.g., suppose that main() calls
      function f1(), f1() calls f2(), and
      f2() in turn calls f3(). If f3() is
      being executed, then the state
      of the run-time stack is as
      shown
                               Lecture 7: Stack & Recursion   26
           Activation Record / Stack Frame
▪ An activation record usually contains the following
  information
  ►   Values for all parameters to the function, location of the first
      cell if an array is passed or a variable is passed by reference,
      and copies of all other data items;
  ►   Local variables that can be stored elsewhere, in which case,
      the activation record contains only their descriptors and
      pointers to the locations where they are stored;
  ►   The return address to resume control by the caller, the
      address of the caller’s instruction immediately following the
      call;
  ►   A dynamic link, which is a pointer to the caller’s activation
      record;
  ►   The returned value for a function not declared as void.
      Because the size of the activation record may vary from one
      call to another, the returned value is placed right above the
      activation record of the caller
                               Lecture 7: Stack & Recursion              27
               Evaluating Exponents Recursively
double power (double x, unsigned int n) /* 102 */
{
if (n == 0)                                           /* 103 */
        return 1.0;                                   /* 104 */
      else
        return x * power(x,n-1);                       /* 105 */
}
int main()
{ ...
             y = power(5.6,2);                        /* 136 */
...
}
                                   Lecture 7: Stack & Recursion    28
           First, the value of the second argument, i.e., 2, is checked,
           and power() tries to return the value of 5.6 * power(5.6,1)
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion        29
           Again first, the value of the second argument, i.e., 1, is checked,
           and power() tries to return the value of 5.6 * power(5.6,0)
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion              30
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion   31
                                                               Again, first, the value of the
                                                               second argument, i.e., 0, is
                                                               checked, and power()
                                                               returns the value 1
                                                               At this point, there are two
                                                               pending calls on the run-time
                                                               stack—the calls to power()—
                                                               that have to be completed
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion                             32
                                                                      5.6 * power(5.6,0)
                                                                      = 5.6 * 1 = 5.6 is
                                                                      computed
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion                33
                                                                      5.6 * power(5.6,0)
                                                                      = 5.6 * 1 = 5.6 is
                                                                      computed
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion                34
                                                                      5.6 * power(5.6,1)
                                                                      = 5.6 * 5.6 = 31.36
                                                                      is computed
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion                   35
                                                                      5.6 * power(5.6,1)
                                                                      = 5.6 * 5.6 = 31.36
                                                                      is computed
double power (double x, unsigned int n) { /* 102 */
if (n == 0)                               /* 103 */
      return 1.0;                         /* 104 */
else
      return x * power(x,n-1); }          /* 105 */
                                       Lecture 7: Stack & Recursion                   36