Skip to content

Commit 65ed549

Browse files
committed
Add a solution to problem 60
1 parent 65f6985 commit 65ed549

File tree

2 files changed

+141
-12
lines changed

2 files changed

+141
-12
lines changed

opam

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ depends: [
1919
"omd" {>= "1.2.1"}
2020
"ocamlnet" {>= "4.0.1"}
2121
"conf-gnutls"
22-
"mpp" {>= "0.1.2"}
22+
"mpp" {>= "0.3.1"}
2323
"uri" {>= "1.3.11"}
2424
"syndic" {>= "1.5.2"}
2525
"cow" {>= "2.2.0"}

site/learn/tutorials/99problems.md

+140-11
Original file line numberDiff line numberDiff line change
@@ -1423,43 +1423,172 @@ List.length t;;
14231423

14241424
Consider a height-balanced binary tree of height `h`. What is the
14251425
maximum number of nodes it can contain? Clearly,
1426-
_maxN = 2<sup>`h`</sup> - 1_.
1427-
However, what is the minimum number *minN*? This question is more
1426+
max_nodes = 2<sup>`h`</sup> - 1.
1427+
1428+
```ocamltop
1429+
let max_nodes h = 1 lsl h - 1
1430+
```
1431+
1432+
However, what is the minimum number min_nodes? This question is more
14281433
difficult. Try to find a recursive statement and turn it into a function
14291434
`min_nodes` defined as follows: `min_nodes h` returns the minimum number
14301435
of nodes in a height-balanced binary tree of height `h`.
14311436

14321437
SOLUTION
14331438

1439+
> The following solution comes directly from translating the question.
14341440
> ```ocamltop
14351441
> let rec min_nodes h =
14361442
> if h <= 0 then 0
14371443
> else if h = 1 then 1
14381444
> else min_nodes (h - 1) + min_nodes (h - 2) + 1
14391445
> ```
1446+
> It is not the more efficient one however. One should use the last
1447+
> two values as the state to avoid the double recursion.
1448+
> ```ocamltop
1449+
> let rec min_nodes_loop m0 m1 h =
1450+
> if h <= 1 then m1
1451+
> else min_nodes_loop m1 (m1 + m0 + 1) (h - 1)
1452+
>
1453+
> let min_nodes h =
1454+
> if h <= 0 then 0 else min_nodes_loop 0 1 h
1455+
> ```
1456+
>
1457+
> It is not difficult to show that `min_nodes h` = F<sub>h+2‌</sub> - 1,
1458+
> where (F<sub>n</sub>) is the
1459+
> [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number).
14401460
1441-
On the other hand, we might ask: what is the maximum height H a
1442-
height-balanced binary tree with N nodes can have? `max_height n` returns
1443-
the maximum height of a height-balanced binary tree with `n` nodes.
1461+
On the other hand, we might ask: what are the minimum (resp. maximum)
1462+
height H a
1463+
height-balanced binary tree with N nodes can have?
1464+
`min_height` (resp. `max_height n`) returns
1465+
the minimum (resp. maximum) height of a height-balanced binary tree
1466+
with `n` nodes.
14441467
14451468
SOLUTION
14461469
1470+
> Inverting the formula max_nodes = 2<sup>`h`</sup> - 1, one directly
1471+
> find that Hₘᵢₙ(n) = ⌈log₂(n+1)⌉ which is readily
1472+
> implemented:
1473+
>
1474+
> ```ocamltop
1475+
> let min_height n = int_of_float(ceil(log(float(n + 1)) /. log 2.))
1476+
> ```
1477+
>
1478+
> Let us give a proof that the formula for Hₘᵢₙ is valid. First, if h
1479+
> = `min_height` n, there exists a height-balanced tree of height h
1480+
> with n nodes. Thus 2ʰ - 1 = `max_nodes h` ≥ n i.e., h ≥ log₂(n+1).
1481+
> To establish equality for Hₘᵢₙ(n), one has to show that, for any n,
1482+
> there exists a height-balanced tree with height Hₘᵢₙ(n). This is
1483+
> due to the relation Hₘᵢₙ(n) = 1 + Hₘᵢₙ(n/2) where n/2 is the integer
1484+
> division. For n odd, this is readily proved — so one can build a
1485+
> tree with a top node and two sub-trees with n/2 nodes of height
1486+
> Hₘᵢₙ(n) - 1. For n even, the same proof works if one first remarks
1487+
> that, in that case, ⌈log₂(n+2)⌉ = ⌈log₂(n+1)⌉ — use log₂(n+1) ≤ h ∈
1488+
> ℕ ⇔ 2ʰ ≥ n + 1 and the fact that 2ʰ is even for that. This allows
1489+
> to have a sub-tree with n/2 nodes. For the other sub-tree with
1490+
> n/2-1 nodes, one has to establish that Hₘᵢₙ(n/2-1) ≥ Hₘᵢₙ(n) - 2
1491+
> which is easy because, if h = Hₘᵢₙ(n/2-1), then h+2 ≥ log₂(2n) ≥
1492+
> log₂(n+1).
1493+
>
1494+
> The above function is not the best one however. Indeed, not every
1495+
> 64 bits integer can be represented exactly as a floating point
1496+
> number. Here is one that only uses integer operations:
1497+
>
1498+
> ```ocamltop
1499+
> let rec ceil_log2_loop log plus1 n =
1500+
> if n = 1 then if plus1 then log + 1 else log
1501+
> else ceil_log2_loop (log + 1) (plus1 || n land 1 <> 0) (n / 2)
1502+
>
1503+
> let ceil_log2 n = ceil_log2_loop 0 false n
1504+
> ```
1505+
>
1506+
> This algorithm is still not the fastest however. See for example
1507+
> the [Hacker's Delight](http://www.hackersdelight.org/), section 5-3
1508+
> (and 11-4).
1509+
>
1510+
> Following the same idea as above, if h = `max_height` n, then one
1511+
> easily deduces that `min_nodes` h ≤ n < `min_nodes`(h+1). This
1512+
> yields the following code:
1513+
>
1514+
> ```ocamltop
1515+
> let rec max_height_search h n =
1516+
> if min_nodes h <= n then max_height_search (h+1) n else h-1
1517+
> let max_height n = max_height_search 0 n
1518+
> ```
1519+
>
1520+
> Of course, since `min_nodes` is computed recursively, there is no
1521+
> need to recompute everything to go from `min_nodes h` to
1522+
> `min_nodes(h+1)`:
1523+
>
14471524
> ```ocamltop
1448-
> let rec max_height = function
1449-
> | 0 -> 0
1450-
> | n ->
1451-
> let h = max_height (n - 1) in
1452-
> if max_height (n - min_nodes (h - 1) - 1) = h then h + 1 else h
1525+
> let rec max_height_search h m_h m_h1 n =
1526+
> if m_h <= n then max_height_search (h+1) m_h1 (m_h1 + m_h + 1) n else h-1
1527+
>
1528+
> let max_height n = max_height_search 0 0 1 n
14531529
> ```
14541530
14551531
Now, we can attack the main problem: construct all the height-balanced
14561532
binary trees with a given number of nodes. `hbal_tree_nodes n` returns a
14571533
list of all height-balanced binary tree with `n` nodes.
14581534
1535+
SOLUTION
1536+
1537+
> First, we define some convenience functions `fold_range` that folds
1538+
> a function `f` on the range `n0`...`n1` i.e., it computes
1539+
> `f (... f (f (f init n0) (n0+1)) (n0+2) ...) n1`. You can think it
1540+
> as performing the assignment `init ← f init n` for `n = n0,..., n1`
1541+
> except that there is no mutable variable in the code.
1542+
>
1543+
> ```ocamltop
1544+
> let rec fold_range ~f ~init n0 n1 =
1545+
> if n0 > n1 then init else fold_range ~f ~init:(f init n0) (n0 + 1) n1
1546+
> ```
1547+
>
1548+
> When constructing trees, there is an obvious symmetry: if one swaps
1549+
> the left and right sub-trees of a balanced tree, we still have a
1550+
> balanced tree. The following function returns all trees in `trees`
1551+
> together with their permutation.
1552+
>
1553+
> ```ocamltop
1554+
> let rec add_swap_left_right trees =
1555+
> List.fold_left (fun a n -> match n with
1556+
> | Node(v, t1, t2) -> Node(v, t2, t1) :: a
1557+
> | Empty -> a) trees trees
1558+
> ```
1559+
>
1560+
> Finally we generate all trees recursively, using a priori the bounds
1561+
> computed above. It could be further optimized but our aim is to
1562+
> straightforwardly express the idea.
1563+
>
1564+
> ```ocamltop
1565+
> let rec hbal_tree_nodes_height h n =
1566+
> assert(min_nodes h <= n && n <= max_nodes h);
1567+
> if h = 0 then [Empty]
1568+
> else
1569+
> let acc = add_hbal_tree_node [] (h-1) (h-2) n in
1570+
> let acc = add_swap_left_right acc in
1571+
> add_hbal_tree_node acc (h-1) (h-1) n
1572+
> and add_hbal_tree_node l h1 h2 n =
1573+
> let min_n1 = max (min_nodes h1) (n - 1 - max_nodes h2) in
1574+
> let max_n1 = min (max_nodes h1) (n - 1 - min_nodes h2) in
1575+
> fold_range min_n1 max_n1 ~init:l ~f:(fun l n1 ->
1576+
> let t1 = hbal_tree_nodes_height h1 n1 in
1577+
> let t2 = hbal_tree_nodes_height h2 (n - 1 - n1) in
1578+
> List.fold_left (fun l t1 ->
1579+
> List.fold_left (fun l t2 -> Node('x', t1, t2) :: l) l t2) l t1
1580+
> )
1581+
>
1582+
> let hbal_tree_nodes n =
1583+
> fold_range (min_height n) (max_height n) ~init:[] ~f:(fun l h ->
1584+
> List.rev_append (hbal_tree_nodes_height h n) l)
1585+
> ```
1586+
14591587
Find out how many height-balanced trees exist for `n = 15`.
14601588
14611589
```ocamltop
1462-
List.length (hbal_tree_nodes 15)
1590+
List.length (hbal_tree_nodes 15);;
1591+
List.map hbal_tree_nodes [0; 1; 2; 3];;
14631592
```
14641593
14651594

0 commit comments

Comments
 (0)