Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2....
Transcript of Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2....
011101110101101111010101010100101011010011010101110101010100101010101010101010101010
101010111101010101010101110101010111010110101011010101101010111010101011101010101101011101110101101011101101010111110101010101000101010101010101101010111010101010100101
Programming abstractions and analysis –– recursion
Mikko KiveläDepartment of Computer Science
Aalto University
CS-A1120 Programming 215 April 2020
Lecture notes based on material created by Tommi Junttila & Petteri Kaski
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?
RecursionDefinition: see recursion
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
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
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) }
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
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
Tail calls and recursion
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 = 1n × (n − 1)! when n > 2
scala> fact(100000)java.lang.StackOverflowError
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
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
Definition
Tail calls
Function call in a method is a tail call if it is the last operation 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
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
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 }
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
@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
Recursively defined data structures
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 of more general tree data structure)
Linked lists
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
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)
Linked lists, the first implementationabstract 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}
object Nil { def apply[A]() = new Nil[A]()}
object Cons { def apply[A](head : A, tail : LinkedList[A]) = new Cons(head, tail)
}
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
tailhead
Nil
5
Cons
tailhead
4 −7
l Cons
tail
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
tailhead
Nil
5
Cons
tailhead
Cons
tailhead
3
4 −7
l
m
Cons
tailhead
• Note that the list l is not copied
• Adding to (and removing from) the beginning of the list is efficient
Linked lists, contains method• Tests if the list contains e
• Implemented recursively:A. empty list does not contain eB. if head of the list is e, then it contains eC. 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
Linked lists, length method• Calculates the length of the list
• Implemented recursively:A. empty list has length 0B. 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}
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 + tmp13
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
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
}
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 ofempty list")
}case class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] { def isEmpty = false}
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)}
• 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 ()
• 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())))
Symbolic arithmetic expressions
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
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)
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 { override def toString = name }
case class Num(value: Double) extends Expr { override def toString = value.toString }
case class Multiply(left: Expr, right: Expr) extends Expr { override def toString = "(" + left + "*" + right + “)" }
case class Add(left: Expr, right: Expr) extends Expr { override def toString = "(" + left + " + " + right + “)" }
case class Subtract(left: Expr, right: Expr) extends Expr { override def toString = "(" + left + " - " + right + “)”} case class Negate(expr: Expr) extends Expr { override def toString = " - “ + expr}
Expressions• Expression, and how the objects look like in the memory:
val e = -((Num(2.0) * Var("x")) + Var("y"))
name = "y"
VarMultiply
left
right
name = "x"
Var
value = 2.0
Num
Add
left
right
Negate
expr
Expressions, evaluationclass 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)}
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
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}
}
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.simplifyres2: expression.Expr = (x*y)
• We can now automatically simplify an expression y(1.0x)+0.0
Solving problems recursively
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
The subset sum problem
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?
How can recursion help us to solve this
problem?
The subset sum problem
The subset sum problem• Example: S = {-9, -3, 4, 7, 12} and t=10
• Start by picking arbitrary element, e.g., -9
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
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
The subset sum problemDoes {-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}**
The subset sum problemDoes {-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}**
The subset sum problemdef 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:
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)
omit -9
inner({-3,7,12}, 19)
take -9
inner({7,12}, 10)
omit -3
inner({7,12}, 13)
take -3
inner({7,12}, 19)
omit -3
inner({7,12}, 22)
take -3
inner({12}, 10)
omit 7
inner({12}, 3)
take 7
inner({12}, 13)
omit 7
inner({12}, 6)
take 7
inner({}, 10)
omit 12
inner({}, -2)
take 12
inner({}, 3)
omit 12
inner({}, -9)
take 12
inner({}, 13)
omit 12
inner({}, 1)
take 12
inner({}, 6)
omit 12
inner({}, -6)
take 12
inner({12}, 19)
omit 7
inner({12}, 12)
take 7
inner({}, 19)
omit 12
inner({}, 7)
take 12
inner({}, 12)
omit 12
inner({}, 0)
take 12
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 treturn None
inner({-9,-3,7,12}, 10)
inner({-3,7,12}, 10)
omit -9
inner({-3,7,12}, 19)
take -9
inner({7,12}, 10)
omit -3
inner({7,12}, 13)
take -3
inner({7,12}, 19)
omit -3
inner({7,12}, 22)
take -3
inner({12}, 10)
omit 7
inner({12}, 3)
take 7
inner({12}, 13)
omit 7
inner({12}, 6)
take 7
inner({}, 10)
omit 12
inner({}, -2)
take 12
inner({}, 3)
omit 12
inner({}, -9)
take 12
inner({}, 6)
omit 12
inner({}, -6)
take 12
inner({12}, 19)
omit 7
inner({12}, 12)
take 7
inner({}, 12)
omit 12
inner({}, 0)
take 12
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
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