Programming abstractions and analysis recursion 10101011110101 - - PowerPoint PPT Presentation

programming abstractions and analysis recursion
SMART_READER_LITE
LIVE PREVIEW

Programming abstractions and analysis recursion 10101011110101 - - PowerPoint PPT Presentation

01110111010110 11110101010101 00101011010011 01010111010101 01001010101010 10101010101010 Programming abstractions and analysis recursion 10101011110101 Mikko Kivel 01010101011101 01010111010110 Department of Computer Science


slide-1
SLIDE 1

01110111010110 11110101010101 00101011010011 01010111010101 01001010101010 10101010101010 10101011110101 01010101011101 01010111010110 10101101010110 10101110101010 11101010101101 01110111010110 10111011010101 11110101010101 00010101010101 01011010101110 10101010100101

Programming abstractions and analysis –– recursion

Mikko Kivelä

Department of Computer Science Aalto University

CS-A1120 Programming 2 15 April 2020

Lecture notes based on material created by Tommi Junttila & Petteri Kaski

slide-2
SLIDE 2

Contents (rounds and modules)

  • 1. Warmup round
  • I The mystery of the computer

  • 2. Bits and data

  • 3. Combinational logic

  • 4. Sequential logic

  • 5. Programmable computer
  • II Programming abstractions and analysis

  • 6. Collections and functions

  • 7. Efficiency

  • 8. Recursion

  • 9. Algorithms and representations of information
  • III Frontiers

  • 10. Concurrency and parallelism

  • 11. Virtualization and scalability

  • 12. Machines that learn?
slide-3
SLIDE 3

Recursion

Definition: see recursion

slide-4
SLIDE 4

Recursion

  • Recursion is a basic principle for…
  • replacing iteration in functional

programming

  • defining structural objects such as data

types or collections and traverse them

  • solving computational problems
slide-5
SLIDE 5

Palindromes (reminder from O1)

  • Palindromes are words or sentences that read the

same forwards and backwards (not including spaces and punctuation)

  • E.g., “testset” or “A man, a plan, a canal: Panama”
  • Can be defined (and checked for) recursively:
  • A. String with zero or one characters is a palindrome
  • B. String is a palindrome if it (1) starts and ends with

the same character and (2) the substring between these is a palindrome

slide-6
SLIDE 6

Palindromes (reminder from O1)

  • Animation for a similar code in Programming 1:

https://plus.cs.aalto.fi/o1/2019/w12/ch01/

