Föreläsning 5
 Träd
 Binära träd
 Binärt sökträd som ADT
 Implementering av binärt sökträd
 Travestera binärt sökträd
 Sökning
 Insättning/borttagning
Terminologi - träd
 Ett träd i datalogi består av en rotnod och ett
ändligt antal underträd
(subtrees)
Trädets höjd är antalet
nivåer (4 i exemplet).
nivå 0
nivå 1
nivå 2
nivå 3
 Ett träd är en graf där man kan ta sig mellan två
noder på endast ett sätt. N noder ger N-1 bågar.
 Vi kommer främst titta på binära träd.
Binära träd
 Ett binärt träd är ett träd där varje nod har
maximalt 2 barn
 Definition:
Ett binärt träd är antingen tomt eller så har
rotnoden 2 underträd (subtree) som också är
binära träd (vänster och höger underträd).
Binärt sökträd (BST)
 Ett binärt sökträd är ett binärt träd som är ordnat
efter nodernas nycklar
 För godtycklig nod gäller




alla nycklar i nodens vänstra underträdet är mindre än nodens
nyckel
alla nycklar i nodens högra underträdet är större än nodens
nyckel
vänster och höger underträd är också binära sökträd
varje nod är unik (inga kopior)
Traversera
 Att besöka alla noder i ett träd kallas att
traversera trädet.
 Tre traverseringsordningar:



Inorder. Besök först trädets vänstra del, sedan noden själv
och sist trädets högra del. (1,2,3,4,5,6,7)
Preorder. Besök först noden själv, sedan trädets vänstra
del och sist trädets högra del. (4,2,1,3,6,5,7)
Postorder. Besök först trädets vänstra del, sedan trädets
högra del och sist noden själv. (1,3,2,5,7,6,4)
Normalt använder vi inorder.
Binärt sökträd som ADT
 Är skapat för att lätt kunna söka och lägga till
och ta ut (log n). För att det ska fungera måste
trädet vara bra balanserat.
 Operationer: searchTree(nyckel)
insertTree(element)
deleteTree(nyckel)
binarySearchTree.h
typedef struct{
char key[WORDLENGTH];
} Data;
typedef struct treeNode TreeNode;
struct treeNode{
Data element;
TreeNode *left, *right;
};
typedef struct{
TreeNode *root;
}BinaryTree;
BinaryTree *initBinaryTree();
void insertTree(BinaryTree *bp,Data e);
void inOrder(BinaryTree *bp);
Data *searchTree(BinaryTree *bp,char key[]);
…
binarySearchTree.c - initiera
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "binarySearchTree.h"
BinaryTree *initBinaryTree()
{
BinaryTree *bp = (BinaryTree*)malloc(sizeof(BinaryTree));
bp->root = NULL;
return bp;
}
…
binarySearchTree.c - traversera
void inOrder(BinaryTree *bp)
{
inOrderNode(bp->root);
}
void inOrderNode(TreeNode *np)
{
if(np!=NULL)
{
inOrderNode(np->left);
printf("%s,",(np->element).key);
inOrderNode(np->right);
}
}
Det här extrasteget hade vi sluppit om vi inte använt en speciell datastruktur
för att representera ett träd utan helt enkelt använt en pekare av typen
TreeNode och kallat den root. Det steget kommer nu behövas på alla funktioner
vi väljer att lösa rekursivt.
binarySearchTree.c – sätta in
void insertTree(BinaryTree *bp,Data e)
{
TreeNode *newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->element = e;
newNode->left=newNode->right=NULL;
if(bp->root==NULL)
bp->root=newNode;
else
insertNodeInTree(bp->root,newNode);
}
binarySearchTree.c sätta in
void insertNodeInTree(TreeNode *tree,TreeNode *newNode)
{
int komp=strcmp((tree->element).key,(newNode->element).key);
assert(komp!=0);
if(komp<0)
{
if(tree->right==NULL)
tree->right=newNode;
else
insertNodeInTree(tree->right,newNode);
}
else
{
if(tree->left==NULL)
tree->left=newNode;
else
insertNodeInTree(tree->left,newNode);
}
}
binarySearchTree.c - söka
Data *searchTree(BinaryTree *bp,char key[])
{
if(bp->root==NULL)
return NULL;
else
{
TreeNode *found = searchTreeNode(bp->root, key);
if(found!=NULL)
return &(found->element);
else
return NULL;
}
}
binarySearchTree.c - söka
TreeNode *searchTreeNode(TreeNode *tree, char key[])
{
int komp=strcmp((tree->element).key,key);
if(komp==0)
return tree;
else if(komp<0){
if(tree->right==NULL)
return NULL;
else
return searchTreeNode(tree->right,key);
}else{
if(tree->left==NULL)
return NULL;
else
return searchTreeNode(tree->left,key);
}
}
Ta bort från binärt sökträd
Det finns tre fall att ta hänsyn till när vi tar bort en
nod:
 noden är ett löv
 noden har bara ett subträd
 noden har både vänster och höger subträd
