Binära träd Inom datalogin brukar man använda träd för att beskriva vissa typer av problem. Om man begränsar sig till träd där varje nod förgrenar sig högst två gånger, talar man om binära träd. Sådana lämpar sig väl t ex för att söka efter tal eller ord i. Binära träd (och egentligen träd i allmänhet) passar bra ihop med rekursiva funktioner. Varje del av trädet kan ju ses som ett eget träd. Det kan man utnyttja när man vill skriva ut trädet eller gå igenom det för något annat ändamål.
En binär trädklass Man kan bygga upp ett binärt träd med hjälp av en Node-klass som liknar den vi använde för att göra länkade listor, men i den här nodklassen, som vi kallar för TreeNode, har vi två pekare: en höger- och en vänsterpekare. class TreeNode: def init (self,tal,left=none,right=none): self.tal=tal self.left=left self.right=right def str (self): return str(self.tal) Här använder vi attributet tal för det innehåll noden ska ha. Det motsvarar attributet cargo från den länkade listan i övning 5. left och right är pekare till varsin nod längre ner i trädet. Vi kan bygga upp ett litet träd med nodklassen. Vi ska ha en rotnod (som det kallas) med två barn. Vi börjar med barnnoderna och kopplar sedan dem till roten. l=treenode(17) r=treenode(23) rot=treenode(19,l,r)
Binär trädklass, forts. Vi kan också göra det hela snabbare genom att bara skriva: rot=treenode(19,treenode(17),treenode(23)) vilket ger samma resultat. Om vi nu skriver print rot så får vi svaret 19. Skriver vi print rot.left får vi svaret 17. Men i längden blir det ju jobbigt att skriva ut genom att gå via pekarna. Vi skulle vilja ha en funktion som låter oss skriva ut ett helt träd.
Att skriva ut ett binärt träd: preorder Här kan vi använda en rekursiv tanke : låt roten skriva ut sitt eget värde och tala om för sina högeroch vänsterträd att de ska skriva ut sig själva. Frågan är i vilken ordning man ska göra det? Vi börjar med att skriva ut rotens värde först, sedan skriva ut vänsterträdet och därefter högerträdet. Observera att vi (som alltid) måste ha ett basfall. Här är basfallet att vi har kommit till en None-pekare. def printpreorder(node): if node==none: return print node.tal, printpreorder(node.left), printpreorder(node.right) Om vi nu skriver printpreorder(rot) (vi antar att vi har kvar vårt lilla träd från förra exemplet) så blir utskriften 19 17 23. Det här utskriftssättet kallas preorder.
Utskrift: inorder Observera att jag med flit lagt upp trädet så att talet till vänster om roten är mindre än roten och talet till höger om roten är större. Om vi vill skriva ut trädet med talen i ordning så får vi ta vänster delträd först, sedan det egna talet, och till sist högra delträdet. Utskriftssättet kallas (överraskning) inorder. Koden blir def printinorder(node): if node==none: return printinorder(node.left), print node.tal, printinorder(node.right) och utskriften blir 17 19 23. Det finns också postorder som ni själva får fundera ut koden till. Med den blir utskriften 17 23 19.
Sökning i binärt träd Varför har jag lagt upp trädet så att mindre tal är till vänster och större till höger? Naturligtvis för att man ska kunna söka i trädet på ett effektivt sätt. Det här fungerar för både tal och strängar (i Python kan man ju jämföra strängar med varandra). Grundtanken är: Utgå från roten. Jämför det sökta talet/ordet med talet/ordet i roten. Om det sökta talet/ordet är mindre, gå till det vänstra delträdet och upprepa därifrån. Om det sökta talet/ordet är större, gå till det högra delträdet och upprepa därifrån. Om det sökta talet/ordet är identiskt med rottalet, har vi hittat vårt svar. Returnera True.
Sökning i binärt träd, forts. Vi kan lägga till en metod finns() i TreeNode-klassen. def finns(r, value): while r!= None: if value < r.tal: r = r.left elif value > r.tal: r = r.right else: return True return False Den här funktionen kollar om talet finns i det träd där noden är rot. Så om (återigen med vårt lilla exempelträd) vi skriver print rot.finns(17) så får vi True, men om vi skriver print rot.right.finns(17) så får vi False, eftersom värdet 17 inte finns i det högra delträdet till roten.
Insättning i binärt sökträd Nu är det ju så att vi också måste sätta in värdena på rätt sätt för att vår binära sökning ska lyckas. Vi måste bara se till att när trädet byggs up så sätter man alltid in mindre tal till vänster och större tal till höger. Det är ungefär som att söka, fast när man kommer till en None-pekare så ska man istället sätta in en ny nod. Så här kan det se ut: (detta är en metod i TreeNode-klassen) def put(self, r, value): if r == None: r = TreeNode(value) return r else: if value < r.tal: r.left = self.put(r.left, value) elif value > r.tal: r.right = self.put(r.right, value) else: print "Dubblett: ", value return r
Lite övningar på binära träd För att träna på binära träd och rekursivitet kan vi fundera på ett par uppgifter. Hur skriver man metoder för att beräkna: Antal noder i ett träd Antal löv (noder där left och right är None) Summan av alla tal i noderna Höjden (antalet nivåer)?
Antal noder def antalnoder(rot): if rot==none: return 0 return 1+antalNoder(rot.left) +antalnoder(rot.right) Antal löv def antallov(rot): if rot==none: return 0 if (rot.left==none) and (rot.right==none): return 1 return antallov(rot.left) +antallov(rot.right)
Nodsumma def nodsumma(rot): if rot==none: return 0 return rot.tal+nodsumma(rot.left) +nodsumma(rot.right) Antal nivåer def antalnivaer(rot): if rot==none: return 0 if(antalnivaer(rot.left)>antalnivaer(rot.right)): return 1+antalNivaer(rot.left) else: return 1+antalNivaer(rot.right)
Testprogram för de rekursiva metoderna rot=none tal=1 while(tal>0): tal=input("positivt heltal? (negativt för att sluta)") if (rot==none): rot=treenode(tal) elif tal<0: pass else: rot.put(rot,tal) print "Antal noder: ", antalnoder(rot) print "Antal löv: ", antallov(rot) print "Summa av nodernas tal: ", nodsumma(rot) print "Antal nivåer: ", antalnivaer(rot)