Garsia–Wachs Algorithm

Introduction

The Garsia–Wachs algorithm is an efficient algorithm for constructing optimal binary search trees and alphabetic Huffman codes in linear time. It is named after Adriano Garsia and Michelle L. Wachs, who published the relevant paper in 1977.

Problem Statement

Given an integer nn and n+1n+1 nonnegative weights w0,w1,…,wnw_{0},w_{1},\dots ,w_{n}, construct a rooted binary tree in which each of the nn internal nodes has two children, so the tree has n+1n+1 leaves. Map the input sequence of length n+1n+1 to the leaves in order. The goal is to find, among all possible trees with nn internal nodes, one minimizing the weighted sum of the path lengths from the root to the leaves.

Optimal Binary Search Trees

This problem can be viewed as constructing a binary search tree for nn ordered keys, assuming that the tree is used only to search for values absent from the tree. The nn keys divide the search space into n+1n+1 intervals, and the weight of an interval can represent the probability that the searched value lies in it. The weighted external path length determines the expected search time.

Alphabetic Huffman Codes

The same problem can be used to construct a Huffman code. It explicitly encodes n+1n+1 given values using variable-length binary strings. In this interpretation, a value's code is the sequence of left and right steps from the root to its leaf (for example, left is 00 and right is 11). Unlike a standard Huffman code, this code is alphabetic: the order of the binary codes is the same as the order of the input values. If a value's weight is its frequency in a message, the Garsia–Wachs algorithm outputs an alphabetically ordered Huffman code that minimizes the encoded message length.

Process

The Garsia–Wachs algorithm generally has three stages:

  1. Build a binary tree whose values are at the leaves; its order may initially be wrong.
  2. Compute the distance from the root to every leaf.
  3. Build another binary tree with the same leaf distances but the correct order.
Figure: garsia wachs

As shown above, the first stage builds a binary tree by repeatedly merging unordered triples from the input sequence (on the left), while the algorithm outputs a correctly ordered binary tree whose leaf heights are the same as those of the first tree.

The first stage is easier to describe if two sentinel values ∞\infty (or sufficiently large finite values) are added at the beginning and end of the sequence. Thus, in contest solutions using Garsia–Wachs for an array num\mathit{num} of length nn, we usually define num[0]=num[n+1]=∞\mathit{num}[0] = \mathit{num}[n+1] = \infty.

The first stage maintains a forest initially consisting of one-node trees, one for each non-sentinel input weight. Each tree has an associated value equal to the sum of the weights of its leaves. To maintain the sequence of these values, there is one sentinel at each end. The initial sequence is simply the input order of the leaf weights. Repeat the following steps, each of which shortens the sequence, until only one tree contains all leaves:

  • Find three consecutive weights xx, yy, zz in the sequence such that x≤zx \leq z. Such a triple always exists because the sentinel at the end is larger than any two preceding finite values.
  • Remove xx and yy from the sequence and create a new tree node as the parent of the xx and yy nodes, with value x+yx+y.
  • Reinsert the new node immediately after the nearest value before the original position of xx that is greater than or equal to x+yx+y. Such a position, measured from the original position of xx, always exists because of the left sentinel.

For an efficient implementation, the current sequence can be maintained in any balanced binary-search-tree structure. This allows xx and yy to be removed and their new parent to be inserted in logarithmic time. At every step, the weights at even indices up to yy form one decreasing sequence, and the weights at odd indices form another decreasing sequence. Therefore, the insertion position of x+yx+y can be found with two binary searches on these two decreasing sequences, each supported by a balanced tree. By linearly searching from the zz value of the previous triple, the first position satisfying x≤zx \leq z can be found in total linear time.

The proof of the third stage—that another tree with the same distances exists and gives an optimal solution—is important. However, there are several proofs and they are too involved to include here. Assuming the third stage is correct, the second and third stages are straightforward to implement in linear time. Therefore, on an input sequence of length nn, the total time complexity of the Garsia–Wachs algorithm is O(nlog⁡n)O(n\log n).

Applications

The Haskell garsia-wachs package provides a functional implementation of the Garsia–Wachs algorithm. It is mainly used to construct optimal search trees or to balance the rope data structure optimally.

Note

rope is a Haskell tool for operating on byte strings (bytestrings), with optional annotations, using a finger tree.

Examples

POJ 1738 An old Stone Game

This is an old stone-merging game. At the beginning, the player arranges nn(1≤n≤500001 \leq n \leq 50000) piles of stones in a row. The goal is to merge them into one pile. At each step, two adjacent piles may be merged into a new pile. The score is the total number of stones in the new pile. Compute the minimum possible total score.

Solution idea

Stone merging is a classic problem that can generally be solved with interval DP. When the data is large, however, such as nn(1≤n≤500001 \leq n \leq 50000) here, the Garsia–Wachs algorithm is more efficient. First initialize an array of size nn, num[n]\mathit{num}[n], with num[0]=num[n+1]=∞\mathit{num}[0] = \mathit{num}[n+1] = \infty. Then repeatedly find the smallest ii satisfying num[i−1]≤num[i+1]\mathit{num}[i-1] \leq \mathit{num}[i+1], merge num[i−1],num[i]\mathit{num}[i-1], \mathit{num}[i] into temp\mathit{temp}, and find the largest preceding jj with num[j]>temp\mathit{num}[j] > \mathit{temp}, then move temp\mathit{temp} after jj. Repeat this step until 11 pile remains. The requirement that only adjacent piles may be merged is respected: since num[j]≥num[i−1]+num[i]\mathit{num}[j]\geq \mathit{num}[i-1] + \mathit{num}[i], we may regard num[j+1]\mathit{num}[j+1] through num[i−2]\mathit{num}[i-2] as one num[mid]\mathit{num}[mid] block, so sum\mathit{sum} is necessarily merged first. Thus the rule is not violated.

ATCODER N-Slimes

NN slimes are arranged in a row. Initially, the ii-th slime from the left has size aia_{i}. Taro repeatedly combines two adjacent slimes into one until only one remains. If the new slime has size x+yx+y, where xx and yy are the sizes before the combination, and the operation costs x+yx+y. The relative order of the slimes does not change. Find the minimum possible total cost.

References and Further Reading

  1. Garsia–Wachs algorithm - Wikipedia
  2. Data.Algorithm.GarsiaWachs - Hackage Haskell
  3. garsia-wachs: A Functional Implementation of the Garsia-Wachs Algorithm
  4. Sentinel value - Wikipedia
  5. A new proof of the Garsia-Wachs algorithm

This content comes from OI Wiki: Garsia–Wachs Algorithm. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.