Fall 1 är trivialt: vi sätter helt enkelt förälderns
relevanta subträd till NULL
Noden har bara ett subträd
 Också relativt enkelt. Vi ersätter helt enkelt
noden med dess barn.
 Ex: Vi ska ta bort 4:
8
4
8
9
9
6
5
6
7
5
7
Noden har både vänster och höger subträd
 Vi måste nu lösa vad vi ska göra med de två barnen. Lösningen är att vi
ersätter noden med den minsta noden i det högra underträdet. Denna
kan inte ha något vänsterbarn (den är ju minst) och därmed lätta att ta
bort (fall 2)
 Ex: vi vill ta bort nod B.
H
H
B
A
N
E
C
C
A
N
E
F
D
F
D
deleteNode
 För att implementera deleteTree kommer vi att skapa en intern
hjälpfunktion som tar som parameter en pekare till noden som
ska tas bort och returnerar en pekare till rotnoden till det nya
subträdet. I exemplet nedan skickar man in en pekare till B och
funktionen returnerar en pekare till C med underträdet i den
högra bilden:
H
H
B
A
N
E
C
C
A
N
E
F
D
F
D
deleteNode - hjälpfunktion
TreeNode *deleteNode(TreeNode *np){
TreeNode *newSubTree;
if(np==NULL)
return NULL;
H
else if(np->right==NULL){
B
N
newSubTree=np->left;
free(np);
A
return newSubTree;
}else if(np->left==NULL){
H
newSubTree=np->right;
B
N
free(np);
return newSubTree;
E
}else{
…
hjälpfunktion forts.
…
}
}
TreeNode *parentToMin;
TreeNode *minOfRightTree;
H
minOfRightTree = np->right;
B
N
if(minOfRightTree->left==NULL){
minOfRightTree->left=np->left;
A
E
free(np);
return minOfRightTree;
F
}
while(minOfRightTree->left!=NULL){
parentToMin = minOfRightTree;
minOfRightTree = minOfRightTree->left;
min
}
parentToMin->left = minOfRightTree->right;
minOfRightTree->left=np->left;
minOfRightTree->right=np->right;
free(np);
return minOfRightTree;
H
B
A
N
E
C
F
D
parent
deleteNode -gränssnittsfunktion
Data deleteTree(BinaryTree *bp,char key[]){
Data d;
bp->root=deleteNodeTree(bp->root,key,&d);
return d;
}
Observera hur deletNodeTree ska returnera en pekare till hela
det nya trädet!
Den kommer sedan rekursivt leta sig ner i trädet mot rätt nod
och hela tiden returnera en pekare till trädet där den är tills den
hittar rätt nod. Då skickar den denna till vår hjälpfunktion som
returnerar en pekare till det nya trädet.
deleteNode –söker upp rätt nod
TreeNode *deleteNodeTree(TreeNode *tree,char key[],Data *d){
int komp=strcmp((tree->element).key,key);
if(komp==0){
*d = tree->element;
return deleteNode(tree);
}else if(komp<0){
if(tree->right==NULL)
return tree;//fanns ej
else{
tree->right = deleteNodeTree(tree->right,key,d);
return tree;
}
}else{
if(tree->left==NULL)
return tree;//fanns ej
else{
tree->left = deleteNodeTree(tree->left,key,d);
return tree;
}}}
Inlämningsuppgifter
 Följande uppgifter redovisas senast måndag
den 6 februari och kan inte redovisas senare:
5.5, 5.A, 5.B, 5.C/5.7, 5.D, 5.E
 Dessa uppgifter bör göras nu för att ni ska kunna
följa kursen på ett bra sätt. Övriga kan ni göra
vid tillfälle för högre betyg.
Uppgifter ej i boken
Utgå från föreläsningens implementering av ett binärt sökträd när du löser nedanstående
uppgifter
 5.A
Skriv en main som läser in sju ord från användaren och placerar dessa i ett BST. Kör
programmet och skriv in 7 ord så att trädet får minsta möjliga höjd. Kör programmet
och skriv in 7 ord så att trädet får maximal höjd.
(2p)
 5.B
Skriv funktioner som traverserar trädet med preOrder och inOrder.
(1p)
 5.C
Skriv två funktioner numberOfLeaves och numberOfNodes till vår implementation från
föreläsningen.
(2p)
 5.D
Skriv om sökfunktionen så att den är iterativ istället för rekursiv.
(3p)

5.E
Skriv en funktion maximum som returnerar det största värdet i ett binärt sökträd. Skriv en
rekursiv och en iterativ version.
(2p)