Chapter3 - Counting 101 - Recursion and Recurrence(1)
Book - Python Algorithms by Magnus Lie Hetland
Chapter 3 - Counting 101
Simple example of how to recursively sum a sequence:
def S(seq, i=0):
if i == len(seq): return 0
return S(seq, i+1) + seq[i]
Undertanding how this function works and figuring out its running time are closely related tasks.
The parameter i indicates where the sum is to start. If itβs beyond the end of the sequence, the function returns 0.
Otherwise, it adds the value at position i to the sum of the remaining sequence.
We have a constant amount of work in each execution of s, excluding the recursive call, and itβs executed once for each item in the sequence, so itβs pretty obvious that the running time is linear.
def T(seq, i=0):
if i == len(seq): return 1
return T(seq, i+1) + 1
This T function has virtually the same structure as s.
Instead of returning a solution to a subproblem, like s does, it returns the cost of finding that solution.
In this case, Iβve just counted the number of times the if statement is executed. In a more mathematical setting, you would count any relevant operations and use \(\theta(1)\) instead of 1.
>>> seq = range(1,101)
>>> s(seq)
5050
>>> T(seq)
101
>>> for n in range(100):
... seq = range(n)
... assert T(seq) == n+1
There are no errors, so the hypothesis does seem sort of plausible.
Doing It By Hand
To describe the running time of recursive algorithms mathematically, we use recursive equations, called recurrence relations (μ νμ).
If our recursive algorithm is like S in the previous section, then the recurrence relation is defined somewhat like T.
we implicitly assume that T(k) = Ξ(1), for some constant k. That means we can ignore the base cases when setting up our equation (unless they donβt take a constant amount of time), and for S, our T can be defined as follows:
\[ T(n) = T(n-1) + 1 \]
This means that the time it takes to compute S(seq, i), which is T(n), is equal to the time required for the recursive call S(seq, i+1), which is T (nβ1), plus the time required for the access seq[i], which is constant, or Ξ(1).
\(\theta(n) = \theta(n-1) + \theta(1)\).
By removing constant \(\theta(1)\),
we can get \(\theta(n) = \theta(n-1)\)
As we know that \(T(n) = T(n-1)+1\), replace n with n-1, then:
\[\begin{align} T(n) & = T(n-1) + 1 \\ & = T(n-2) + 1 + 1 \\ & = T(n-3) + 1 + 1 + 1 \\ \end{align}\]The fact that \(T(nβ2) = T (nβ3) + 1\) (the two boxed expressions) again follows from the original recurrence relation. Itβs at this point we should see a pattern: Each time we reduce the parameter by one, the sum of the work (or time) weβve unraveled (outside the recursive call) goes up by one.
If we unravel T (n) recursively i steps, we get the following:
\[ T(n) = T(n-i) + i\]
where the level of recursion is expressed as a variable.
What we do is go right up to the base case and try to make \(T(nβi)\) into \(T(1)\), because we know, or implicitly assume, that \(T(1)\) is \(\theta(1)\), which would mean we had solved the entire thing. And we can easily do that by setting i = nβ1:
\[\begin{align} T(n) & = T(n-(n-1)) + (n-1)\\ & = T(1) + n - 1\\ & = \theta(1) + n - 1\\ & = \theta(n)\\ \end{align}\]Found that s has a linear running time.
Some Recurrences [Table 3-1]
[Table 3-1]
β Caution β
This method, called the method of repeated substitutions (or sometimes the iteration method ), is perfectly valid, if youβre careful. However, itβs quite easy to make an unwarranted assumption or two, especially in more complex recurrences. This means you should probably treat the result as a hypothesis and then check your answer using the techniques described in the section βGuessing and Checkingβ later in this chapter.
Recurrence 5 [Table 3-1]
a perfect binary tree
Test
Unraveling Recurrence
This stepwise unraveling (or repeated substitution) is just the first step of our solution method. The general approach is as follows:
- Unravel the recurrence until you see a pattern.
- Express the pattern (usually involving a sum), using a line number variable, i.
- Choose i so the recursion reaches its base case (and solve the sum).
The first step is what we have done already. Letβs have a go at step 2:
\[ T(n) = T(n/2^{i}) + \sum_{k=1}^{i}1 \]
For each unraveling (each line further down), we halve the problem size (that is, double the divisor) and add another unit of work (another 1).
We know we have i ones, so the sum is clearly just i. Iβve written it as a sum to show the general pattern of the method here. \(\sum_{k=1}^{i}1 = i\).
To get to the base case of the recursion, we must get \(T (n/2^{i} )\) to become, say, \(T(1)\). That just means we have to halve our way from n to 1, which should be familiar by now: The recursion height is logarithmic, or \(i = \log n\).
Insert that into the pattern, and you get that \(T(n)\) is, indeed, \(\theta(\log n)\).
Recurrence 6 [Table 3-1]
\(\begin{align} T(n) & = 2T(n/2) + n \\ & = 2{2T(n/4) + n/2} + n \\ & = 2(2\{2T(n/8) + n/4\} + n/2) + n \\ ... \\ & = 2^{i}T(n/2^{i}) + n\cdot i \\ \end{align}\)
Set \(i = logn\). Assuming that \(T(1) = 1\), we get the following:
\[ T(n) = 1 + \sum_{k=0}^{\log n-1}(n/2^{k}) = \sum_{k=0}^{\log n}(n/2^{k}) \]
Assert (κ°μ μ€μ λ¬Έ)
assertλ λ€μ μ‘°κ±΄μ΄ Trueκ° μλλ©΄ AssertErrorλ₯Ό λ°μνλ€.
Assert μ¬μ©νλ μ΄μ :
μ΄λ€ ν¨μλ μ±λ₯μ λμ΄κΈ° μν΄ λ°λμ μ μλ§μ μ λ ₯λ°μ μ²λ¦¬νλλ‘ λ§λ€ μ μλ€. μ΄λ° ν¨μλ₯Ό λ§λ€κΈ° μν΄μλ λ°λμ ν¨μμ μ μλ§ λ€μ΄μ€λμ§ νμΈν νμκ° μλ€. μ΄λ₯Ό μν΄ ifλ¬Έμ μ¬μ©ν μλ μκ³ βμμΈ μ²λ¦¬βλ₯Ό μ¬μ©ν μλ μμ§λ§ βκ°μ μ€μ λ¬Έβμ μ¬μ©νλ λ°©λ²λ μλ€.
μλ μ½λλ ν¨μ μΈμκ° μ μμΈμ§ νμΈνλ μ½λμ΄λ€.
lists = [1, 3, 6, 3, 8, 7, 13, 23, 13, 2, 3.14, 2, 3, 7]
def test(t):
assert type(t) is int, 'μ μ μλ κ°μ΄ μλ€'
for i in lists:
test(i)
#κ²°κ³Ό
AssertionError: μ μ μλ κ°μ΄ μλ€
Assert format
assert 쑰건, βλ©μμ§β
βλ©μμ§βλ μλ΅ν μ μλ€.
assertλ κ°λ°μκ° νλ‘κ·Έλ¨μ λ§λλ κ³Όμ μ κ΄μ¬νλ€. μνλ 쑰건μ λ³μ κ°μ 보μ¦λ°μ λκΉμ§ assertλ‘ ν
μ€νΈ ν μ μλ€.
μ΄λ λ¨μν μλ¬λ₯Ό μ°Ύλκ²μ΄ μλλΌ κ°μ 보μ¦νκΈ° μν΄ μ¬μ©λλ€.
μλ₯Ό λ€μ΄ ν¨μμ μ
λ ₯ κ°μ΄ μ΄λ€ 쑰건μ μ°Έμμ 보μ¦νκΈ° μν΄ μ¬μ©ν μ μκ³ ν¨μμ λ°ν κ°μ΄ μ΄λ€ 쑰건μ λ§μ‘±νλλ‘ λ§λ€ μ μλ€. νΉμ λ³μ κ°μ΄ λ³νλ κ³Όμ μμ νΉμ λΆλΆμ λ°λμ μ΄λ€ μμμ μνλ κ²μ 보μ¦νκΈ° μν΄ κ°μ μ€μ λ¬Έμ ν΅ν΄ νμΈ ν μλ μλ€.
μ΄μ²λΌ μ€μλ₯Ό κ°μ ν΄ κ°μ 보μ¦νλ λ°©μμΌλ‘ μ½λ© νκΈ° λλ¬Έμ μ΄λ₯Ό βλ°©μ΄μ νλ‘κ·Έλλ°βμ΄λΌ λΆλ₯Έλ€.
>>> assert 2 + 2 == 4
>>> assert 2 + 2 == 3
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AssertionError
>>> assert 1 == False, "That can't be right."
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AssertionError: That can't be right.
Recurrence Relation (μ νμ)
In mathematics, a recurrence relation is an equation that recursively defines a sequence or multidimensional array of values, once one or more initial terms are given; each further term of the sequence or array is defined as a function of the preceding terms.
python assert: https://wikidocs.net/21050
docs: https://python-reference.readthedocs.io/en/latest/docs/statements/assert.html
recurrence relation: https://en.wikipedia.org/wiki/Recurrence_relation
Leave a comment