0% found this document useful (0 votes)
60 views

Data Structures and Algorithms Lecture Slides: Programming Languages Implementations of Recursion

The document discusses activation records and the runtime stack. When a function is called, an activation record is created and pushed onto the runtime stack. This activation record stores local variables, parameters, and the return address. When the function finishes, it pops off the stack and returns to the instruction after the call in the caller. This allows functions to call other functions and remember where to return each time.

Uploaded by

Phạm Gia Dũng
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
60 views

Data Structures and Algorithms Lecture Slides: Programming Languages Implementations of Recursion

The document discusses activation records and the runtime stack. When a function is called, an activation record is created and pushed onto the runtime stack. This activation record stores local variables, parameters, and the return address. When the function finishes, it pops off the stack and returns to the instruction after the call in the caller. This allows functions to call other functions and remember where to return each time.

Uploaded by

Phạm Gia Dũng
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 39

Data Structures and Algorithms

Lecture slides: Programming languages


implementations of recursion

Lecturer: Michel Toulouse

Hanoi University of Science & Technology


[email protected]
Topics cover

Activation records & run time stacks

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

An activation record is created each time a function call is made in a


program

In a programming language like C, the first activation record is the one


corresponding to main()
Activation record

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

Below is a recursive implementation of factorial n

factorial (n)
if (n ≤ 1)
return 1 ;
else
return n × factorial(n − 1) ;

The function ”factorial” is first called with n as argument, then it calls


itself with argument n − 1.
C code implementing recursive factorial

#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

Entering factorial with n = 7


Entering factorial with n = 6
Entering factorial with n = 5
Entering factorial with n = 4
Entering factorial with n = 3
Entering factorial with n = 2
Entering factorial with n = 1
Exiting factorial for n = 1, factorial of n is 1
Exiting factorial for n = 2, factorial of n is 2
Exiting factorial for n = 3, factorial of n is 6
Exiting factorial for n = 4, factorial of n is 24
Exiting factorial for n = 5, factorial of n is 120
Exiting factorial for n = 6, factorial of n is 720
Exiting factorial for n = 7, factorial of n is 5040
A second example : recursive binary Search
Given a sorted array A of size n, and a searched value x, recursive binary
search compares x with A[b n2 c]
I if x < A[b n2 c], recursively searches the sub-array A[1..b n2 c − 1]
I if x > A[b n2 c], recursively searches the sub-array A[b n2 c + 1, n]
I if x = A[b n2 c], return b n2 c, the index in A[] where x has been found

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

int RecursiveBsearch(int A[], int i, int j, int x) {


if(i>j) {
printf(“x not found, i = 2 > j = 1, returning -1\n”,i,j);
return -1;
}
int mid = (i+j)/2;
if( A[mid] == x ) {
printf("Found the value, returning the value of index = %d\n", mid);
return mid;
}
else if( x < A[mid] ){
printf("x %d is smaller then A[mid] %d where mid is %d so calling left recursion\n", x, A[mid], mid);
solution = RecursiveBsearch(A, i, mid-1, x);
printf("Returning from left recursion of mid = %d with returned value = %d \n",mid,solution);
return solution;
}
else {
printf("x %d is greater then A[mid] %d where mid is %d so calling right recursion\n", x, A[mid], mid);
solution = RecursiveBsearch(A, mid+1, j, x);
printf("Returning from right recursion with mid = %d with returned value = %d \n",mid,solution);
return solution;
}
}
Binary search : steps of the recursion
Binary search steps for A = [0, 2, 6, 11, 12, 18, 34, 45, 55, 99, 103, 114] and x = 3

if (i > j) { printf(“x = 3 not found, i > j, returning -1\n”,i,j); return -1;}


