Docsity
Docsity

Prepare for your exams
Prepare for your exams

Study with the several resources on Docsity


Earn points to download
Earn points to download

Earn points by helping other students or get them with a premium plan


Guidelines and tips
Guidelines and tips

Recursion Basic Beginner questions by stanford, Exercises of Data Structures and Algorithms

Recursion Basic Beginner questions by stanford

Typology: Exercises

2018/2019

Uploaded on 12/15/2019

harshit2981997
harshit2981997 🇮🇳

1 document

1 / 22

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Recursion
Recursion
Recursion
Recursion
Recursion
Recursion
Recursion
CS209 Lecture-02: Spring 2009
Dr. Greg Lavender
Department of Computer Sciences
Stanford University
greg.lavender@stanford.edu
Induction
Induction
Induction
Induction
Induction
Induction
Induction
Types of Recursion
Recursive statements (also called self-referential)
Recursively (inductively) defined sets
Recursively defined functions and their algorithms
Recursively defined data structures and recursive
algorithms defined on those data structures
Recursion vs Iteration
1
2
Sunday, April 12, 2009
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16

Partial preview of the text

Download Recursion Basic Beginner questions by stanford and more Exercises Data Structures and Algorithms in PDF only on Docsity!

Recursion

Recursion

Recursion Recursion Recursion Recursion Recursion

CS209 Lecture-02: Spring 2009

Dr. Greg Lavender

Department of Computer Sciences

Stanford University

greg.lavender@stanford.edu

Induction^ Induction Induction Induction Induction Induction

Induction

Types of Recursion

Recursive statements (also called self-referential) Recursively (inductively) defined sets Recursively defined functions and their algorithms Recursively defined data structures and recursive algorithms defined on those data structures Recursion vs Iteration 1 2

Recursive Statements

In order to understand

recursion, one must first

understand recursion

This sentence contains

thirty-eight letters

GNU = GNU’s Not Unix!

The Ouroboros is an ancient symbol implying self-reference or a “vicious circle”

Paradoxical Statements

This is not a pipe The Barber Paradox A barber shaves all and only those men who do not shave themselves if the barber does not shave himself, he must shave himself if the barber does shave himself, he cannot shave himself The Treachery of Images (1928-29), by René Magritte 3 4

Induction vs Recursion

For beginners, induction is

intuitive, but recursion is often

counter-intuitive

Induction is like “ascending”

e.g., counting up: 1,2,3,...

Recursion is like “descending”

e.g., counting down: n,n-1,n-2,...

But they often go hand-in-hand

to solve a problem

Lost in Recursion Land

Beginners often fail to appreciate

that a recursion must have a

conditional statement or conditional

expression that checks for the

“bottom-out” condition of the

recursion and terminates the

recursive descent

We call the bottom-out condition

the “base case” of the recursion

If you fail to do this properly, you

end up lost in Recursion Land and

you never return!

7 8

A Simple Algebraic Proof

Due to Carl Friedrich Gauss (1777-1855)

he was told to sum the first 100 positive integers at a young age while in a class on arithmetic

Gauss combined counting up with counting down

(1 + 2 + 3 + ... + n) + (n + n-1 + n-2 + ... + 1) = (1+n) + (2+n-1) + (3+n-2)... + (n+1) = n+1 + n+1 + n+1 + ... + n+ = n * (n+1) = 2 * sum(n)

Therefore, sum(n) = n*(n+1)/2 for all n >= 1

Ex: sum(100) = (100 * 101)/2 = 50*101 = 5050

Summing Up vs Summing Down

a well-ordered ascending

sequence

isum(n) = 1 + 2 + 3 + ... + n

a well-ordered descending

sequence

rsum(n) = n + n-1 + n-2 + ... + 1

Both isum and rsum compute the

same value for a given n, but isum

does so in O(1) stack space while

rsum requires O(n) stack space

// inductive sum (count up) int isum(int n) { int sum = 0; for (int i=1; i<=n; ++i) sum += i; return sum; } // recursive sum (count down) int rsum(int n) { assert(n > 0); if (n == 1) return 1; else return n + rsum(n-1); } 9 10