def isPalindrome(s: String): Boolean = { // Helper inner function doing the actual recursive task def actualSearch(t: String): Boolean = { if (t.length <= 1) true else if (t.head != t.last) false else actualSearch(t.substring(1, t.length - 1)) } // Remove spaces and special characters val sPlain = s.filter(_.isLetterOrDigit).map(_.toLower) // Call the actual recursive search function actualSearch(sPlain) }

slide-7
SLIDE 7

Textual representation of a call stack

  • A way of visualising what happens when executing a

recursive function

isPalindrome("ufo tofu"): val sPlain = "ufotofu" // We could omit this return actualSearch("ufotofu") actualSearch("ufotofu"): // tests (t.length <= 1) and (t.head != t.last) both fail return actualSearch("fotof") actualSearch("fotof"): return actualSearch("oto"): actualSearch(“oto”): return actualSearch("t"): actualSearch("t"): return true // test (t.length <= 1) succeeds

  • Not interesting or obvious rows such as sPlain =

“ufotofu” can be left out of this illustration

slide-8
SLIDE 8

Expanding

  • Another way of visualising recursive function calls
  • Computations in functions without side effects can be

illustrated by “expanding” the code:

isPalisPalindrome("ufo tofu") = actualSearch("ufo tofu".filter(_.isLetterOrDigit).map(_.toLower)) = actualSearch("ufotofu".map(_.toLower)) = actualSearch("ufotofu") = actualSearch("fotof") = actualSearch("oto") = actualSearch("t") = true

  • Again, not interesting or obvious rows can be left out
slide-9
SLIDE 9

Tail calls and recursion

slide-10
SLIDE 10

Recursion and the call stack

  • The main problem in recursion: there is only limited

amount of memory for the call stack

  • For example, computing factorial recursively:
  • Running this will lead to problems:

def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") if(n == 1) BigInt(1) else n * fact(n-1) }

n! = { 1 when n = 1 n × (n − 1)! when n > 2

scala> fact(100000) java.lang.StackOverflowError

slide-11
SLIDE 11

Recursion and the call stack

  • Call stack for our recursive factorial function:

fact(4): val temp1 = fact(3) fact(3): val temp2 = fact(2) fact(2): val temp3 = fact(1) fact(1): return BigInt(1) // temp3 = 1 return 2*temp3 // 2*1 = 2 // temp2 = 2 return 3*temp2 // 3*2 = 6 // temp1 = 6 return 4*temp1 // 4*6 = 24

  • For every call, we need to remember the value of n and

calculate fact(n-1) before we can return the result ➔ Call stack will have the depth of the parameter n

slide-12
SLIDE 12

Recursion and the call stack

  • We can compute the result by expanding the function

(because it doesn’t have side effects)

fact(4) = (4 * fact(3)) = (4 * (3 * fact(2))) = (4 * (3 * (2 * fact(1)))) = (4 * (3 * (2 * 1))) = (4 * (3 * 2)) = (4 * 6) = 24

slide-13
SLIDE 13

Definition

Tail calls

Function call in a method is a tail call if it is the last

  • peration of the function

def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") if(n == 1) BigInt(1) else n * fact(n-1) }

  • BigInt(1) is a tail call
  • fact(n-1) is not a tail call; the last operation in

the function is n * fact(n-1)

  • For example, in the function
slide-14
SLIDE 14

Definition

Tail recursion

Function is tail recursive if all of its calls to itself are tail calls

  • Here we only consider direct tail recursion where

the tail calls do not go through some other function (e.g., def f() = …; g() and def g() = …; f() )

  • Scala can optimise tail recursive functions such that

they are implemented with iteration

  • This is because the compiler knows that no

variables cannot be used after the tail call and the the call frame can be released

slide-15
SLIDE 15

Tail recursion

  • Lets make a tail recursive version of the factorial

function (by using tail recursive inner function):

  • Note the similarity with the iterative version:

def fact(n: Int): BigInt = { require(n >= 1, "n should be a positive integer") def iterate(i: Int, result: BigInt): BigInt = { if(i == 0) result else iterate(i - 1, result * i) } iterate(n, BigInt(1)) } def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") var result = BigInt(1) for(i <- n to 1 by -1) result = result * i result }

slide-16
SLIDE 16

Tail recursion

  • Expansion of the tail recursive fact function:
  • Same with call stack: the call frame of iterate is reused:

fact(4) = iterate(4, 1) = iterate(3, 4) = iterate(2, 12) = iterate(1, 24) = iterate(0, 24) = 24

fact(4): return iterate(4, 1) iterate(4, 1): return iterate(3, 4) iterate(3, 4): return iterate(2, 12) iterate(2, 12): return iterate(1, 24) iterate(0, 24): return 24

slide-17
SLIDE 17

@tailrec annotation

  • The @tailrec annotation in Scala declares that the

compiler must optimise tail recursion into iteration

  • If the annotated function is not tail recursive, the

compiler will issue an error

  • Only direct tail calls are allowed
  • It must not be possible to override the function: it

must be an inner function or declared as final method

import scala.annotation.tailrec ... @tailrec def myFunction(...) { ... } For more information, see: http://theyougen.blogspot.com/2010/01/why-overrideable-tail-recursive-methods.html

slide-18
SLIDE 18

Recursively defined data structures

slide-19
SLIDE 19

Recursive data structures

  • In addition to functions, data structure can be

recursive

  • They are most naturally manipulated with

recursive functions

  • Next we go through two examples:
  • 1. Linked lists (similar to List in Scala)
  • 2. Symbolic arithmetic expressions (example
  • f more general tree data structure)
slide-20
SLIDE 20

Linked lists

slide-21
SLIDE 21

Linked lists

  • We consider lists containing elements of type T
  • Let define such T-lists recursively:
  • 1. Nil is the empty T-list
  • 2. If l is a T-list and e is an element of type T, then

Cons(e, l) is a T-list where e is the first element

followed by the elements of the list l Examples:

  • Nil is an empty Int-list []
  • Cons(1,Nil) is an Int-list [1]
  • Cons(1,Cons(3,Cons(5,Nil))) is an Int-list

[1,3,5]

  • Cons(1,Cons(Nil,Cons(5,Nil))) is not an Int-

list

slide-22
SLIDE 22

Linked lists

  • For a list

Cons(e, l)

  • We say that:
  • e is the head of the list
  • l is the tail of the list
  • Empty list does not have head or tail

Examples:

  • In Int-list Cons(5, Nil) the head is 5 and tail Nil
  • In String-list Cons(“first”, Cons(“second”,Nil))

the head is “first” and tail Cons(“second”,Nil)

slide-23
SLIDE 23

Linked lists, the first implementation

abstract class LinkedList[A] { def isEmpty: Boolean def head: A def tail: LinkedList[A] } class Nil[A]() extends LinkedList[A] { def isEmpty = true def head = throw new java.util.NoSuchElementException("head of empty list") def tail = throw new java.util.NoSuchElementException("tail of empty list") } class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] { def isEmpty = false }

  • bject Nil {

def apply[A]() = new Nil[A]() }

  • bject Cons {

def apply[A](head : A, tail : LinkedList[A]) = new Cons(head, tail) }

slide-24
SLIDE 24

Linked lists, the first implementation

  • We can now write:

val l = Cons(5, Cons(4, Cons(-7, Nil())))

  • … and these objects will be created in the memory:

head

Cons

tail head

Nil 5 Cons

tail head

4 −7

l

Cons

tail

slide-25
SLIDE 25

Linked lists, the first implementation

  • If we write:

val l = Cons(5, Cons(4, Cons(-7, Nil()))) val m = Cons(3, l)

  • … these objects will be created:

Cons

tail head

Nil 5 Cons

tail head

Cons

tail head

3 4 −7

l m

Cons

tail head

  • Note that the list l is not copied
  • Adding to (and removing from) the beginning of the

list is efficient

slide-26
SLIDE 26

Linked lists, contains method

  • Tests if the list contains e
  • Implemented recursively:
  • A. empty list does not contain e
  • B. if head of the list is e, then it contains e
  • C. otherwise it only contains e if the tail contains e

@tailrec final def contains(e: A): Boolean = { if(isEmpty) false else if(head == e) true else tail.contains(e) }

  • Example of the call stack:

Cons(1, Cons(2, Cons(3, Nil()))).contains(3): return Cons(2, Cons(3, Nil())).contains(3) Cons(2, Cons(3, Nil())).contains(3): return Cons(3, Nil()).contains(3) Cons(3, Nil()).contains(3): return true

slide-27
SLIDE 27

Linked lists, length method

  • Calculates the length of the list
  • Implemented recursively:
  • A. empty list has length 0
  • B. the length of list Cons(e, t) is 1 plus length of the list t

abstract class LinkedList[A] { ... def length: Int } class Nil[A]() extends LinkedList[A] { ... def length = 0 } class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] { ... def length = 1 + tail.length }

