Med två referenser i varje objekt kan man emellertid bygga
mer intressanta träd, till exempel
ett som beskriver en symfoniorkesters
sammansättning.
Här har objekten följande utseende.
class Node:
value=""
down=None
right=None
All systematisk uppdelning kan beskrivas med liknande träd,
till exempel ett bokverks uppdelning i delar, kapitel,
sektioner osv. Man kan också tänka sej det som ett
släktträd och då kallas ofta down-referensen för
firstChild och rightreferensen för nextSibling.
Det är ganska förvånande att det räcker med två referenser i varje
objekt, oavsett hur stora barnaskarorna är.
När man talar om binärträd brukar man tänka
på en lite annorlunda bild.
Högst upp finns konstigt nog trädets rot och dit
har man alltid en referens root.
Antalet nivåer i trädet avgör hur många objekt det kan innehålla.
Ett fullt träd med k nivåer innehåller 2 höjt till k
objekt minus 1. Exempel: k=3 i vår bild ger högst 7 objekt (det finns plats
för två till under 9999). Man kan också säga att ett balanserat träd
med N objekt har cirka log N nivåer.
write(root) skriver ut
1 17 666 4711 9999 för vårt träd.
//Inordning
def write(p):
if p is None: return
write(p.left)
print p.value
write(p.right)
Om man kastar om dom tre sista satserna får man ändå ut alla talen
på skärmen men i andra ordningar. Preordning (eng.
preorder) innebär att rottalet skrivs först, sedan vänsterträdet
och sist högerträdet.
I vårt exempel blir ordningen
4711 17 1 666 9999.
Om vi återgår till orkesterträdet kan vi se att preordning faktiskt ger vettigare utskrift. Så här blir koden i det fallet.
//Preordning
def write(p):
if p is None: return
print p.value
write(p.down)
write(p.right)
Utskriften blir då den naturliga. Om vi för tydlighets skull
använder indragning av orden på lägre nivå blir utskriften
Orkester
Blås
Trä
Bleck
Stråk
Vi
Va
Vc
Kb
Slag
(Är det någon som kommer på hur man måste ändra koden för att få just
dessa indragningar, så vill jag gärna höra förslaget.)
Slutligen kan man skriva ut i postordning (eng. postorder) och det innebär att vänsterträdet skrivs först, sedan högerträdet och sist roten. Det ger 1 666 17 9999 4711 i vårt exempel.
finns(p,value) returnera True ifall ordet
finns i det delträd där p är rot.
def finns(p,value):
if p is None: return False
if value==p.value: return True
if value<p.value: return finns(p.left,value)
if value>p.value: return finns(p.right,value)
Men den kan också göras utan rekursion. Komplexiteten blir
densamma.
def finns(value):
p=root
while True:
if p is None: return False
if value==p.value: return True
if value<p.value: p=p.left
if value>p.value: p=p.right
Det här är ju nästan precis samma sak som binär sökning i en vektor.
I båda fallen blir antalet jämförelser cirka log N, men
vektorn har två nackdelar.
bintree.py och vi vill använda det som en abstrakt ordlista.
För en abstrakt ordlista bör åtminstone tre anrop finnas:
exists, put och write.
from bintree import Bintree wordlist=Bintree() # Skapar ett trädobjekt wordlist.put(word) # Sorterar in ett nytt ord i trädet if wordlist.exists(word): # Returnerar True om ordet finns i trädet wordlist.write() # Skriver ut alla ord i bokstavsordningEndast dessa tre metoder kan anropas utifrån, och det är därför vi säger att vi gjort binärträdet abstrakt. I filen
bintree.py finns den
konkreta implementationen av vårt abstrakta träd och först
i den filen står class Node som vi förut sett. Sen kommer då
class Bintree med tre metoder som alla ska ha self
som första parameter.
class Bintree:
root=None
def put(self,word):
- - -
def exists(self,word):
- - -
def write(self):
- - -
Så ser det ut inne i binärträdsmodulen. Nu kan vi se vad anropen från
vårt huvudprogram egentligen betyder.
from bintree import Bintree # class Bintree blir synlig (men inte class Node) wordlist=Bintree() # nytt objekt med egen root och tre funktioner wordlist.put(word) # blir put(wordlist,word) med self=objektet if wordlist.exists(word): # blir exists(wordlist,word) med self=objektet wordlist.write() # blir write(wordlist) med self=objektetKoden för (den icke-rekursiva)
finns(value) duger också för
exists(self,word) om root byts ut mot self.root.
Om man i stället ville använda den rekursiva varianten med anropet
finns(p,value) kan man göra exists-koden så här kort.
def exists(self,word):
return finns(self.root,word)
Definitionen av finns skriver man utanför klassen Bintree
(före eller efter spelar ingen roll), eftersom den inte ska kunna anropas
utifrån.
Att putta in något nytt i ett sökträd innebär att söka efter det och sedan, när man kommit längst ner i trädet, länka in den nya noden på just det stället.
def put(self,word):
ny = Node() # Ny nod skapas...
ny.value=word
if self.root==None: # ...om trädet tomt, lägg in...
self.root=ny
return
p=self.root # ...börja annars en sökning.
while True:
if word<p.value:
if p.left==None: # Om None läggs nya noden in,
p.left=ny
return
p=p.left # annars går sökningen vidare.
elif word>p.value:
if p.right==None:
p.right=ny
return
p=p.right
else: # Dubblettter läggs inte in.
return
När det gäller write har man inget val, den måste vara vår rekursiva
inorder. Därför skickar exists bara vidare jobbet till en utanför
klassen definierad procedur skriv(p) (som är just rekursiva inorder).
def write(self):
skriv(self.root)
class Node:
key="" # Det som man sorterar på
info=None # Informationen om key
left=None
right=None
Sökmetoden getinfo(key) blir likadan som exists
men returnerar p.info i stället för True/False.
Biblioteket i Alexandria fick leva i sjuhundra år. Hypatia lever fortfarande som symbol för den som offrar sitt liv i kampen mot okunnighet och övertro och för vetenskap och sanning.