Basic Arithmetic Functions

Arithmetic can then be defined recursively in terms of counting up (successor) and counting down (predecessor) succ, pred :: Nat -> Nat -- unary functions succ n = Succ n -- count up by prepending Succ to n pred (Succ n) = n -- count down by removing a Succ from n pred Zero = error “no predecessor of Zero” add, mult :: (Nat, Nat) -> Nat -- binary functions add (n, Zero) = n add (Zero, m) = m add (n, m) = succ(add(n, pred m)) -- succ of n, m times mult (n, Zero) = Zero mult (Zero, m) = Zero mult (n, m) = add(n, mult(n, pred m)) -- succ of n, n+m times

Example

A calculator using Peano Arithmetic: PA> pred Zero *** Exception: no predecessor of Zero PA> succ Zero Succ (Zero) PA> pred (Succ Zero) Zero PA> add(Succ Zero, Succ (Succ Zero)) Succ (Succ (Succ (Zero))) PA> add(Succ Zero, Succ (Succ (Succ (Succ Zero)))) Succ (Succ (Succ (Succ (Succ (Zero))))) PA> mult(Succ (Succ Zero), Succ (Succ (Succ (Succ Zero)))) Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ(Zero)))))))) 13 14

Classic Recursive Functions

Euclid’s Greatest Common Divisor (GCD) function

Factorial function

Fibonacci function

Euclid’s GCD Function

The Greatest Common Divisor (GCD) of a pair of integers (x, y)

is defined by taking the remainder r of (abs x) divided by (abs

y). If r is 0, return x. Otherwise compute GCD of y and r.

gcd :: (Int, Int) -> Int gcd (x, y) = gcd’ (abs x) (abs y) where gcd’ x 0 = x gcd’ x y = gcd’ y (x mod y) gcd (-98, 16) = gcd’ 16 (98 mod 16) = gcd’ 16 2 = gcd’ 2 (16 mod 2) = gcd’ 2 0 = 2 15 16

Lists are Recursive Structures

A list is a recursively defined data type with elements of some

type ‘a’, e.g., [1,2,3] is a list of type ‘int’

[] constructs the empty list; ‘:’ is an infix right associative list

constructor operator (cons), that constructs a new list from an

element of type ‘a’ on the left and a list [a] on the right

data [a] = [] | a : [a] 3:[] = [3]; 2:[3] = [2,3]; 1:[2,3] = [1,2,3] = 1:2:3:[]

let head [a 1 ,a 2 ,...,an] = a 1 ; tail [a 1 ,a 2 ,...,an] = [a 2 ,...,an]

head [] = error; tail [] = error

let (h:t) pattern match [a 1 ,a 2 ,...,an] such that h = a 1 , t = [a 2 ,..,an]

let ‘++’ be list concatenation: [1,2] ++ [3,4] = [1,2,3,4]

List Comprehensions

Another way to construct a list is using a list comprehension,

similar to a set comprehension in Set Theory

Set Theory: { f(x) | x in S}, {x | p(x), x in S }

Haskell: [f x | x <- xs], [ x | x <- xs, p(x) ]

Example:

map :: (a -> b) -> [a] -> [b] map f xs = [ f x | x <- xs ] map square [1,2,3] => [1,4,9] filter :: (a -> bool) -> [a] -> [b] filter p xs = [ x | x <- xs, p x] cprod :: [a] -> [b] -> [(a,b)] cprod xs ys = [(x,y) | x <- xs, y <- ys] 19 20

Recursive List Functions

length :: [a] -> Int -- [a] means a list of any type ‘a’ length [] = 0 -- empty list is the base case length (h:t) = 1 + length t sum :: (Num a) => [a] -> Int -- type of a must be subtype of Num sum [] = 0 -- empty list is the base case sum (h:t) = h + sum t mean :: (Num a) => [a] -> Float mean lst = sum lst / length lst -- Note: mean above requires 2 traversals of the list! -- Can we compute the mean using just one traversal? -- let (x,y) be an ordered pair and fst (x,y) = x, snd (x,y) = y sumlen :: (Num a) => [a] -> (Int,Int) -> (Int,Int) sumlen [] = p sumlen (h:t) p (^) = sumlen t (h + fst(p), 1 + snd(p)) mean lst = fst(p) / snd(p) where p = sumlen lst (0,0)