slide-28
SLIDE 28

Linked lists, length method

  • This implementation is not tail recursive!
  • With a long list it will result in a StackOverflowError
  • Example of a call stack with a short list

Cons(1, Cons(2, Cons(3, Nil()))).length: val tmp1 = Cons(2, Cons(3, Nil())).length Cons(2, Cons(3, Nil())).length: val tmp2 = Cons(3, Nil()).length Cons(3, Nil()).length: val tmp3 = Nil().length Nil().length: return 0 // tmp3 == 0 return 1 + tmp3 // tmp2 == 1 return 1 + tmp2 // tmp1 == 2 return 1 + tmp1 3

slide-29
SLIDE 29

Linked lists, length method

  • A tail recursive version:

def length: Int = { def inner(remaining: LinkedList[A], result:Int): Int = { if(remaining.isEmpty) result else inner(remaining.tail, result+1) } inner(this, 0) }

  • Expanded function call:

Cons(1, Cons(2, Cons(3, Nil()))).length = inner(Cons(1, Cons(2, Cons(3, Nil()))), 0) = inner(Cons(2, Cons(3, Nil())), 1) = inner(Cons(3, Nil()), 2) = inner(Nil(), 3) = 3

slide-30
SLIDE 30

Linked lists, length method

  • A tail recursive version:

