25-Comp-A4 Program Design and Data Structures · December 2014
Question 9 of 9: Algorithm Design — One-Pass Partition of a Binary Array
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
Notes on this paper
Paper format. 98-Comp-A4 Program Design and Data
Structures, December 2014 — 3 hours, closed book, no calculator
permitted. Nine questions, each of equal weight (20 marks); candidates answer
any six, so a complete paper is 120 marks. Pseudocode or any high-level
language is accepted, and the examiner's note states explicitly that marking
emphasises the operation of the program, not syntactic details.
All nine questions are answered below, because the whole set is
the more useful revision resource. Answers are given in C or C++ as the
question dictates; each is compilable as written, but a clear, correctly
reasoned pseudocode answer would earn the same marks.
Weiss, Data Structures and Algorithm Analysis in C, 2nd ed. —
linked lists (ch. 3), binary search trees (ch. 4).
Deitel & Deitel, C++ How to Program, 10th ed. — class
design and operator overloading (ch. 9–10), file streams (ch. 14).
Kernighan & Ritchie, The C Programming Language, 2nd ed. —
character I/O idioms (ch. 1, 7), pointers and structures (ch. 5–6).
Stroustrup, The C++ Programming Language, 4th ed. — value
semantics and const-correctness (ch. 16–18).
The Computer Engineering citation list is built around architecture and
networking texts (Patterson & Hennessy, Tanenbaum, Mano); this subject is
programming and data structures, so the works above are cited instead.
Question 9: Algorithm Design — One-Pass Partition of a Binary Array (20 marks)
Given. An array of length $n$ whose elements take only the
values 0 and 1, a worked 15-element example, and three explicit constraints: no
sorting routine, no counting pass, and one traversal only.
Find. An algorithm and program that rearranges the array
in place so that every 0 precedes every 1, within those constraints.
The array at input. The pointers begin at opposite ends and move towards each other; between them lies the unexamined region.
Approach. Run two indices inwards from the two ends,
advancing whichever already sees a correctly placed value and exchanging the pair
when both are misplaced, until the indices meet.
See why the obvious methods are excluded, and what remains.
Counting the zeros and then overwriting is banned by the question; a comparison
sort is banned as well, and would cost $O(n \log n)$ for what is really a
partition. What remains is the partition step of quicksort, specialised to a
two-valued array — and it is both faster and simpler than either
prohibited approach.
State the invariant that the algorithm maintains. Two
indices $i$ and $j$ start at the two ends, and the algorithm preserves
$$\boxed{a[0..i-1] \text{ are all } 0
\quad\text{and}\quad a[j+1..n-1] \text{ are all } 1}$$
Both regions are empty at the start, so the invariant holds trivially; the loop
only ever extends them. When $i$ and $j$ meet, the unexamined middle has
vanished and the two regions together are the whole array, which is the required
result.
Derive the three cases. At each step only two elements are
inspected. If $a[i] = 0$ it is already in the left region, so $i$ advances. Else
if $a[j] = 1$ it is already in the right region, so $j$ retreats. Otherwise
$a[i] = 1$ and $a[j] = 0$ — both are misplaced, and a single exchange puts
both where they belong, after which both indices move. Every iteration
therefore reduces the unexamined region by at least one, guaranteeing
termination.
Confirm the constraints are met. The indices $i$ and $j$
between them visit each position at most once and never reverse, so the array is
traversed exactly once: this is the “one pass” the question demands,
even though two indices are in play. Nothing is counted, nothing is compared
against a pivot, and no auxiliary array is used, so the extra space is $O(1)$
and the time is $\Theta(n)$ — optimal, since every element must be
examined at least once.
/* Move every 0 to the left and every 1 to the right, in ONE traversal.
No sort, no counting pass, O(1) extra space. */
void partition01(int a[], int n)
{
int i = 0; /* everything strictly left of i is known to be 0 */
int j = n - 1; /* everything strictly right of j is known to be 1 */
int t;
while (i < j) {
if (a[i] == 0) {
i++; /* already on the correct side */
} else if (a[j] == 1) {
j--; /* already on the correct side */
} else {
/* a[i] == 1 and a[j] == 0: one exchange puts BOTH right */
t = a[i]; a[i] = a[j]; a[j] = t;
i++;
j--;
}
}
}
Trace the given example. On
0 1 0 1 0 0 1 1 0 1 0 1 0 0 0, the pointer at the left skips the leading 0,
stops at the 1 in position 1, while the pointer at the right skips nothing and
stops at the 0 in position 14; they exchange. Three further exchanges follow,
after which the pointers meet at position 9. The array is then
$$\boxed{0\;0\;0\;0\;0\;0\;0\;0\;0\;1\;1\;1\;1\;1\;1}$$
which is the output the question prints: nine 0s followed by six 1s, matching
the nine 0s and six 1s of the input. Only four exchanges were needed.
Note what is not preserved. The partition is not
stable — the 0s and 1s do not keep their original relative order among
themselves. For a two-valued array this is invisible, since all 0s are
identical, but the distinction matters the moment the same routine is applied to
records carrying a 0/1 key alongside other data, where a stable partition would
require either extra space or a different algorithm.
The array at output, after four exchanges. The pointers meet exactly at the boundary between the nine 0s and the six 1s — the algorithm locates that boundary without ever having counted either value.
Question 9 — results
Item
Result
Algorithm
converging two-index partition (the quicksort partition step, specialised to two values)