Data Structures and Algorithms Lecture Slides: Programming Languages Implementations of Recursion
Data Structures and Algorithms Lecture Slides: Programming Languages Implementations of Recursion
Computing factorial
Binary search
Merge sort
Activation record
• An activation record is a
data structure that Local variables
contains the following
fields among others: Parameters
• Local variables
• Function parameters Return address
• A return address
void fun1(float r) {
• This program has 3 consecutive int s, t;
function calls: ...
• main() calls fun1() fun2(s);
...
• fun1() calls fun2()
}
• fun2() calls fun3() void fun2(int x) {
• Consequently 4 activation records int y;
will be created consecutively at run ...
time fun3(y);
• One once the program starts to ...
run }
• A second when main arrives at void fun3(int q) {
the instruction that calls fun1() ...
• Third when fun1() arrives at the }
instruction that calls fun2() void main() {
• Finally a fourth one once fun2() float p;
arrives at the instruction that call ...
fun3() fun1(p);
...
}
4 activation records
• The four activation records created as function calls are made consecutively
Local variables
fun3(){ Return
Parameters
to main
Local variables Local variables
fun2() { Parameters fun2() { Parameters
return return
Local variables Local variables Local variables
fun1() { Parameters fun1() { Parameters fun1() { Parameters
Return to main Return to main Return to main
Local variables Local variables Local variables
main()
{ Parameters
return
main() { Parameters
return
main() {
Local variables
Parameters main() { Parameters
return
return
Function calls
void fun1(float r) {
• A function call causes a program int s, t;
to jump to the first instruction of ...
the function it calls fun2(s);
...
• When main calls fun1(), the }
program stops executing void fun2(int x) {
instructions in main and go to int y;
execute instructions in fun1() ...
• Once a function has executed all fun3(y);
its instructions, the program ...
returns to execute instructions }
from the caller void fun3(int q) {
...
• So once fun1() has completed, the }
program will continue to execute void main() {
instructions in main, precisely the float p;
instruction just after the call to ...
fun1() fun1(p);
...
}
Function calls & activation records
void fun1(float r) {
int s, t;
• A program remember where to
...
return at the end of the execution fun2(s);
of a function because it has stored ...
the address of the return }
instruction in the activation void fun2(int x) {
record of the called function int y;
...
fun3(y);
• For example, once fun1() has ...
completed its execution, the }
void fun3(int q) {
return address in main can be
...
found from the activation record }
of fun1() void main() {
float p;
...
fun1(p);
...
}
Activation records, function calls & runtime stack
void fun1(float r) {
• Not only a program remember which int s, t;
instruction it should returns but it ...
also knows which function it should fun2(s);
return thanks to the runtime stack ...
that pile the activation records as
they are created }
void fun2(int x) {
• This program has to execute 3
consecutive function calls before int y;
returning to main() ...
• main() → fun1() fun3(y);
• fun1() → fun2() ...
• fun2() → fun3() }
• Then void fun3(int q) {
• fun3() returns to fun2() ...
• fun2() returns to fun1() }
• fun1() returns to main() void main() {
• The program has to remember all float p;
these function calls and returns, the ...
runtime stack take care of this fun1(p);
...
}
Runtime stack
• Here the program first calls fun1(), then fun1() call fun2() which then call fun3()
• Each call adds one activation record on top of the runtime stack
Local variables
fun2()
{ Parameters
• Runtime stack
Return to fun1
Local variables Local variables
fun1()
{ Parameters
Return to main
fun1()
{ Parameters
Return to main
main()
{ Local variables
Parameters
return
main()
{ Local variables
Parameters
return
main()
{ Local variables
Parameters
return
Runtime stack
• The function currently running is always the one for which the activation record is
on top of the runtime stack
• Once that function returns
• the corresponding activation record is popped out of the runtime stack
• the function corresponding to the new top of the stack resumes execution
{
Local variables
fun3() Parameters
Return to fun2
Local variables Local variables
fun2()
{ Parameters
Return to fun1
fun2()
{ Parameters
Return to fun1
Local variables Local variables
fun1()
{ Parameters
Return to main
fun1()
{ Parameters
Return to main
fun1()
{
Local variables
Parameters
Return to main
main()
{ Local variables
Parameters
Return address
main()
{ Local variables
Parameters
Return address
main()
{ Local variables
Parameters
Return address
Runtime stack,function arguments and local variables
• Not only the return address is pushed on the stack, also the function
arguments and local variables
int y
fun2()
{ int x
Return to fun1
int s, t int s, t
fun1()
{ float r
Return to main
fun1()
{ float r
Return to main
main()
{ float p
Parameters
return
main()
{ float p
Parameters
return
main()
{ float p
Parameters
return
Recursive function for factorial
A recursive function is one that makes a call to itself inside its own
code
Example factorial : n × n − 1 × n − 2 × . . . × 2 × 1
factorial (n)
if (n ≤ 1)
return 1 ;
else
return n × factorial(n − 1) ;
#include<stdio.h>
int fact(int n);
int main() {
int n;
printf("Enter a positive integer: ");
scanf("%d",&n);
printf("Factorial of %d = %ld", n, fact(n));
return 0;
}
int fact(int n) {
if (n<=1)
return 1;
else
return n*fact(n-1);
}
Runtime stacks for n = 7
fact(5) { int n = 5
Return to fact()
fact(6) { int n = 6
Return to fact()
fact(6) { int n = 6
Return to fact()
fact(7) { int n = 7
Return to main()
fact(7) { int n =7
Return to main()
fact(7) { int n = 7
Return to main()
main() { int n =7
Return main() { int n = 7
Return
main() { int n =7
Return
main() { int n = 7
Return
Runtime stacks for n = 7
fact(1) { int n = 1
Return to fact()
fact(2) { int n = 2
..
Return to fact()
fact(4) { int n = 4
Return to fact()
fact(4) { int n = 4
Return to fact()
fact(5) { int n = 5
Return to fact()
fact(5) { int n = 5
Return to fact()
fact(5) { int n = 5
Return to fact()
fact(n6) { int n = 6
Return to fact()
fact(6) { int n = 6
Return to fact()
fact(6) { int n = 6
Return to fact()
fact(7) { int n = 7
Return to main()
fact(7) { int n = 7
Return to main()
fact(7) { int n = 7
Return to main()
main() { int n = 7
Return
main() { int n = 7
Return
main() { int n = 7
Return
Code tracing factorial
#include<stdio.h>
long fact(int n);
long total;
int main() {
int n;
printf("Enter a positive integer: ");
scanf("%d",&n);
printf("Factorial of %d = %ld", n, fact(n));
return 0;
}
long fact(int n) {
printf("Entering factorial with n = %d\n", n);
if (n<=1) {
printf("Exiting factorial for n %d and factorial of n %d\n",n,n);
return 1;
}
else {
total = fact(n-1);
printf("Exiting factorial for n %d and factorial of n %ld\n",n,n*total);
return n*total;
}
}
factorial 7
BinarySearch(A[i..j], x)
if i > j then return -1
k = b (i+j)
2 c
if A[k] = x return k
if x < A[k] then
return BinarySearch(A[i..k − 1], x)
else
return BinarySearch(A[k + 1..j], x)
C code implementing recursive binary search
#include<stdio.h>
int RecursiveBsearch(int A[], int i, int j, int x) {
if(i>j) return -1 ;
int mid = (i+j)/2 ;
if( A[mid] == x ) return mid ;
else if( x < A[mid] )
RecursiveBsearch(A, i, mid-1, x) ;
else
RecursiveBsearch(A, mid+1, j, x) ;
}
int main() {
int A[] = {0,2,6,11,12,18,34,45,55,99} ;
int x=55 ;
printf(”%d is found at Index %d”,x,RecursiveBsearch(A,0,9,x)) ;
return 0 ;
}
Binary search : code tracing the recursive procedure
Mergesort(A[p..r ])
if p < r
q = b p+r
2 c
Mergesort(A[p..q])
Mergesort(A[q + 1..r ])
Merge(A, p, q, r )
Merge Sort
The code below only describe the recursive part of merge sort
Mergesort(A[p..r ])
if p < r
q = b p+r
2 c
Mergesort(A[p..q])
Mergesort(A[q + 1..r ])
Merge Sort : execution sequence
Mergesort(A[1..7])
if 1 < 7 38 27 43 3 9 82 10
4 = b 1+7
2 c
→ Mergesort(A[1..4])
38 27 43 3
Mergesort(A[5..7])
Mergesort(A[1..4])
if 1 < 4 38 27 43 3 9 82 10
2 = b 1+4
2 c
→ Mergesort(A[1..2])
38 27 43 3
Mergesort(A[3..7])
38 27
Mergesort(A[1..2])
if 1 < 2 38 27 43 3 9 82 10
q = b 1+2
2 c
→ Mergesort(A[1..1])
38 27 43 3
Mergesort(A[2..2])
38 27
38
Mergesort(A[1..1])
→if 1 < 1 38 27 43 3 9 82 10
q = b 1+1
2 c
Mergesort(A[1..q])
38 27 43 3
Mergesort(A[q + 1..1])
38 27
38
Mergesort(A[1..2])
if 1 < 2 38 27 43 3 9 82 10
1 = b 1+2
2 c
Mergesort(A[1..1])
38 27 43 3
→ Mergesort(A[2..2])
38 27
38 27
Mergesort(A[2..2])
→if 2 < 2 38 27 43 3 9 82 10
q = b 2+2
2 c
Mergesort(A[2..q])
38 27 43 3
Mergesort(A[q + 1..2])
38 27
38 27
Mergesort(A[1..2])
if 1 < 2 38 27 43 3 9 82 10
q = b 1+2
2 c
Mergesort(A[1..1])
38 27 43 3
Mergesort(A[2..2])
→
38 27
38 27
Mergesort(A[1..4])
if 1 < 4 38 27 43 3 9 82 10
q = b 1+4
2 c
Mergesort(A[1..2])
38 27 43 3
→ Mergesort(A[3..4])
38 27 43 3
38 27
Merge sort, recursive calls and run time stack
Mergesort(A[1..2]) Mergesort(A[1..1]) 38 27 43 3 9 82 10
Merge sort keeps executing the first recursive call, pushing activation
records along the way on the run time stack, until the array in the
function call has only one entry.
mergesort(A[1..1] mergesort(A[2..2]
mergesort(A[1..2] mergesort(A[1..2] mergesort(A[1..2] mergesort(A[1..2]
mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4]
mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7]
main() main() main() main() main() main() main()
Merge sort, recursive calls and run time stack
38 27 43 3 9 82 10
Mergesort(A[1..1]) Mergesort(A[1..2])
→if 1 < 1 if 1 < 2 38 27 43 3
q = b 1+1
2 c q = b 1+2
2 c
38 27
Mergesort(A[1..q]) Mergesort(A[1..1]) 38 27
mergesort(A[1..1] mergesort(A[2..2]
mergesort(A[1..2] mergesort(A[1..2] mergesort(A[1..2] mergesort(A[1..2]
mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4]
mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7]
main() main() main() main() main() main() main()
Merge sort complete sequence of recursive calls
The complete sequence of recursive calls is often represented by a tree,
sometime named the ”call tree”
The call tree pictures
I from which problem instance a recursive function calls has been
made (origin of an arc)
I for which problem instance (destination of an arc)
I which one of the recursive calls in the function has been executed
(the left most arc represents the first recursive call in the function,
the second arc the second function call, and so on)
Merge sort in terms of activation records
As the recursive function executes, the run time stack grows and
shrinks, the deeper a function call in the call tree, the higher the
corresponding activation record is in the run time stack and vice versa.
mergesort(A[1..1] mergesort(A[2..2]
mergesort(A[1..2] mergesort(A[1..2] mergesort(A[1..2] mergesort(A[1..2]
mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4]
mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7]
main() main() main() main() main() main() main()
mergesort(A[3..3] mergesort(A[4..4]
mergesort(A[3..4]
mergesort(A[1..2] mergesort(A[3..4] mergesort(A[3..4] mergesort(A[3..4] mergesort(A[1..4]
mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4] mergesort(A[1..4]
mergesort(A[1..7]
mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7] main()
main() main() main() main() main()
mergesort(A[3..4] mergesort(A[5..6]
mergesort(A[5..7] mergesort(A[5..7]
mergesort(A[1..4] mergesort(A[1..4]
mergesort(A[1..7] mergesort(A[1..7]
main() main()
mergesort(A[1..7] mergesort(A[1..7] mergesort(A[1..7]
main() main() main() … main()
The divide and merge parts !
Mergesort(A[p..r ])
if p < r
q = b p+r
2 c
Mergesort(A[p..q])
Mergesort(A[q + 1..r ])
→ Merge(A, p, q, r )
Mergesort(A[p..r ])
if p < r 38 27 43 3 9 82 10
q = b p+r
2 c
1 8
L- Mergesort(A[p..q]) 9 82 10
38 27 43 3
R- Mergesort(A[q + 1..r ]) 2 9 12
5
Merge(A, p, q, r ) 38 27 43 3 9 82 10
1 L-Mergesort(A[1..4]) 3 4 6 7 10 11 13
2 L-Mergesort(A[1..2]) 38 27 43 3 9 82 10
3 L-Mergesort(A[1..1]) 2 2 5 5 9 12
9
4 R-Mergesort(A[2..2])
2 Merge(A,1,1,2) 27 38 3 43 9 82 10
5 R-Mergesort(A[3..4]) 1 1 8 8
6 L-Mergesort(A[3..3]) 3 27 38 43 9 10 82
7 R-Mergesort(A[4..4]) 0 0
5 Merge(A,3,3,4) 3 9 10 27 38 43 82
1 Merge(A,1,2,4)
Sequence of recursive calls
Mergesort(A[p..r ])
if p < r 38 27 43 3 9 82 10
q = b p+r
2 c
1 8
L- Mergesort(A[p..q]) 9 82 10
38 27 43 3
R- Mergesort(A[q + 1..r ]) 2 9 12
5
Merge(A, p, q, r ) 38 27 43 3 9 82 10
8 R-Mergesort(A[5..7]) 3 4 6 7 10 11 13
9 L-Mergesort(A[5..6]) 38 27 43 3 9 82 10
10 L-Mergesort(A[5..5]) 2 2 5 5 9 12
9
11 R-Mergesort(A[6..6])
9 Merge(A,5,5,6) 27 38 3 43 9 82 10
12 R-Mergesort(A[7..7]) 1 1 8 8
13 L-Mergesort(A[7..7]) 3 27 38 43 9 10 82
12 Merge(A,7,7,7) 0 0
8 Merge(A,5,6,7) 3 9 10 27 38 43 82
0 Merge(A,1,4,7)
Conclusion
Iterative algorithms :
I A function call causes the program to jump in a different place in
the code, i.e. the first instruction of the called function
I Returning from a function call causes the program to return to
the instruction next to the function call in the calling function
Recursive algorithms :
I A function call causes the program to jump to the first instruction
of the currently executing function (the calling function)
I Returning from a function call causes the program to return to
the instruction next to the function call in the same function
Function calls for iterative and recursive algorithms are implemented
the same way using activation records and run time stacks.