else if( x < A[mid] ){
printf("x %d is smaller then A[mid] %d where mid is %d so calling left recursion\n", x, A[mid], mid);
solution = RecursiveBsearch(A, i, mid-1, x);
printf("Returning from left recursion of mid = %d with returned value = %d \n",mid,solution);
return solution;
}
else {
printf("x %d is greater then A[mid] %d where mid is %d so calling right recursion\n", x, A[mid], mid);
solution = RecursiveBsearch(A, mid+1, j, x);
printf("Returning from right recursion with mid = %d with returned value = %d \n",mid,solution);
return solution;

x = 3 is smaller then A[mid] 18 where mid is 5 so calling left recursion


x = 3 is smaller then A[mid] 6 where mid is 2 so calling left recursion
x = 3 is greater then A[mid] 0 where mid is 0 so calling right recursion
x = 3 is greater then A[mid] 2 where mid is 1 so calling right recursion
x = 3 not found, i = 2 > j = 1, returning -1
Returning from right recursion with mid = 1 with returned value = -1
Returning from right recursion with mid = 0 with returned value = -1
Returning from left recursion of mid = 2 with returned value = -1
Returning from left recursion of mid = 5 with returned value = -1
3 is found at Index -1
Binary search : run time stack, pushing activation records

x = 3 is smaller then A[mid] 18 where mid is 5 so calling left recursion


x = 3 is smaller then A[mid] 6 where mid is 2 so calling left recursion
x = 3 is greater then A[mid] 0 where mid is 0 so calling right recursion
x = 3 is greater then A[mid] 2 where mid is 1 so calling right recursion
x = 3 not found, i = 2 > j = 1, returning -1
mid i>j
{ A[empty]
return -1
mid = 1
{ A[1]
return
mid = 0{ A[0..1]
mid = 0{ A[0..1]
Return mid = 2{
Return
A[0..5]
mid = 2{ A[0..5]
Return
mid = 2{ A[0..5]
Return mid = 5{
Return
A[0..11]
mid = 5{ A[0..11]
mid = 5{
mid=5 { A[[0..11]
return Return
A[0..11]
Return main() {
Return
int x = 3
main() { main() { int x = 3
main() { int x = 3 Return
main()
{ int x =3
Return
int x = 3
Return Return Return
Binary search : run time stack, popping activation records

Returning from right recursion with mid = 1 with returned value = -1


Returning from right recursion with mid = 0 with returned value = -1
Returning from left recursion of mid = 2 with returned value = -1
Returning from left recursion of mid = 5 with returned value = -1
3 is found at Index -1
mid = 1 A[1]
return -1
mid = 0{ A[0..1]
Return mid = 0{ A[0..1]
return -1
mid = 2{ A[0..5]
mid = 2{ A[0..5]
Return Return mid = 2{ A[0..5]
return -1
mid = 5{ A[0..11]
mid = 5{
Return
A[0..11]
Return
mid = 5{ A[0..11]
Return
mid=5 { A[[0..11]
return -1
main() { int x = 3
main() { int x = 3
main() {
main() {
int x =3
Return Return
int x = 3
Return
int x = 3
Return
main()
{ return -1
Merge Sort

Merge sort is a recursive algorithm to sort an array of integers

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

if 1 < 2 →if 1 < 1 38 27 43 3


q = b 1+2
2 c q = b 1+1
2 c 38 27
→ Mergesort(A[1..1]) Mergesort(A[1..q])
Mergesort(A[2..2]) Mergesort(A[q + 1..1]) 38

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[q + 1..1]) →Mergesort(A[2..2])

Then mergesort(A[1..1]) returns to mergesort(A[1..2]), thus its


activation record is popped from the stack, executing the instruction
next to the call to mergesort(A[1..1]), i.e. mergesort(A[2..2]), thus the
activation record for mergesort(A[2..2]) is pushed on the stack

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 )

The merge part takes 2


sorted arrays and
merges them into a
single array sorted in
increasing order
Merge algorithm
Merge(A, p, q, r )
n1 = q − p + 1
Works as follows :
n2 = r − q I Has a pointer to the
L[1..n1 + 1], R[1..n2 + 1] beginning of each
for i = 1 to n1 subarray.
L[i] = A[p + i − 1]
I Put the smaller of the
for j = 1 to n2
R[j] = A[q + j] elements pointed to in
L[n1 + 1] = −∞ the new array.
R[n2 + 1] = −∞ I Move the appropriate
i = 1, j = 1 pointer.
for k = p to r
if L[i] ≤ R[j] I Repeat until new array
A[k] = L[i] is full.
i =i +1
else
A[k] = R[j]
j =j +1
Merge algorithm
Merge(A, p, q, r )
n1 = q − p + 1
n2 = r − q
L[1..n1 + 1], R[1..n2 + 1]
for i = 1 to n1
L[i] = A[p + i − 1]
for j = 1 to n2
R[j] = A[q + j]
L[n1 + 1] = −∞
R[n2 + 1] = −∞
i = 1, j = 1
for k = p to r
if L[i] ≤ R[j]
A[k] = L[i]
i =i +1
else
A[k] = R[j]
j =j +1
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
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.

You might also like