def length: Int = { def inner(remaining: LinkedList[A], result:Int): Int = { if(remaining.isEmpty) result else inner(remaining.tail, result+1) } inner(this, 0) }

  • Compare to imperative version:

def length: Int = { var result = 0 var remaining = this while(!remaining.isEmpty) { result += 1 remaining = remaining.tail } result }

slide-31
SLIDE 31

case class

  • Companion objects are defined automatically!

abstract class LinkedList[A] { def isEmpty: Boolean def head: A def tail: LinkedList[A] } case class Nil[A]() extends LinkedList[A] { def isEmpty = true def head = throw new java.util.NoSuchElementException("head of empty list") def tail = throw new java.util.NoSuchElementException("tail of empty list") } case class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] { def isEmpty = false }

slide-32
SLIDE 32

case class

  • The previously defined operations now implemented

using case class and the match pattern matching:

@tailrec final def contains(e: A): Boolean = this match { case Nil() => false case Cons(h, t) => if (h == e) true else t.contains(e) } def length: Int = { @tailrec def inner(remaining: LinkedList[A], result: Int): Int = remaining match { case Nil() => result case Cons(_, t) => inner(t, result + 1) } inner(this, 0) }

slide-33
SLIDE 33
  • The previously defined operations now implemented

using case class and the match pattern matching:

abstract class LinkedList[A]{ … @tailrec final def foreach(f : A => Any) : Unit = this match { case Nil() => () // The only value of type Unit is "()" case Cons(v, t) => f(v); t.foreach(f) } …

foreach method

  • Example of a call stack:

Cons(1, Cons(2, Cons(3, Nil()))).foreach(println _): println(1) return Cons(2, Cons(3, Nil())).foreach(println _) Cons(2, Cons(3, Nil())).foreach(println _): println(2) return Cons(3, Nil()).foreach(println _) Cons(3, Nil()).foreach(println _): println(3) return Nil().foreach(println _) Nil().foreach(println _): return ()

slide-34
SLIDE 34
  • Reverses the order of elements in the list:

def reverse: LinkedList[A] = { @tailrec def inner(remaining: LinkedList[A], result: LinkedList[A]): LinkedList[A] = remaining match { case Nil() => result case Cons(h, t) => inner(t, Cons(h, result)) } inner(this, Nil()) }

reverse method

  • Example expansion:

Cons(1, Cons(2, Cons(3, Nil()))).reverse = inner(Cons(1, Cons(2, Cons(3, Nil()))), Nil()) = inner(Cons(2, Cons(3, Nil())), Cons(1, Nil())) = inner(Cons(3, Nil()), Cons(2, Cons(1, Nil()))) = inner(Nil(), Cons(3, Cons(2, Cons(1, Nil())))) = Cons(3, Cons(2, Cons(1, Nil())))

slide-35
SLIDE 35

Symbolic arithmetic expressions

slide-36
SLIDE 36

Target: Data structure for simple arithmetic expressions with variables, constants, additions, multiplications, negations Example:

  • ((2.0*x) + y)

How to define the expressions in Scala?

Symbolic arithmetic expressions

slide-37
SLIDE 37

Symbolic arithmetic expressions

  • Let define symbolic arithmetic expressions recursively:
  • 1. Every number is an expression (e.g., 1.2)
  • 2. Every variable is an expression (e.g., x)
  • 3. If e1 and e2 are expression, then the following are also

expressions: (e1 + e2), (e1 - e2), (e1 * e2), and -e1 All of the following examples are expressions:

