A Tale of two tournaments
Although there are many types of tournaments, let's consider two rather common ones, with rather catchy names.
These are the round-robin tournament and the knockout tournament.
In a round-robin tournament(or, specifically, a single round-robin tournament), each contestant meets each of the others in turn. The question then becomes, how many matches or fixtures do we need, if we have, for example, n knights jousting? In a knockout tournament, the competitors are arranged in pairs, and only the winner from each pair goes on to the next round. Here there are more questions to ask: for n knights, how many rounds do we need, and how many matches will there be, in total?
Shaking Hands
The round-robin problem is exactly equivalent to another well-known puzzler: if you have n algorists meeting at a conference and they all shake hands, how many handsshakes do you get? Or, equivalently, how many edges are there in a complete graph with n nodes? It's the same count you get in all kinds of "all against all" situations. For example, if you have n locations on a map and want to find the two that are closest to each other, the simple (brute-force) approach would be compare all points with all others.
The Hare and the tortoise
Let's say our knights are 100 in number and that the tournament staff are still a bit tired from last year's round robin.(Quite understandable, as there would have been 4950 matches.) They decide to introduce the (more efficient) knockout system and want to know how many matches they'll need.
Now comes the blindingly obvious part: in each match, one knight is knocked out. All except the winner are knocked out, so we need n-1 matches to leave only one standing.(The tournament structure is illustrated as a rooted tree in Figure, where each leaf is a knight and each internal node represents a match).
The upper limit, h-1, is the number of rounds.( so 2h =n).
Here's the first lesson about doubling, then: a perfectly balanced binary tree(that is , a rooted tree where all internal nodes have two children and all leaves have the same depth) has n-1 internal nodes.
The hare and the tortoise are meant to represent the width and height of the tree, respectively.
h = lgn
n = 2h
Let's do a game I like to call "think of a particle.". I think of one of the particles in the visible universe, and you try to guess which one, using only yes/no question.
By asking only yes/no questions, you can pinpoint any particle in the observable universe in about five minutes! This is a class example of why logarithmic algorithms are so super-sweet.
This is an example of bisection, or binary search, one of the most important and well-known logarithmic algorithms.
By now, I hope you're starting to see how exponentials and logarithms are the inverses of one another.
At level 1, there are two node, each of which has n/2 tokens; at level 2, we have four nodes, with n/4 tokens, and so forth. The sum across any row is always n.
The string length k, will be the height of the tree, and the number of possible strings will equal the number of leaves, 2k.
Another, more direct way to see this is to consider the number of possibilities at each step: the first bit can be zero or one, and for each of these values, the second also has two possibilites, and so forth. It's like k nested for loops, each running two iterations, the total count is still 2k.
Recursion
def S(seq,i=0): if i==len(seq): return 0 return S(seq,i+1)+seq[i]
sample call
seq=range(100)
S(seq)
Some basic Recurrences with Solutions, as well as some sample applications
# | Recurrence | Solution | Example Applications |
1 | T(n) = T(n-1) + 1 | Θ(n) | Processing a sequence, for example, with reduce |
2 | T(n) = T(n-1) + n | Θ(n2) | Handshake problems |
3 | T(n) = 2T(n-1) + 1 | Θ(2n) | Towers of Hanoi |
4 | T(n) = 2T(n-1) + n | Θ(2n) | |
5 | T(n) = T(n/2) + 1 | Θ(lgn) | Binary search |
6 | T(n) = T(n/2) + n | Θ(n) | Randomized Select, average case. |
7 | T(n) = 2T(n/2) + 1 | Θ(n) | Tree Traversal |
8 | T(n) = 2T(n/2) + n | Θ(nlgn) | Sorting by divide and conquer |
two algorithms to show python' simplicity
def gsort(seq): i =0 while i< len(seq): if i==0 or seq[i-1]<=seq[i]: i +=1 else: seq[i],seq[i-1] = seq[i-1],seq[i] i -=1
mergesort
def mergesort(seq): mid = len(seq)//2 lft, rgt = seq[:mid], seq[mid:] if len(lft) > 1: lft = mergesort(lft) if len(rgt) > 1: rgt = mergesort(rgt) res = [] while lft and rgt: if lft[-1] >= rgt[-1]: res.append(lft.pop()) else: res.append(rgt.pop()) res.reverse() return (lft or rgt) + res