Programming I, 2018-9
Lecture 12 – Notes

combinatorial search

Sometimes we may wish to generate sets of values that have a natural recursive structure. For example, we may wish to enumerate

We can accomplish this using recursion; we call this a combinatorial search.

Note: we discussed this topic in the lecture, but did not have time to cover it fully or work through exercises on this topic. So this topic will not be on the Programming I exam this year. We will return to it and cover it fully in Programming II.

Instead of full lecture notes on this topic, I will only include a few examples that we covered in class.

N-letter sequences

Here is a procedure that prints all N-letter sequences that can formed from the characters {a, b, c}.

// write all N-letter sequences, preceding each with path
procedure abc1(path: string; n: integer);
var
  c: char;
begin
  if n = 0 then
    writeln(path)
  else
    for c := 'a' to 'c' do
      abc1(path + c, n - 1);
end;

procedure abc(n: integer);
begin
  abc1('', n);
end;

subsets of a set

Here are two procedures that print all subsets of the set { 1 .. N }. They show two different ways to perform this task.

// write all subsets of {1 .. N}, preceding each with path
procedure subset1(path: string; n: integer);
var
  i: integer;
begin
  for i := n downto 1 do
    subset1(path + ' ' + intToStr(i), i - 1);
    
  writeln(path + ' }');
end;

// alternate version
procedure subset1(path: string; n: integer);
begin
  if n = 0 then writeln(path, ' }')
  else begin
    subset1(path + ' ' + intToStr(n), n - 1);
    subset1(path, n - 1);
  end;
end;

procedure subset(n: integer);
begin
  subset1('{', n,);
end;

permutations of a string

Here's a procedure to print all permutations of a string:

// delete a character from a string
function delete(s: string; i: integer): string;
begin
  exit(leftStr(s, i - 1) + rightStr(s, length(s) - i));
end;

procedure permute1(path: string; s: string);
var
  i: integer;
begin
  if s = '' then
    writeln(path)
  else
    for i := 1 to length(s) do
      permute1(path + s[i], delete(s, i));
end;

procedure permute(s: string);
begin
  permute1('', s);
end;

prefix, infix and postfix expressions

Arithmetic expressions are composed from numbers and operators that act on those numbers. In this section we will use the operators +, -, * and /, the last of which denotes integer division.

In traditional mathematical notation, these operators are infix operators: they are written between the values that they act on (which are called operands). For example, we write 2 + 2, or 8 - 7. In this last expression, 8 and 7 are the operands.

Here are some arithmetic expressions written using infix notation:

((4 + 5) * (2 + 1))

((7 / 2) - 1)

We may choose an alternative syntax that uses prefix notation, in which we write each operator before its operands. For example, we write + 2 4 to mean the sum of 2 and 4, or / 8 2 to mean 8 divided by 2. Here are the above expressions rewritten in prefix notation:

* + 4 5 + 2 1

- / 7 2 1

Or we may use postfix notation, in which operators come after both operands: we might write 4 5 + to mean the sum of 4 and 5. Here are the above expressions in postfix notation:

4 5 + 2 1 + *

7 2 / 1 -

In infix notation we must write parentheses to distinguish expressions that would otherwise be ambiguous. For example, 4 + 5 * 9 could mean either ((4 + 5) * 9) or (4 + (5 * 9)). (Another way to disambiguate expressions is using operator precedence. For example, * is generally considered to have higher precedence than +. But in this discussion we will assume that no such precedence exists.)

In prefix or postfix notation there is no need either for parentheses or operator precedence, because expressions are inherently unambiguous. For example, the prefix expression * + 4 5 9 is equivalent to ((4 + 5) * 9), and the prefix expression + 4 * 5 9 is equivalent to (4 + (5 * 9)). There is no danger of confusing these in prefix form even without parentheses.

In the next lecture we will see how to write programs that read and evaluate expressions in these various notations.

Tower of Hanoi

The Tower of Hanoi is a well-known puzzle that looks like this:

The puzzle has 3 pegs and a number of disks of various sizes. The player may move disks from peg to peg, but a larger disk may never rest atop a smaller one. Traditionally all disks begin on the leftmost peg, and the goal is to move them to the rightmost.

Supposedly in a temple in the city of Hanoi there is a real-life version of this puzzle with 3 rods and 64 golden disks. The monks there move one disk each second from one rod to another. When they finally succeed in moving all the disks to their destination, the world will end.

The world has not yet ended, so we can write a program that solves a version of this puzzle with a smaller number of disks. We want our program to print output like this:

move disk 1 from 1 to 2
move disk 2 from 1 to 3
move disk 1 from 2 to 3
move disk 3 from 1 to 2
…

To solve this puzzle, the key insight is that a simple recursive algorithm will do the trick. To move a tower of disks 1 through N from peg A to peg B, we can do the following:

  1. Move the tower of disks 1 through N-1 from A to C.

  2. Move disk N from A to B.

  3. Move the tower of disks 1 through N-1 from C to B.

The program below implements this algorithm:

procedure move(n: integer; fromPeg: integer; toPeg: integer);
var
  other: integer;
begin
  if n = 0 then exit;
  
  other := 6 - fromPeg - toPeg;
  move(n - 1, fromPeg, other);
  writeln('move disk ', n, ' from ', fromPeg, ' to ', toPeg);
  move(n - 1, other, toPeg);
end;

var
  n: integer;

begin
  readln(n);
  move(n, 1, 3);
end.

We can compute the exact number of moves required to solve the puzzle using the algorithm above. If M(n) is the number of moves to move a tower of height n, then we have the recurrence

M(n) = 2 ⋅ M(n–1) + 1

The solution to this recurrence is, exactly,

M(n) = 2n – 1

Similarly, the running time of our program above follows the recurrence

T(n) = 2 ⋅ T(n–1) + O(1)

And the program runs in time T(n) = O(2n).

It will take 264 - 1 seconds for the monks in Hanoi to move the golden disks to their destination tower. That is far more than the number of seconds that our universe has existed so far.

visual Hanoi

Here is a program that animates the Tower of Hanoi using text-mode (CRT) graphics.