Recursive List Functions

-- list concatenation infix operator (++) :: [a] -> [a] -> [a] [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) Ex: [1,2,3] ++ [4,5,6] => [1,2,3,4,5,6] -- index infix operator, starting at index 0 (!!) :: [a] -> Int -> a xs !! n | n<0 = error "Prelude.!!: negative index" [] !! _ = error "Prelude.!!: index too large" (x:) !! 0 = x (:xs) !! n = xs !! (n- Ex: [1,2,3,4,5] !! 4 => 5 take :: Int -> [a] -> [a] take n _ | n <= 0 = [] take _ [] = [] take n (x:xs) = x : take (n-1) xs Ex: take 5 [1..] => [1,2,3,4,5] 21 22

Mapping over a List

Map applies a function to each element of type ‘a’ of a list,

producing a list of type ‘b’ values. Type ‘a’ may equal ‘b’

map :: (a -> b) -> [a] -> [b] map f (x:xs) = f x : map f xs -- or with a list comprehension: [f x | x <- xs] -- map any function of type (a->b) across a list map length [[],[1,2,3],[‘A’,‘C’,‘T’,’G’] => [0,3,4] map even [1,2,3,4,5] => [False,True,False,True,False] map Char.ord [‘A’,’T’,’C’,’G’] => [65,67,84,71] -- (+5) is called a “section” -- (+) :: a->a->a but (+5),(5+) :: a->a map (+5) [1,2,3,4,5] => [6,7,8,9,10] map (5+) [1,2,3,4,5] => [6,7,8,9,10] -- (\x -> x * x) is an anonymous “lambda” function map (\x -> x * x) [1,2,3,4,5] => [1,4,9,16,25]

A Recursive List Fibonacci

With “lazy lists” we can take a different approach

fibs :: [Integer] fibs = 0 : 1 : zipWith (+) fibs (tail fibs) fiblist :: Int -> [Integer] fiblist n = take n fibs fib :: Int -> Integer fib n = fibs !! (n-1)

This is a recursive mind-twister! What is going on?

zipWith :: (a->b->c) -> [a] -> [b] -> [c] zipWith z (a:as) (b:bs) = z a b : zipWith z as bs zipWith _ _ _ = [] 25 26

Polynomial Evaluation

Given a n-degree polynomial P of the form:

Pn(x) = anxn^ + an-1xn-1^ + ... a 1 x + a 0

A naive evaluation requires at most n additions

BUT, n+1 + n + n-1 + ... + 1 = (n^2 +n)/2 multiplications

Can we do better? Yes, factor out the duplicate

multiplications required for each xi

Pn(x) = (((...(an x + an-1) ... )x + a 2 )x + a 1 )x + a 0

Requires n additions, but only n multiplications!

Horner’s Method

Described by the Englishman George Horner in 1819,

but known to Isaac Newton as early as 1669.

Pn(x) = (((...(an x + an-1) ... )x + a 2 )x + a 1 )x + a 0

Leads to a natural recursive definition:

let Pn(x) = Hn(x)

where

H 0 (x) = a 0

Hi(x) = Hi-1(x) * x + ai, for i = 1,2,...,n

p :: (Num a) => a -> [a] -> a p x coeffs = h x (reverse coeffs) where h x (a:[]) = a h x (a:as) = (h x as) * x + a 27 28

Recursive Binary Tree

Traversal Algorithms

data BinTree a = Leaf a | Root a (BinTree a) (BinTree a) preorder :: BinTree a -> [a] preorder (Leaf v) = [v] preorder (Root v l r) = [v] ++ preorder l ++ preorder r inorder :: BinTree a -> [a] inorder (Leaf v) = [v] inorder (Root v l r) = inorder l ++ [v] ++ inorder r postorder :: BinTree a -> [a] postorder (Leaf v) = [v] postorder (Root v l r) = postorder l ++ postorder r ++ [v]

Simple Recursive Sorting

Given a list of values of type ‘a’ on which there is an ordering

relation defined, permute the elements of the list so that they

are ordered in either ascending or descending order

Example: Given the input list [16, -99, 25, 71, 9, 3, 28], sort it

into ascending order

Step 1: select the first element of the list as a “pivot”

Step 2: partition the list into two sublists “left” and “right”

where left = [ x | x <= pivot] and right = [ y | y > pivot]

Step 3: Recursively sort the left sublist and prepend that

result to the singleton list [pivot], and recursively sort the

right sublist and append the result to the left++pivot list

31 32

Recursive Sorting Example

sort [16, -99, 25, 71, 9, 3, 28]

sort [-99,9,3] ++ [16] ++ sort [25,71,28]

(sort [] ++ [-99] ++ [9,3]) ++ [16] ++ sort [25,71,28]

(sort [] ++ [-99] ++ sort [9,3]) ++ [16] ++ sort [25,71,28]

(sort [] ++ [-99] ++ (sort [3] ++ [9] ++ sort []) ++ [16] ++ sort [25,71,28]

[-99, 3, 9] ++ [16] ++ sort [25,71,28]

[-99, 3, 9] ++ [16] ++ (sort [] ++ [25] ++ sort [28, 71])

[-99, 3, 9] ++ [16] ++ (sort [] ++ [25] ++ (sort [] ++ [28] ++ sort [71])

[-99, 3, 9, 16, 25, 28, 71]

[-99, 3, 9] ++ [16] ++ [25, 28, 71]

Recursive Sort Algorithm

Polymorphic recursive sorting function that sorts a list of

elements of type ‘a’, where ‘a’ is required to be ordered

(i.e., has the relational operators ==, <, >, <= and >= defined)

sort :: (Ord a) => [a] -> [a] sort [] = [] -- base case sort (x:[]) = [x] -- singleton list sort (pivot:rest) = sort left ++ [pivot] ++ sort right where left = [x | x <- rest, x <= pivot] right = [y | y <- rest, y > pivot] 33 34

Tail Recursion

“Recursion is the root of

computation since it trades

description for time”

  • Alan Perlis

The benefits of an elegant

recursive description, but

equivalent in space and

time to an iteration

requires O(1) stack space instead of O(n) stack space © M. C. Escher

Tail Recursive Factorial

Written recursively, but only requires O(1) stack space like

an iteration. We just need to invoke tfact with m = 1 and

compute the expression m*n before the recursive call. The

compiler can then do “tail call” optimization and turn the

recursion into an iteration automatically

int tfact (int n, int m=1) { return (n == 0 || n == 1)? m : tfact(n-1, m*n); }

n=

m=

n=3- m=1* Frame #1 Frame #1 Frame # n=2- m=3* 37 38

No Tail Call Optimization

Use “g++ -S tfact.cc”. Note the “call” instruction

_tfact: pushl %ebp movl %esp, %ebp subl $40, %esp cmpl $1, 8(%ebp) je L cmpl $0, 8(%ebp) jne L L14: movl 12(%ebp), %eax movl %eax, -12(%ebp) jmp L L16: movl 12(%ebp), %eax imull 8(%ebp), %eax movl 8(%ebp), %edx subl $1, %edx movl %eax, 4(%esp) movl %edx, (%esp) call _tfact movl %eax, -12(%ebp) L17: movl -12(%ebp), %eax leave ret

Tail Call Optimization

Recompile tfact using “g++ -O2 -S tfact.cc” to verify

that a loop is generated using one stack frame, not

call insn using O(n) stack frames

_tfact: pushl %ebp movl %esp, %ebp movl 8(%ebp), %edx movl 12(%ebp), %eax cmpl $1, %edx jbe L L26: imull %edx, %eax subl $1, %edx cmpl $1, %edx ja L L22: popl %ebp ret 39 40