(* Find the optimally structured binary search tree for n keys. Known are the search frequencies of the keys, b[i] for key[i], and the frequencies of searches with arguments that are not keys (represented in the tree). a[i] is the frequency of an argument lying between key[i-1] and key[i]. Use Knuth's algorithm, "Acta informatica" 1, 1, 14-25 (1971). The following example uses Modula keywords as keys. *) MODULE optimaltree; FROM InOut IMPORT Read, Write, WriteLn, WriteString, WriteCard, OpenInput, Done; FROM RealInOut IMPORT WriteReal; FROM Storage IMPORT ALLOCATE, DEALLOCATE; IMPORT Terminal; CONST n = 29; (* # of keys *) kln = 9; (* max key length *) TYPE index = [0..n]; alfa = ARRAY [0..kln] OF CHAR; VAR ch,tch: CHAR; k1,k2,i,j,k: CARDINAL; id,buf: alfa; key: ARRAY [1..n] OF alfa; a: ARRAY index OF CARDINAL; b: ARRAY index OF CARDINAL; p,w: ARRAY index,index OF CARDINAL; r: ARRAY index,index OF index; suma,sumb: CARDINAL; PROCEDURE balltree(i,j: index): CARDINAL; VAR k,tmp: CARDINAL; BEGIN k := (i+j+1) DIV 2; r[i,j] := k; IF i >= j THEN tmp := b[k] ELSE tmp := balltree(i,k-1) + balltree(k,j) + w[i,j] END; RETURN tmp END balltree; PROCEDURE copystring(VAR from,to: alfa); VAR i: CARDINAL; BEGIN FOR i := 0 TO kln DO to[i] := from[i] END END copystring; PROCEDURE compalfa(a,b:alfa):INTEGER; VAR i,j: INTEGER; BEGIN i := 0; j := 0; LOOP IF CAP(a[i]) < CAP(b[i]) THEN j := -1; EXIT ELSIF CAP(a[i]) > CAP(b[i]) THEN j := 1; EXIT ELSE INC(i) END; IF i > kln THEN EXIT END END; RETURN j; END compalfa; PROCEDURE opttree; VAR x,min: CARDINAL; i,j,k,h,m: index; BEGIN j := 0; FOR i := 0 TO n DO p[i,i] := w[i,i] END; (* width of tree h = 0 *) FOR i := 0 TO n-1 DO INC(j); p[i,j] := p[i,i] + p[j,j]; r[i,j] := j END; FOR h := 2 TO n DO FOR i := 0 TO n-h DO j := i + h; m := r[i,j-1]; min := p[i,m-1] + p[m,j]; FOR k := m+1 TO r[i+1,j] DO x := p[i,k-1] + p[k,j]; IF x < min THEN m := k; min := x END END; p[i,j] := min + w[i,j]; r[i,j] := m END END END opttree; PROCEDURE printtree; CONST lw = 120; TYPE ref = POINTER TO node; lineposition = [0..lw]; node = RECORD key: alfa; pos: lineposition; left,right,link: ref END; VAR q,q1,q2,root,current,next: ref; i,k: CARDINAL; u,u1,u2,u3,u4: lineposition; PROCEDURE tree(i,j: index): ref; VAR p: ref; BEGIN IF i = j THEN p := NIL ELSE NEW(p); p^.left := tree(i,r[i,j]-1); p^.pos := TRUNC((FLOAT(lw)-FLOAT(kln))*FLOAT(k)/FLOAT(n-1)) + (kln DIV 2); INC(k); p^.key := key[r[i,j]]; p^.right := tree(r[i,j],j) END; RETURN p END tree; BEGIN k := 0; root := tree(0,n); current := root; root^.link := NIL; next := NIL; WHILE current # NIL DO FOR i := 1 TO 3 DO q := current; REPEAT u := 0; u1 := q^.pos; REPEAT Write(' '); INC(u) UNTIL u = u1; Write(':'); INC(u); q := q^.link UNTIL q = NIL; WriteLn; END; (* now print master line; descending from nodes on current list collect their descendants and form next list *) q := current; u := 0; REPEAT copystring(q^.key,buf); (* center key about pos *) i := kln; WHILE buf[i] = ' ' DO DEC(i) END; u2 := q^.pos - ((i-1) DIV 2); u3 := u2 + i + 1; q1 := q^.left; q2 := q^.right; IF q1 = NIL THEN u1 := u2 ELSE u1 := q1^.pos; q1^.link := next; next := q1 END; IF q2 = NIL THEN u4 := u3 ELSE u4 := q2^.pos + 1; q2^.link := next; next := q2 END; i := 0; WHILE u < u1 DO Write(' '); INC(u); END; WHILE u < u2 DO Write('-'); INC(u); END; WHILE u < u3 DO Write(buf[i]); INC(i); INC(u); END; WHILE u < u4 DO Write('-'); INC(u); END; q := q^.link UNTIL q = NIL; WriteLn; (* now invert next list AND make it current list *) current := NIL; WHILE next # NIL DO q := next; next := q^.link; q^.link := current; current := q END END END printtree; BEGIN (* initialize table of keys and counters *) OpenInput('MOD'); key[ 1] := "ARRAY "; key[ 2] := "BEGIN "; key[ 3] := "BY "; key[ 4] := "CASE "; key[ 5] := "CONST "; key[ 6] := "DIV "; key[ 7] := "DO "; key[ 8] := "ELSE "; key[ 9] := "END "; key[10] := "FOR "; key[11] := "FROM "; key[12] := "IF "; key[13] := "IMPORT "; key[14] := "IN "; key[15] := "MOD "; key[16] := "MODULE "; key[17] := "NIL "; key[18] := "OF "; key[19] := "PROCEDURE "; key[20] := "RECORD "; key[21] := "REPEAT "; key[22] := "SET "; key[23] := "THEN "; key[24] := "TO "; key[25] := "TYPE "; key[26] := "UNTIL "; key[27] := "VAR "; key[28] := "WHILE "; key[29] := "WITH "; FOR i := 1 TO n DO a[i] := 0; b[i] := 0 END; FOR i := 1 TO n DO FOR j := 1 TO n DO w[i,j] := 0 END END; b[0] := 0; k2 := kln; (* scan input text and determine a and b *) LOOP Read(ch); IF NOT Done THEN EXIT END; IF (CAP(ch) >= 'A') AND (CAP(ch) <= 'Z') THEN k1 := 0; REPEAT IF k1 <= kln THEN buf[k1] := ch; INC(k1); END; Read(ch) UNTIL NOT (((CAP(ch) >= 'A')AND(CAP(ch) <= 'Z')) OR ((ch >= '0')AND(ch <= '9'))); DEC(k1); IF k1 >= k2 THEN k2 := k1 ELSE REPEAT buf[k2] := ' '; DEC(k2) UNTIL k2 = k1 END; copystring(buf,id); i := 1; j := n; REPEAT k := (i+j) DIV 2; IF compalfa(key[k],id) <= 0 THEN i := k+1 END; IF compalfa(key[k],id) >= 0 THEN j := k-1 END UNTIL i > j; IF compalfa(key[k],id) = 0 THEN INC(a[k]) ELSE k := (i+j) DIV 2; INC(b[k]) END ELSIF ch = '"' THEN REPEAT Read(ch) UNTIL ch = '"' END END; WriteString(' keys and frequencies of occurrence: '); WriteLn; suma := 0; sumb := 0; FOR i := 1 TO n DO suma := suma + a[i]; sumb := sumb + b[i]; WriteCard(b[i-1],6); WriteCard(a[i],6); Write(' '); WriteString(key[i]); WriteLn END; WriteCard(b[n],6); WriteLn; WriteString(' ------ ------'); WriteLn; WriteCard(suma,6); WriteCard(sumb,6); WriteLn; (* compute w from a and b *) FOR i := 0 TO n DO w[i,i] := b[i]; FOR j := i+1 TO n DO w[i,j] := w[i,j-1] + a[j] + b[j] END END; WriteLn; WriteString(' average path length of balanced tree = '); WriteReal(FLOAT(balltree(0,n))/FLOAT(w[0,n]),6); printtree; WriteLn; opttree; WriteLn; WriteString(' average path length of optimal tree = '); WriteReal(FLOAT(p[0,n])/FLOAT(w[0,n]),6); printtree; WriteLn; (* now considering keys only, setting b = 0 *) FOR i := 0 TO n DO w[i,i] := 0; FOR j := i+1 TO n DO w[i,j] := w[i,j-1] + a[j] END END; opttree; WriteLn; WriteString(' optimal tree considering keys only '); printtree; END optimaltree.