
(*
title    message decoding by optimal binary search tree building
         using the hu-tucker algorithm
         cs 321 homework 5
author   robert a. van valzah   03/31/80


this  program will build an optimal binary search tree given
a set of characters and frequencies of occurance.  the  tree
is constructed using the hu-tucker algorithm (see knuth, the
art of computer programming, volume 3/sorting and searching,
pp.  439-446).  an input sequence of 1's and 0's is then de-
coded into a sequence of characters  using  this  tree.  the
resulting message is printed.
*)


const
   nmax     = 30; (* max number of characters *)
   rl       = 9;  (* record length in words *)
   dim      = 810;(* nmax * rl * 3 *)
   char     = 0;  (* character value offset *)
   freq     = 1;  (* character frequence offset *)
   lst      = 2;  (* pointer to left  subtree offset *)
   rst      = 3;  (* pointer to right subtree offset *)
   lforst   = 4;  (* pointer to left  brother offset *)
   rforst   = 5;  (* pointer to right brother offset *)
   lev      = 6;  (* node level number *)
   lexp     = 7;  (* pointer to lexicographic predecessor *)
   lexs     = 8;  (* pointer to lexicographic successor *)

   nil      = 0;  (* zeroth element never used *)
   sent     = '$';(* sentinal character *)
   maxint   = 32767; (* kludge cause not defined by compiler *)

type
   ary      = array[0..dim] of word;
   boolean  = (false, true); (* kludge till compiler is done *)
   
var         (* global variables *)
   h        : ary;  (* the heap *)
   hp       : word; (* the heap pointer *)
   lmost,
   rmost    : word; (* left and right most ends of the list *)
   lexfirst : word; (* pointer to first node in lex order *)

procedure new(var p:word);

   begin
   hp:=hp+1;
   p:=hp*rl;
   if (p>dim-rl)
      then put#1('heapover')
   end; (* procedure new *)


(*
read  a  sequence of characters and weights from the standard
input file and create a node for each  pair.  the  nodes  are
linked into a doubly linked list to form a forest as they are
read.
*)
procedure readtree;

   var
      ch  : word;  (* node value *)
      frq : word;  (* frequency *)
      p   : word;  (* pointer to new node *)
      prev: word;  (* pointer to previous node read (for linking) *)

   procedure readnode;

      var
         c : word;

      begin
      get#0(ch);    (* get node value character *)
      if (ch<>sent)
         then begin
            get#0(c);
            while (c=' ') do get#0(c);
            frq:=0;
            while (c>='0') and (c<='9') do begin
               frq:=frq*10+c-'0';
               get#0(c)
               end (* while *)
            end;
      repeat get#0(c) until (c=10) (* ignore till lf found *)
      end; (* readnode *)

   begin
   readnode;        (* readln(ch, frq); *)
   prev:=nil;       (* no left forest for first node *)
   repeat
      new(var p);
      if (prev=nil) then lmost:=p; (* record pointer to first node *)
      h[p+char  ]:=ch;
      h[p+freq  ]:=frq;
      h[p+lst   ]:=nil;  (* leaves have no subtrees *)
      h[p+rst   ]:=nil;
      h[p+lforst]:=prev; (* link to last node read created *)
      h[p+lexp  ]:=prev; (* predecessor is also last node created *)
      if (prev<>nil) then begin (* on all but first node . . . *)
         h[prev+rforst  ]:=p;   (* make previous right forest pointer and *)
         h[prev+lexs    ]:=p    (* lexicographic successor point to the new node *)
         end;
      prev:=p;
      readnode
   until (ch=sent);

   (* done reading nodes *)
   rmost:=p;          (* record pointer to right most node *)
   h[p+rforst]:=nil;  (* right most node has no right brother *)
   h[p+lexs  ]:=nil   (* right most node has no lexicographic successor *)
   end; (* procedure readtree *)


