Trie v AS3

Dnes se ve flashi pokusíme implementovat datovou strukturu Trie. Kromě samotné trie ji navíc nějak hezky vykreslíme, abychom dostali přehledné znázornění této struktury.

Něco o Trii

Řekněme, že náš program dostane tisíc slov, která musíme uložit do nějaké struktury. Poté nám budou chodit dotazy, zda naše struktura obsahuje konkrétní slovo. Uložení prvku do lineárního pole by bylo paměťově náročné, zároveň při hledání slova bychom museli projít všechny prvky. Trie je na tom o mnoho lépe. Sice přidání slova není tak rychlé jako u pole, ale hledání je výrazně rychlejší. Pokud v každém uzlu uchováváme pole o velikosti celé abecedy, jdeme od kořene přímo k listu, složitost je rovná délce slova. Více ve článku na Wikipedii.

Třída TNode

Co od trie chci

Chci si trii snadno vytvářet (var trie = new ...), snadno přidávat slova (trie.AddWord("ahoj");). Chci snadno zjišťovat, zda obsahuje nějaké slovo (if(trie.Contains("ahoj")) ...).

Jak ukládat potomky uzlu

Každý uzel ve trii může mít potomků od 0 až do velikosti abecedy. Abecedy jsou často velké (26 - písmena ang. abecedy, 256 - ASCII znaky, $2^{16}, 2^{24}, 2^{32}$ - znaky Unicode atd.). Avšak uzel většinou neobsahuje všechny tyto potomky. Paměťově efektivnější použít nějaké menší pole pevné velikosti, do kterého budeme prvky hešovat. Níže ukládám prvek na pozici (kód znaku) modulo 4. Kolize řeším vytvářením spojového seznamu.

Základní třída

package
{
   public class TNode
   {
      // pole 4 potomků tohoto uzlu
      public var cn:Vector. = new Vector.(4, true);
      public var c:String;   // znak uložený v uzlu
      public var next:TNode; // ukazatel na další prvek na stejné úrovni
      public var isEnd:Boolean = false; // zda je uzel koncem slova
  
      public function TNode(c:String):void
      {
         this.c = c; // přiřadím znak do "c"
      }
   }
}

Vyhledání potomka pro daný znak

Jelikož potomky máme v poli pevné velikosti a navíc ve spojových seznamech, není jejich vyhledání úplně triviální, proto si raději napíšeme metodu.

public function GetChild(s:String):TNode
{
   // nejdříve se podíváme do pole
   var n:TNode = cn[ s.charCodeAt(0)% 4 ];
   if ( n == null) return null; // nebyl v poli
   else    // byl v poli, hledáme ve spojáku
      while (n.next != null)  
      {
         if (n.c == s) return n;
         n = n.next;
      }
   if (n.c == s) return n; // byl na konci spojáku
   return null; // nebyl nikde
}

Přidání slova do trie

Nejdříve si přečteme první znak slova. Buď pro tento znak už máme potomka, nebo ne (potom ho vytvoříme). Tomuto potomku sdělíme mu, aby si přidal zbytek slova.

public function AddWord(s:String):void
{
   // podíváme se, zda nemáme potřebného potomka
   var n:TNode = GetChild(s.charAt(0));
   if(n==null) // nemáme
   {
      n = new TNode(s.charAt(0)); // vytvoříme ho
      var pos:int = s.charCodeAt(0)% 4; // kam ho dáme
      if(cn[pos] == null) cn[pos] = n;  // místo je volné
      else // není volné - dáme do spojáku
      {
         var no:TNode = cn[ pos ];
         while(no.next != null) no = no.next;
         no.next = n;
      }
   }
   // když není konec slova, přidáme zbytek do "n"
   if(s.length>1) n.AddWord(s.substring(1,s.length));
   else n.isEnd = true; // konec slova
}

Zda trie obsahuje slovo

public function Contains(s:String):Boolean
{
   // když jsme na dně rekurze, vrátíme "isEnd"
   if(s.length == 0) return isEnd;
   // podíváme se na potomka s daným znakem
   var n:TNode = GetChild(s.charAt(0));
   if(n==null) return false; // potomek není
   else return n.Contains(s.substring(1,s.length)); // potomek je
}

Vykreslení trie

Trii vykreslujeme podobným způsobem, jako Binární vyhledávací strom.

function vykresli(n:TNode, x:int, y:int, w:int, place:Sprite)
{
   var i:int;
   // potomky uzlu si dám do nového pole
   var nodes:Array = new Array();
   for(i = 0; i<4; i++)
   {
      var no:TNode = n.cn[i]; if(no == null) continue;
      while(no != null){ nodes.push(no); no = no.next;}
   }
   var sW:int = w/nodes.length; // místo pro každý uzel
   for (i = 0; i < nodes.length; i++)
   {
      with(place.graphics)  // čára k potomkovi
      {
         lineStyle(3,0x00E1FF);
         moveTo(x, y);
         lineTo(x-w/2 + i*sW + sW/2, y+60);
      }
      // a vykreslení potomka
      vykresli(nodes[i], x-w/2 + i*sW + sW/2, y+60, sW, place); 
   }

   // nakreslíme kroužek pro daný uzel
   with(place.graphics)
   {
      if(n.isEnd) lineStyle(4, 0x000000); // když je koncový
      else lineStyle(2, 0x333333);  // když není - tenčí okraj
      beginFill(0x6D00D9);
      drawEllipse(x-14, y-14, 28, 28);
      endFill();
   }
 
   // přidáme textové pole s písmenkem
   var tf:TextField = new TextField();
   tf.text = n.c; tf.x = x-tf.textWidth; tf.y = y-14;
   place.addChild(tf);
}

0 komentářů:

Okomentovat