Algorithm Search⌘K

Proofs: Direct-address table

These are the human proofs of Direct-address table, at full length.

The direct-address table and its sparse version implement a dictionary full

This proof shows: correctness.

Theorem 1 (plain table). Start from the table with A[k]=NILA[k] = \text{NIL} for all 0≤k<u0 \le k < u and n=0n = 0, and apply any sequence of DA-SEARCH, DA-INSERT and DA-DELETE with keys in {0,…,u−1}\{0, \ldots, u - 1\} and values other than NIL. After each operation the invariant holds:

  • (D1) D={(k,A[k]):A[k]≠NIL}D = \{(k, A[k]) : A[k] \ne \text{NIL}\} is the map that the same sequence gives on an abstract dictionary;
  • (D2) n=∣{k:A[k]≠NIL}∣n = |\{k : A[k] \ne \text{NIL}\}|.

Each search returns the value of its key in that map, or NIL.

Proof. At the start every slot is NIL, DD is the empty map and n=0n = 0, so (D1) and (D2) hold. Assume they hold before an operation on key kk, and let DD be the map at that time.

Search. Line L1 returns A[k]A[k]. If kk is a key of DD, then A[k]=D(k)A[k] = D(k) by (D1). If not, then no pair of DD has key kk, so A[k]=NILA[k] = \text{NIL} by (D1). No field changes.

Insert. Only slot kk and nn change. Line L3 sets A[k]=v≠NILA[k] = v \ne \text{NIL}, so the new set of pairs is DD with the pair of kk removed and (k,v)(k, v) added, which is the dictionary insert. If A[k]A[k] was NIL, the number of slots that are not NIL grows by one, and line L2 adds one to nn. If A[k]A[k] was not NIL, that number does not change, and line L2 does not change nn. So (D2) holds.

Delete. Only slot kk and nn change. Line L5 sets A[k]=NILA[k] = \text{NIL}, so the new set of pairs is DD without the pair of kk, if there was one, which is the dictionary delete. Line L4 lowers nn by one exactly when the slot was not NIL before, which is exactly when the number of slots that are not NIL drops by one. So (D2) holds.

By induction on the number of operations, the claim holds after each one. ■\blacksquare

Theorem 2 (sparse version). Let the arrays val\mathit{val}, idx\mathit{idx} and key\mathit{key} have arbitrary contents at creation, and let t=0t = 0. Apply any sequence of SP-SEARCH, SP-INSERT and SP-DELETE with keys in {0,…,u−1}\{0, \ldots, u - 1\}. Let SS be the set of keys of the abstract dictionary after the same sequence, with values D(k)D(k). After each operation:

  • (S1) key[0],…,key[t−1]\mathit{key}[0], \ldots, \mathit{key}[t - 1] are distinct, and {key[0],…,key[t−1]}=S\{\mathit{key}[0], \ldots, \mathit{key}[t - 1]\} = S;
  • (S2) idx[key[i]]=i\mathit{idx}[\mathit{key}[i]] = i for each 0≤i<t0 \le i < t;
  • (S3) val[k]=D(k)\mathit{val}[k] = D(k) for each k∈Sk \in S.

Moreover, under (S1) and (S2), VALID(k)(k) is true if and only if k∈Sk \in S, whatever the contents of the slots that were never written. So each search returns D(k)D(k) or NIL.

Proof. First the claim on VALID. Assume (S1) and (S2). If k∈Sk \in S, then by (S1) k=key[i]k = \mathit{key}[i] for some i<ti < t, and by (S2) idx[k]=i\mathit{idx}[k] = i. So line L6 reads ii and line L7 finds 0≤i<t0 \le i < t and key[i]=k\mathit{key}[i] = k, and returns true. Conversely, if line L7 returns true, then i=idx[k]i = \mathit{idx}[k] satisfies 0≤i<t0 \le i < t and key[i]=k\mathit{key}[i] = k, so k∈Sk \in S by (S1). This argument uses only the slots key[0…t−1]\mathit{key}[0 \ldots t-1], which (S1) describes, and not the content of idx[k]\mathit{idx}[k] beyond the value read, so garbage cannot make VALID true for a key outside SS.

At creation t=0t = 0 and S=∅S = \emptyset, so (S1) to (S3) hold, the last two vacuously. Assume they hold before an operation on key kk.

Search. By the claim, line L8 returns val[k]=D(k)\mathit{val}[k] = D(k) by (S3) when k∈Sk \in S, and NIL otherwise. No field changes.

Insert, k∈Sk \in S. VALID is true, so line L10 is skipped, and line L11 sets val[k]=v\mathit{val}[k] = v. The set SS does not change and the new value of kk is vv, so (S1) to (S3) hold.

Insert, k∉Sk \notin S. Line L10 writes key[t]=k\mathit{key}[t] = k and idx[k]=t\mathit{idx}[k] = t, then raises tt by one. Since k∉Sk \notin S, the keys key[0…t]\mathit{key}[0 \ldots t] are distinct and form S∪{k}S \cup \{k\}, which is (S1). For i<ti < t the old relation idx[key[i]]=i\mathit{idx}[\mathit{key}[i]] = i still holds, since the only slot of idx\mathit{idx} that changed is idx[k]\mathit{idx}[k], and kk differs from each key[i]\mathit{key}[i] with i<ti < t. Together with idx[k]=t\mathit{idx}[k] = t this gives (S2). Line L11 sets val[k]=v\mathit{val}[k] = v, and the other values are unchanged, so (S3) holds. The stack never overflows: by (S1), t=∣S∣≤ut = |S| \le u.

Delete, k∉Sk \notin S. VALID is false and nothing changes, which is the dictionary delete of an absent key.

Delete, k∈Sk \in S. Let i=idx[k]i = \mathit{idx}[k], so key[i]=k\mathit{key}[i] = k by (S2), and let j=key[t−1]j = \mathit{key}[t - 1]. Line L14 sets key[i]=j\mathit{key}[i] = j and idx[j]=i\mathit{idx}[j] = i and lowers tt by one. If i=t−1i = t - 1, then j=kj = k, and the effect is to drop the last stack entry, which is kk. If i<t−1i < t - 1, the entry kk at position ii is replaced by the last entry jj, and the last position leaves the used part. In both cases key[0…t−2]\mathit{key}[0 \ldots t-2] holds the old keys without kk, each once, which is (S1) for S∖{k}S \setminus \{k\}. For (S2), every key other than jj keeps its position and its slot of idx\mathit{idx}, and jj, if it is still stored, is now at position ii with idx[j]=i\mathit{idx}[j] = i. The values of the other keys are unchanged, so (S3) holds.

By induction on the number of operations, (S1) to (S3) hold after each one, and with the claim on VALID each operation answers as the dictionary does. ■\blacksquare

Back to Direct-address table