(*
given a forest of trees (all leaves when we start), build them
into  a  single tree using phase 1 of the hu-tucker algorithm.
the root of the resultant tree will be in lmost on exit.

the algorithm is implemented using two internal procedures.the
first (picklr) chooses two  trees  for  combination,  and  the
second  (combinelr)  combines the two chosen trees to form new
internal node in the final  tree.  this  process  is  repeated
unitl the forest contains only one tree.
*)
procedure build1tree;

   var left, rite : word; (* pointers to nodes to be combined *)


   (*
   pick  two  trees from the forest which satisfy the following
   rules:

   let i and j be pointers to the left and right trees

   i)   no external nodes occur between i and j.

   ii)  the  sum of the weights of i and j is minimal for all i
        and j satisfying rule (i).

   iii) the  index i is minimal for all i satisfying rules (i),
        (ii).

   iv)  the  index j is minimal for all j satisfying rules (i),
        (ii), (iii).

   pointers to the two trees chosen will be left  in  left  and
   rite (respectivly).

   one  internal  procedure  is used to compare the minimum sum
   found so far against the sum of the frequencies of the trees
   under consideration.
   *)
   procedure picklr;

      var i,j : word; (* pointers to left and right nodes which
                            are mininimum pair candidates *)
          minsum : word; (* mininimum sum found so far *)


      (*
      compare  the sum of the frequencies of nodes i and j.  if
      their sum is less than the minimum  found  so  far,  then
      record  the  new  minimum (in minsum) and the position of
      i and j as the two best candidates for combining.
      *)
      procedure takemin;

         begin
         if (h[i+freq]+h[j+freq]<minsum) then begin
            minsum:=h[i+freq]+h[j+freq];
            rite:=j; left:=i
            end
         end; (* procedure takemin *)


      begin (* procedure picklr *)
      i:=lmost;         (* start with leftmost tree in forest *)
      minsum:=maxint;
      while (h[i+rforst]<>nil) do begin (* more i's to test *)
         j:= h[i+rforst];

         (* compare to internal nodes till exeternal is found *)
         while (h[j+char]=sent) do begin
            takemin;
            j:=h[j+rforst] (* on to the next tree *)
            end;

         (* j now points to only external node candidate *)
         takemin;
         i:=h[i+rforst ]     (* move to next tree in forest *)
         end (* while not out of i's *)
      end; (* procedure picklr *)


   (*
   combine  the two trees pointed to by left and rite to form a
   new internal node in the final  tree.  link  this  new  node
   into  the  existing  forest  in place of the left tree.  the
   rite tree is  deleted  from  the  forest.  pointers  to  the
   leftmost  and  rightmost  (lmost and rmost, respectivly) are
   updated in the process.  the frequency of the new  new  node
   becomes the sum of the frequencies of its offspring.
   *)
   procedure combinelr;

      var newn : word; (* pointer to new node created *)

      begin
      new(var newn);  (* get pointer to new node on heap *)
      h[newn+char]:=sent;  (* init all internal nodes to sent char *)
      h[newn+freq]:=h[left+freq]+h[rite+freq];

      (* link to left and right subtrees (offspring) *)
      h[newn+lst]:=left;
      h[newn+rst]:=rite;

      (* link new node into the forest in place of old left *)
      (* first, make new node to point to its neighbors in the forest *)
      h[newn+lforst ]:=h[left+lforst];
      h[newn+rforst ]:=h[left+rforst];

      (* second, make neighbors point to new node *)
      if (h[left+lforst]<>nil)
         then h[h[left+lforst ]+rforst ]:=newn;
      h[h[left+rforst ]+lforst ]:=newn;

      (* delete rite node *)
      h[h[rite+lforst ]+rforst ]:=h[rite+rforst];
      if (h[rite+rforst]<>nil)       (* rite has a right neighbor *)
         then h[h[rite+rforst ]+lforst ]:=h[rite+lforst];

      (* update leftmost and rightmost pointers *)
      if (lmost=left) then lmost:=newn;
      if (rmost=rite) then rmost:=h[rite+lforst]
      end; (* procedure combinelr *)


   begin (* procedure build1tree *)
   repeat
      picklr;
      combinelr;
      put#1('.');      (* show progress on screen . . . *)
   until (lmost=rmost) (* only one node left *)
   end; (* procedure build1tree *)


(*
given the tree built in phase 1, traverse it (in order will do)
and  assign  a level to each node.  then return to the original
forest of trees (all leaves when we start), build them  into  a
single tree using phase 3 of the hu-tucker algorithm.  the root
of the resultant tree will be in lexfirst on exit.

the  algorithm  is  implemented  using two internal procedures.
the first (picklr) chooses two trees for combination,  and  the
second  (combinelr) combines the two chosen trees to form a new
internal node in the  final  tree.  this  process  is  repeated
unitl the forest contains only one tree.

the  procedure  used  is very similar to that used to build the
tree in phase 1.
*)
procedure build3tree;

   var maxlev : word; (* largest level in tree *)
       picklev: word; (* level of node now being picked *)
       left : word;   (* left most node to be replaced *)


   (*
   setlev  will  traverse  the  tree  generated  in phase 1 and
   assign levels to  each  of  the  nodes.  also,  the  deepest
   level reached will be recorded in maxlev on exit.
   *)
   procedure setlev;


      (*
      traverse  a  node  of  a  tree  pointed  to  by the first
      argument, assigning it the level  passed  in  the  second
      argument.
      *)
      procedure travinord(p : word  ;  curlev : word);

         begin
         if (p<>nil) then begin
            if (curlev>maxlev) then maxlev:=curlev;
            travinord(h[p+lst], curlev+1);
            h[p+lev]:=curlev;
            travinord(h[p+rst], curlev+1)
            end
         end; (* procedure travinord *)


      begin  (* procedure setlev *)
      maxlev:=0;
      travinord(lmost, 0) (* root is leftmost node *)
      end; (* procedure setlev *)


   (*
   pick  two  trees from the forest which satisfy the following
   rules:

   let i and j be pointers to the left and right trees:

   i')   the  trees  i  and  j  must be adjacent in the working
         sequence.

   ii')  the  levels  of  trees  i  and j must be maximal among
         all remaining levels.

   iii') the  index  i  is  minimal  for all i and j satisfying
         rules (i'), (ii').

   a pointer to the left most chosen will be left in left.  the
   right tree chosen is its lexicographic successor.
   *)
   procedure picklr;

      var picked  : boolean; (* true if one picked on this lev el *)
      begin
      picked:=false;
      while (picked=false) do begin
         left:=lexfirst; (* start with first node in lexicographic order *)
         while (left<>nil) and (picked<>true) do
            if (h[left+lev]=picklev)
                then picked:=true
                else left:=h[left+lexs];
         if (picked=false) then picklev:=picklev-1
         end (* while *)
      end; (* procedure picklr *)


   (*
   combine  the  tree  pointed to by left and its lexicographic
   successor to form a new internal node  in  the  final  tree.
   link  this new node into the existing lexicographic sequence
   in place of the left tree and its successor.  the pointer to
   the first node in the sequence (lexfirst), is updated in the
   process.
   *)
   procedure combinelr;

      var newn : word; (* pointer to new node created *)
          rite : word; (* pointer to right node being combined *)

      begin
      new(var newn);
      rite:=h[left+lexs]; (* right node is allways next in lex order *)
      h[newn+char]:=sent; (* init all internal nodes to sent char *)

      (* link left and right subtrees to new node *)
      h[newn+lst]:=left;
      h[newn+rst]:=rite;

      (* level of new node is one less than level of its offspring *)
      h[newn+lev]:=h[left+lev]-1;
      h[newn+lexs]:=h[rite+lexs];
      h[newn+lexp]:=h[left+lexp];

      (* link new node in place of left node from left *)
      if (h[left+lexp]<>nil) then (* left has a lex predecessor *)
         h[h[left+lexp]+lexs]:=newn;
      if (h[rite+lexs]<>nil) then (* right has a lex successor *)
         h[h[rite+lexs]+lexp]:=newn;

      if (left=lexfirst) then (* new node becomes lex first *)
         lexfirst:=newn
      end; (* procedure combinelr *)


   begin (* procedure build3tree *)
   setlev; (* compute node levels *)
   put#1(13,10);
   put#1('maxlev =',maxlev#,13,10);
   picklev:=maxlev;
   repeat
      picklr;
      combinelr;
      put#1('.')      (* show progress on screen . . . *)
   until (picklev<=1) (* true when all nodes have been picked *)
   end; (* procedure build3tree *)


(*
decode  a  sequence of 1's an 0's read from the standard input
file into a sequence of characters written to standard output.
this is done by starting at the root and taking a left  when a
zero  is  read, a right when a one is read.  this is continued
unitl a leaf is reached, when the character in  that  leaf  is
printed.  this process is repeated until end-of-file is found.
*)
procedure decode;

   var eof : boolean;
       ch  : word;   (* last one or zero read from input *)
       p   : word;   (* pointer used to traverse tree *)


   procedure getoz;

      begin
      get#0(ch);
      while (ch=13) or (ch=10) or (ch=' ') do
         get#0(ch);
      if (ch=26) then eof:=true
      end; (* procedure getoz *)


   begin (* prodecure decode *)
   put#1(13,10);
   put#1('decoded ', 'message  ',13,10);
   eof:=false;
   getoz;
   while (eof=false) do begin
      p:=lexfirst;      (* start at root of phase 3 tree *)
      while (h[p+char]=sent) do begin (* while at internal node *)
         if (ch='0')
            then p:=h[p+lst]  (* left  turn *)
            else p:=h[p+rst]; (* right turn *)
         getoz
         end; (* while at internal node *)
      put#1(h[p+char])
      end (* while not eof *)
   end; (* procedure decode *)

begin (* main line *)
   hp:=0; (* initialize heap pointer *)
   readtree;
   lexfirst:=lmost; (* first node in lex order is leftmost *)
   build1tree;
   build3tree;
   decode
end.