  • Variables and numbers: 2.0, x, and y
  • Their combinations: (2.0 * x)
  • Any further combinations: ((2.0 * x) + y)

and -((2.0 * x) + y)

slide-38
SLIDE 38

Expressions, definition

abstract class Expr { /* To enable familiar infix notation */ def +(other: Expr) = Add(this, other) def -(other: Expr) = Subtract(this, other) def *(other: Expr) = Multiply(this, other) def unary_-() = Negate(this) } case class Var(name: String) extends Expr {

  • verride def toString = name }

case class Num(value: Double) extends Expr {

  • verride def toString = value.toString }

case class Multiply(left: Expr, right: Expr) extends Expr {

  • verride def toString = "(" + left + "*" + right + “)" }

case class Add(left: Expr, right: Expr) extends Expr {

  • verride def toString = "(" + left + " + " + right + “)" }

case class Subtract(left: Expr, right: Expr) extends Expr {

  • verride def toString = "(" + left + " - " + right + “)”}

case class Negate(expr: Expr) extends Expr {

  • verride def toString = " - “ + expr}
slide-39
SLIDE 39

Expressions

  • Expression, and how the objects look like in the memory:

val e = -((Num(2.0) * Var("x")) + Var("y"))

name = "y"

Var Multiply

left right name = "x"

Var

value = 2.0

Num Add

left right

Negate

expr

slide-40
SLIDE 40

Expressions, evaluation

class VariableNotAssignedException(message: String) extends java.lang.RuntimeException(message) /** * Evaluate the expression in the point p, where p is a map * associating each variable name in the expression into a Double value. */ def evaluate(p: Map[String, Double]): Double = this match { case Var(n) => p.get(n) match { case Some(v) => v case None => throw new VariableNotAssignedException(n) } case Num(v) => v case Multiply(l, r) => l.evaluate(p) * r.evaluate(p) case Add(l, r) => l.evaluate(p) + r.evaluate(p) case Subtract(l, r) => l.evaluate(p) - r.evaluate(p) case Negate(t) => -t.evaluate(p) }

slide-41
SLIDE 41

Expressions, evaluation example

val e = -((Num(2.0) * Var("x")) + Var("y")) val p = Map("x" -> 2.0, "y" -> -3.5)

  • By expanding we get:

e.evaluate(p) = ((Num(2.0) * Var("x")) + Var("y")).evaluate(p) = ((Num(2.0) * Var("x")).evaluate(p) + Var("y").evaluate(p)) = ((Num(2.0).evaluate(p) * Var("x").evaluate(p)) + Var("y").evaluate(p)) = ((2.0 * Var("x").evaluate(p)) + Var("y").evaluate(p)) = ((2.0 * 2.0) + Var("y").evaluate(p)) = (4.0 + Var("y").evaluate(p)) = (4.0 + -3.5) = 0.5

slide-42
SLIDE 42

Expressions, symbolic simplification

def simplify: Expr = { // First simplify sub-expressions val subresult = this match { case Multiply(l, r) => Multiply(l.simplify, r.simplify) case Add(l, r) => Add(l.simplify, r.simplify) case Subtract(l, r) => Subtract(l.simplify, r.simplify) case Negate(t) => Negate(t.simplify) case _ => this // Handles Var and Num } // and then the expression itself subresult match { case Multiply(Num(1.0), r) => r ... case Add(l, Num(0.0)) => l ... case _ => subresult // Could not simplify } }

slide-43
SLIDE 43

Expressions, symbolic simplification

scala> val e = Var("y")* ((Num(1.0) * Var("x")) + Num(0.0)) e: expressions.Multiply = (y*((1.0*x) + 0.0)) scala> e.simplify res2: expression.Expr = (x*y)

  • We can now automatically simplify an expression

y(1.0x)+0.0

slide-44
SLIDE 44

Solving problems recursively

slide-45
SLIDE 45

Many problems can be solved recursively (even if they are not defined recursively) This is usually done by backtracking search: Candidate solutions are built recursively, and backtracking is done if we notice that the solution is not going to work

Solving problems recursively

slide-46
SLIDE 46

The subset sum problem

slide-47
SLIDE 47

Definition (subset sum problem)

The subset sum problem

Given a set of integers S = {v1, . . . , vn} and a target number t, is there a subset R ⊆ S such that ∑

v∈R

v = t .

Examples

  • S = {-9, -3, 4, 7, 12} and t=10 ⇒ there is a

solution R= {-9, 7, 12}

  • S = {-9, -3, 4, 7, 12} and t=6 ⇒ no solution exists

Is this problem easy to solve efficiently?

slide-48
SLIDE 48

How can recursion help us to solve this problem?

The subset sum problem

slide-49
SLIDE 49

The subset sum problem

  • Example: S = {-9, -3, 4, 7, 12} and t=10
  • Start by picking arbitrary element, e.g., -9
slide-50
SLIDE 50

The subset sum problem

  • Example: S = {-9, -3, 4, 7, 12} and t=10
  • Start by picking arbitrary element, e.g., -9
  • If there is a solution R, then -9 either (1.) belongs to or

(2.) doesn’t belong to the set R:

  • 1. Since -9 doesn’t belong to R, then S \ {-9} = {-3, 4, 7, 12}

must have a subset that sums up to t

  • 2. Since -9 belongs to R, then S \ {-9} = {-3, 4, 7, 12} must

have a subset that sums up to t - (-9) = 19

slide-51
SLIDE 51

The subset sum problem

  • We are still missing the base cases, which

determine where the recursion stops

  • These are:
  • If S = ∅ and t = 0 then R = ∅ is a solution (or

more generally if t=0 then R = ∅ is a solution)

  • If S = ∅ and t ≠ 0, then there is no solution
slide-52
SLIDE 52

The subset sum problem

Does {-9,-3,7,12} have a subset with sum 10? Does {-3,7,12} have a subset with sum 10? Does {7,12} have a subset with sum 10? Does {12} have a subset with sum 10? Does {} have a subset with sum 10? No! Does {} have a subset with sum 10-12=-2? No! => No! Does {12} have a subset with sum 10-7 = 3? Does {} have a subset with sum 3? No! Does {} have a subset with sum 3-12=-9? No! => No! => No! Does {7,12} have a subset with sum 10- -3 = 13? [Many rows removed ...] => No! Does {-3,7,12} have a subset with sum 10- -9 = 19? Does {7,12} have a subset with sum 19? Does {12} have a subset with sum 19? [Few lines omitted ...] => No!
 Does {12} have a subset with sum 19-7=12? Does {} have a subset with sum 12? **No** Does {} have a subset with sum 12-12=0? **Yes** => Yes!, {12}! The branch in which we included $7$ has a solution **Yes, {7,12}** The branch in which we omitted $-3$ has a solution **Yes, {7,12}** The branch in which we included $-9$ has a solution **Yes, {-9,7,12}**

slide-53
SLIDE 53

The subset sum problem

Does {-9,-3,7,12} have a subset with sum 10? Does {-3,7,12} have a subset with sum 10? Does {7,12} have a subset with sum 10? Does {12} have a subset with sum 10? Does {} have a subset with sum 10? No! Does {} have a subset with sum 10-12=-2? No! => No! Does {12} have a subset with sum 10-7 = 3? Does {} have a subset with sum 3? No! Does {} have a subset with sum 3-12=-9? No! => No! => No! Does {7,12} have a subset with sum 10- -3 = 13? [Many rows removed ...] => No! Does {-3,7,12} have a subset with sum 10- -9 = 19? Does {7,12} have a subset with sum 19? Does {12} have a subset with sum 19? [Few lines omitted ...] => No!
 Does {12} have a subset with sum 19-7=12? Does {} have a subset with sum 12? **No** Does {} have a subset with sum 12-12=0? **Yes** => Yes!, {12}! The branch in which we included $7$ has a solution **Yes, {7,12}** The branch in which we omitted $-3$ has a solution **Yes, {7,12}** The branch in which we included $-9$ has a solution **Yes, {-9,7,12}**

slide-54
SLIDE 54

The subset sum problem

def solve(set: Set[Int], target: Int, elementSelector: (Set[Int], Int) => Int = selectElementSimple): Option[Set[Int]] = { def inner(s: Set[Int], t: Int): Option[Set[Int]] = { if (t == 0) return Some(Set[Int]()) else if (s.isEmpty) return None val e = elementSelector(s, t) val rest = s - e // Search for a solution without e val solNotIn = inner(rest, t) if (solNotIn.nonEmpty) return solNotIn // Search for a solution with e val solIn = inner(rest, t - e) if (solIn.nonEmpty) return Some(solIn.get + e) // No solution found here, backtrack return None } inner(set, target) }

  • Let the computer do the heavy lifting:
slide-55
SLIDE 55

The subset sum problem

  • The tree of recursive function call
  • execution proceed from top to bottom, left branch

before the right branch

  • The gray branch is not reached before we find the

solution

inner({-9,-3,7,12}, 10) inner({-3,7,12}, 10)

  • mit -9

inner({-3,7,12}, 19) take -9 inner({7,12}, 10)

  • mit -3

inner({7,12}, 13) take -3 inner({7,12}, 19)

  • mit -3

inner({7,12}, 22) take -3 inner({12}, 10)

  • mit 7

inner({12}, 3) take 7 inner({12}, 13)

  • mit 7

inner({12}, 6) take 7 inner({}, 10)

  • mit 12

inner({}, -2) take 12 inner({}, 3)

  • mit 12

inner({}, -9) take 12 inner({}, 13)

  • mit 12

inner({}, 1) take 12 inner({}, 6)

  • mit 12

inner({}, -6) take 12 inner({12}, 19)

  • mit 7

inner({12}, 12) take 7 inner({}, 19)

  • mit 12

inner({}, 7) take 12 inner({}, 12)

  • mit 12

inner({}, 0) take 12

slide-56
SLIDE 56

The subset sum problem

  • Adding pruning of branches to the inner function

else if (s.filter(_ > 0).sum < t || s.filter(_ < 0).sum > t) // The positive (negative) number cannot add up (down) to t return None

inner({-9,-3,7,12}, 10) inner({-3,7,12}, 10)

  • mit -9

inner({-3,7,12}, 19) take -9 inner({7,12}, 10)

  • mit -3

inner({7,12}, 13) take -3 inner({7,12}, 19)

  • mit -3

inner({7,12}, 22) take -3 inner({12}, 10)

  • mit 7

inner({12}, 3) take 7 inner({12}, 13)

  • mit 7

inner({12}, 6) take 7 inner({}, 10)

  • mit 12

inner({}, -2) take 12 inner({}, 3)

  • mit 12

inner({}, -9) take 12 inner({}, 6)

  • mit 12

inner({}, -6) take 12 inner({12}, 19)

  • mit 7

inner({12}, 12) take 7 inner({}, 12)

  • mit 12

inner({}, 0) take 12

slide-57
SLIDE 57

On computationally hard problems

  • The recursion tree in our programs (and hence the run

time) will grow exponentially with the size of the set S

  • For example, can you solve: S = {316, 5356, 190, 1261, 4474, 4159, 6238, 442,

1702, 820, 3214, 2962, 1765, 5419, 6049, 4852, 4285, 5356, 4348, 1072, 5167, 2899, 1198, 6301, 2962, 2773, 4096, 5860, 3718, 6931, 1513, 4348, 1954, 5104, 4474, 3529, 4348, 3655, 5671, 1261, 6238, 3340, 1450, 1513, 1261, 1261, 5797, 4159, 3718, 5923} and t = 92105?

  • Subset sum is an NP-complete problem
  • The correctness of the solution is easy to check
  • There is no known way of solve the problem in

polynomial time

  • If we could solve this problem in polynomial time, then

we could solve all of them in polynomial time

  • There are thousands of known NP-complete problems
  • There has been a large number of attempts to find a

polynomial time algorithm for them

slide-58
SLIDE 58

Exercises

  • Linked lists


–– Extending the linked list data structure defined in the lectures

  • Expressions continued


–– Extending the expressions data structure defined in the lectures

  • Group scheduling


–– Solving scheduling problems recursively

  • Sudoku solver


–– Solving sudoku instances recursively

  • Subset sums with dynamic programming (challenge problem)


–– In some cases dynamic programming is better solution method than recursion for subset sum