![]() |
Índex |
Un cas particular és el d'arbres binaris, en els que cada node té dos subarbres. En aquest capítol estudiarem aquests tipus d'arbre.
Arbre binari ordenat: és aquell arbre en el que les branques de cadascun dels nodes estan ordenades. Per tant, els dos arbres de la figura són diferents segons haguem definit el tipus d'ordenació.
Definició recursiva d'arbre binari: Un arbre binari és:
typedef struct Node
{ //Zona de dades ············································· //Aqui inserim totes les dades necessàries int Num; //Zona de punters ·············································
|
Mirant la figura veuràs els enllaços que haurem de crear en el moment de construir un arbre binari.
Per a expressar la forma en que han de distribuïr-se els nodes en un arbre perfectament equilibrat podem fer ús de la recursivitat. Per a construir un arbre de n nodes hem de seguir la regla següent:
Tipus de dades i declaració
de funcions
|
#include <stdio.h>
typedef struct NodeArbreEnters
typedef NodeArbreEnters ArbreEnters; ArbreEnters * ConstrueixArbre(int nNodes);
|
La funció main
Aquesta funció demana el número de nodes que ha de tenir l'arbre, fa construir l'arbre i desprès el fa imprimir. |
main()
{ int nNodes; ArbreEnters * ArbreProva; printf("Número de nodes: ");
return 0;
|
La funció ConstrueixArbre
Aquesta funció és una funció recursiva. Rep el número de nodes que ha de tenir l'arbre i fa la construcció del node corresponent assignant la informació següent a un node:
|
ArbreEnters * ConstrueixArbre(int nNodes)
{ NodeArbreEnters * NouNode, * Arrel; int Numero, ni, nd; if (nNodes == 0) Arrel = NULL;
NouNode = new
ArbreEnters;
ni = (int)(nNodes/2);
|
![]() Aquesta funció rep l'arbre a imprimir i treu per pantalla una figura com la de la dreta. Aquesta forma d'imprimir s'ha fet per comoditat i facilitat de comprensió de l'exercici. La funció rep l'arbre a imprimir i primer imprimeix el subarbre esquerra cap amunt i desprès el subarbre dret cap avall. Aquesta impressió de dades es fa de manera recursives amb crides a la mateixa funció. |
void ImprimeixArbre(ArbreEnters * Arrel,
int nivell)
{ int i; if (Arrel) { ImprimeixArbre(Arrel->pEsquerra, nivell+1); for (i=0;i<nivell;i++) printf(" "); printf("%d\n",Arrel->Numero); ImprimeixArbre(Arrel->pDret, nivell+1); } } |
Suposem que tenim definit un arbre com el de la dreta. Tenim
els tres recorreguts següents:
|
![]() |
void VisitaPreOrdre(ArbreEnters * a)
{ if (a != NULL) { P(a); VisitaPreOrdre(a->pDret); VisitaPreOrdre(a->pEsquerra); } } |
void VisitaOrdreCentral(ArbreEnters * a)
{ if (a != NULL) { VisitaOrdreCentral(a->pDret); P(a); VisitaOrdreCentral(a->pEsquerra); } } |
void VisitaPostOrdre(ArbreEnters * a)
{ if (a != NULL) { VisitaPostOrdre(a->pDret); VisitaPostOrdre(a->pEsquerra); P(a); } } |
La cerca d'una dada clau es farà començant pel node arrel i seguint per la branca de l'esquerra o per la branca de la dreta segons que la clau sigui major o menor (respectivament) que la dada del node arrel i així successivament fins arribar a les fulles (NULL) o fins a trobar la dada.
Si un arbre binari, a més d'estar organitzat d'aquesta manera, és perfectament equilibrat llavors la cerca es fa molt ràpidament i es realitza quasi en el mateix número de passos que en el cas de la cerca binària en vectors.
L'algoritme per a buscar en arbres organitzats d'aquesta manera
és el següent:
Punter Busca(DadaABuscar, PunterANode)
Boolean Trobat Trobat = Fals Mentre (PunterANode i No(trobat) ) Si (PunterANode.Clau = DadaABuscar) Trobat = Cert Sinó Si (PunterANode.Clau > DadaABuscar) PunterANode = PunterANode.PunterEsquerra Sinó PunterANode = PunterANode.PunterDret Fi del si Fi del si Fi del mentre Torna PunterANode Fi de la funció Busca |
Aquesta funció torna un punter al node que conté la dada a cercar si es troba dins el arbre i si després de fer el recorregut per l'arbre no troba aquesta dada torna un punter a NULL.
A la figura veiem que si un arbre amb ordenat hem de inserir un nou node amb el camp clau Pablo l'algoritme busca primer aquesta dada a l'arbre i com que no la trova insereix el nou node com subarbre esquerra de Pedro per mantenir l'arbre ordenat, no per a mantenir l'arbre equilibrat.
Tipus de dades i declaració
de funcions
Definim l'estructura de dades Alumnes com a nodes d'un arbre binari am dades, en aquest cas només el nom, i els punters dret i esquerra. |
#include <stdio.h>
#include <string.h> typedef struct Alumnes
void ImprimeixArbre(Alumnes * Arrel);
|
La funció main
La funció main té un punter a l'arbre que volem crear. En un bucle demana noms per pantalla i després insereix aquest nom a l'arbre. No és necessari introduir els noms en ordre alfabètic car la funció BuscaInsereix construeix l'arbre ordenat en el sentit expressat en l'apartat 4.2. Després imprimeix els noms que tenim a l'arbre ordenats en ordre alfabètic. |
main()
{ Alumnes * Arrel; char Nom[50]; Arrel=NULL;
while (strcmp(Nom, "fin"))
|
La funció BuscaInsereix
Aquesta funció rep l'adreça del punter arrel de l'arbre i un nom per a inserir en aquest arbre. És una funció recursiva:
|
void BuscaInsereix(char Nom[], Alumnes
** Arrel)
{ if ((*Arrel) == NULL) { //El nom nou (clau) no hi ‚s a l'arbre i l'inserim. * Arrel = new struct Alumnes; strcpy((*Arrel)->Nom,Nom); (* Arrel)->pDret=NULL; (* Arrel)->pEsquerra=NULL; } else if (strcmp(Nom, (*Arrel)->Nom ) < 0) BuscaInsereix(Nom,&(*Arrel)->pEsquerra); else if (strcmp(Nom, (*Arrel)->Nom ) > 0) BuscaInsereix(Nom,&(*Arrel)->pDret); } |
La funció ImprimeixArbre
Fa un recorregut de l'arbre de tipus ordre central i com que ja tenim fet l'ordre alfabètic de l'arbre en aquesta forma la funció ens imprimeix els noms en ordre alfabètic. |
void ImprimeixArbre(Alumnes * Arrel)
{ if (Arrel!=NULL) { ImprimeixArbre(Arrel->pEsquerra); puts(Arrel->Nom); ImprimeixArbre(Arrel->pDret); } } |
A l'exemple següent veuràs un exercici on es fa ús
d'un camp com a número d'entrada del registre. També afegeix
una nova funció, la funció Localiza que serveix per
a buscar una clau dins l'arbre.
#include <stdio.h>
#include <string.h> #define CIERTO 1; #define FALSO 0; typedef int Boolean; struct Alumnos
void ImprimeArbol(struct Alumnos * Puntero);
main()
DatoPantalla.Nombre[0]='\0';
void ImprimeArbol(struct Alumnos * Puntero)
void BuscaInserta(struct Alumnos Datos, struct Alumnos * * Puntero)
struct Alumnos * Localiza(char Nom[],struct Alumnos * Puntero)
Encontrado = FALSO;
|
La funció Esborra que hem fet distingeix tres casos
Adaptem la funció
main per tal de probar la funció Esborra.
Es fa notar que l'arbre que queda és el de la figura. Podeu fer probes per tal d'esborrar diferents elemets de l'arbre. |
main()
{ Alumnes * Arrel; char Nom[50]; char Noms[7][50]={"jorge","nuria","pedro","maria","vicente","ana","pablo"};
Arrel=NULL; for(i=0;i<7;i++) BuscaInsereix(Noms[i], &Arrel); ImprimeixArbre(Arrel);
|
La funció Esborra
Rep el nom a esborrar i el punter arrel de l'arbre. És una funció recursiva que primer busca el node on es troba la informació continguda en Nom i després contempla tots els casos dits abans. Fem notar que en cas de esborrar un node hem d'alliberar la memòria d'aquest node. |
void Esborra(char Nom[], Alumnes ** Arrel)
{ Alumnes **Auxiliar; if ((*Arrel) == NULL) printf("\nLa clau buscada
no hi ‚s a l'arbre");
|
La funció elimina | void Elimina(Alumnes ** Punter, Alumnes ** Auxiliar)
{ if ((*Punter)->pDret != NULL) Elimina(&(*Punter)->pDret,&(*Auxiliar)); else { strcpy((*Auxiliar)->Nom,(*Punter)->Nom); (*Auxiliar)->pEsquerra=(*Punter)->pEsquerra; delete (*Punter); } } |
Així apareix el concepte d'arbre equilibrat: Un arbre està equilibrat si i només si per a cadascun dels seus nodes s'esdevé que les alçades dels seus dos arbres es diferencien com a molt en un.
Aques concepte d'arbre equilibrat permet crear algoritmes amb reequilibri bastant eficients.
A l'exemple següent s'escriu l'algoreitme de creació d'arbres
equilibrats:
Tipus de dades i declaració
de funcions
Es defineix una estructura Alumnes que serveix com a node per a construir
l'arbre.
|
#include <stdio.h>
#include <string.h> #define CERT 1
typedef int Boolean; typedef struct Alumnes
void Busca(char Nom[], Alumnes ** Arrel, Boolean *h);
|
La funció main
Declara el punter Arrel per a construir-hi l'arbre. Es declara un vector de noms (NomMat) amb 7 noms per a incloure dins els nodes de l'arbre. Desprès de construir l'arbre amb la funció Busca imprimim l'arbre. |
main()
{ Alumnes * Arrel; char Nom[50]; Boolean h=FALS; char NomMat[7][50]={"jorge","nuria","pedro","maria","vicente","ana","alma"};
Arrel=NULL; for(i=0;i<7;i++) Busca(NomMat[i], &Arrel,&h); ImprimeixArbre(Arrel);
|
La funció Busca
Aquesta és una funció que busca si la dada que rep està en el arbre. Si no està dins l'arbre la insereix i després i en cas que sigui necessari reequilibra l'arbre. |
void Busca(char Nom[], Alumnes ** Arrel, Boolean
*h)
{ Alumnes ** Aux1; Alumnes ** Aux2; if ((*Arrel) == NULL) { //El registre no hi ‚s dins l'arbre i l'inserim. * Arrel = new Alumnes; strcpy((*Arrel)->Nom,Nom); (*Arrel)->Comptador = 1; (* Arrel)->pDret=NULL; (* Arrel)->pEsquerra=NULL; (* Arrel)->Equilibri = 0; } else if (strcmp(Nom, (*Arrel)->Nom ) < 0) { Busca(Nom,&(*Arrel)->pEsquerra,h); if (h) //La branca dreta ha crescut switch ((* Arrel)->Equilibri) { case 1: (* Arrel)->Equilibri=0; *h=FALS; break; case 0: (* Arrel)->Equilibri=-1; break; case -1: //Reequilibrar (*Aux1)=(*Arrel)->pEsquerra; if ((*Aux1)->Equilibri==-1) { //Rotació II simple (*Arrel)->pEsquerra= (*Aux1)->pDret; (*Aux1)->pDret=(*Arrel); (*Arrel)->Equilibri=0; (*Arrel)=(*Aux1); } else { //Rotació ID doble (*Aux2)=(*Aux1)->pDret; (*Aux1)->pDret=(*Aux2)->pEsquerra; (*Aux2)->pEsquerra=(*Aux1); (*Arrel)->pEsquerra=(*Aux2)->pDret; (*Aux2)->pDret=(*Arrel); if ((*Aux2)->Equilibri == -1) (*Arrel)->Equilibri=1; else (*Arrel)->Equilibri=0; if ((*Aux2)->Equilibri == 1) (*Aux1)->Equilibri=-1; else (*Aux1)->Equilibri=0; (*Arrel)=(*Aux2); } (*Arrel)->Equilibri=0; *h=FALS; break; } } else if (strcmp(Nom, (*Arrel)->Nom ) > 0) { Busca(Nom,&(*Arrel)->pDret,h); if (h) //La branca dreta ha crescut switch ((* Arrel)->Equilibri) { case -1: (* Arrel)->Equilibri=0; *h=FALS; break; case 0: (* Arrel)->Equilibri=+1; break; case 1: //Reequilibrar (*Aux1)=(*Arrel)->pDret; if ((*Aux1)->Equilibri==+1) { //Rotació DD simple (*Arrel)->pDret= (*Aux1)->pEsquerra; (*Aux1)->pEsquerra=(*Arrel); (*Arrel)->Equilibri=0; (*Arrel)=(*Aux1); } else { //Rotació DI doble (*Aux2)=(*Aux1)->pEsquerra; (*Aux1)->pEsquerra=(*Aux2)->pDret; (*Aux2)->pDret=(*Aux1); (*Arrel)->pDret=(*Aux2)->pEsquerra; (*Aux2)->pEsquerra=(*Arrel); if ((*Aux2)->Equilibri == 1) (*Arrel)->Equilibri=-1; else (*Arrel)->Equilibri=0; if ((*Aux2)->Equilibri == -1) (*Arrel)->Equilibri=-1; else (*Arrel)->Equilibri=0; (*Arrel)=(*Aux2); } (*Arrel)->Equilibri=0; h=FALS; break; } } else { (*Arrel)->Comptador++; *h=FALS; } } |
La funció ImprimeixArbre
És la mateixa que la de tots els exemples anteriors. |
void ImprimeixArbre(Alumnes * Arrel)
{ int i; if (Arrel!=NULL) { ImprimeixArbre(Arrel->pEsquerra); puts(Arrel->Nom); ImprimeixArbre(Arrel->pDret); } } |
L'operació d'esborrar un registre és del mateix tipus de la inserció dins els arbres equilibrats. Es a dir, que quan esborrem un node d'un arbre es fa necessari comprovar l'equilibri i restablir-ho, aquest equilibri, cas que s'hagi perdut.
Les funcions necessàries per a esborrar un node en el mateix
exemple que l'exemple precedent són les següents:
Declaració de funcions
La funció que elimina és la funció Esborra que ha fa ús de les altres per a realitzar la seva tasca. |
void Esborra(char Nom[], Alumnes
** Arrel, Boolean *h);
void Esbor(Alumnes ** Node, Boolean *h, Alumnes ** Aux); void Equilibra1(Alumnes ** Node, Boolean *h); void Equilibra2(Alumnes ** Node, Boolean *h); |
La funció main
Hem declarat un vector amb noms per tal d'inserir dins l'arbre. Fem notar que el paràmetre h es passa per referència a totes les funcions. Com que la funció Busca i la funció Esborra han de rebre h amb valor FALS hem de tenir curar de donar-li aquest valor abans de fer la crida a aquestes funcions. |
main()
{ Alumnes * Arrel; char Nom[50]; Boolean h=FALS; char NomMat[20][50]={"jorge","nuria","pedro","maria","vicente",
int i; Arrel=NULL; for(i=0;i<11;i++) Busca(NomMat[i],
&Arrel,&h);
h=FALS;
|
La funció esborra
Funcions traduïdes del llibre ALGORITMOS +
|
void Esborra(char Nom[], Alumnes ** Arrel, Boolean
* h)
{ Alumnes ** Aux; if ((*Arrel) == NULL) { printf("El registre no hi és dins l'arbre"); h=FALS; } else if (strcmp(Nom, (*Arrel)->Nom ) < 0) { Esborra(Nom,&(*Arrel)->pEsquerra,h); if (*h) Equilibra1(&(*Arrel),h); } else if (strcmp(Nom, (*Arrel)->Nom ) > 0) { Esborra(Nom,&(*Arrel)->pDret,h); if (*h) Equilibra2(&(*Arrel),h); } else { //Esborrar *Arrel (*Aux)=(*Arrel); if ((*Aux)->pDret==NULL) { (*Arrel)=(*Aux)->pEsquerra; *h=CERT; delete (*Aux); } else if ((*Aux)->pEsquerra==NULL) { (*Arrel)=(*Aux)->pDret; *h=CERT; delete (*Aux); } else { Esbor(&(*Aux)->pEsquerra,h,&(*Arrel)); if (*h) Equilibra1(&(*Arrel),h); } } } |
void Esbor(Alumnes ** Node, Boolean *h,
Alumnes ** Aux)
{ //h = Fals Alumnes ** Aux2; if ((*Node)->pDret != NULL) { Esbor(&(*Node)->pDret,h,&(*Aux)); if (*h) Equilibra2(&(*Node),h); } else { *Aux2 = *Node; strcpy((*Aux)->Nom,(*Node)->Nom); (*Aux)->Comptador=(*Node)->Comptador; (*Aux)->pEsquerra=(*Node)->pEsquerra; *h=CERT;
|
|
void Equilibra1(Alumnes ** Node, Boolean *h)
{ Alumnes ** Aux1; Alumnes ** Aux2; int e1,e2; //h = Cert, la branca esquerra ha disminuit switch ((* Node)->Equilibri)
|
|
void Equilibra2(Alumnes ** Node, Boolean *h)
{ Alumnes ** Aux1; Alumnes ** Aux2; int e1,e2; //h = Cert, la branca esquerra ha disminuit switch ((* Node)->Equilibri)
|
Així, l'arbre de directoris d'un disc dur, l'arbre genealògic en el que una persona pot tenir més de dos fills són exemples d'arbres que podem anomenar arbres multicamí.
Existeixen com a eina de treball fonamental els Arbres B que són arbres multicamí pensats per a treballar en dispositius d'enmagatzament extern i grans quantitats de dades, i en els que els algoritmes de cerca, inserció i esborrat necessiten molts menys passos que els mateixos algoritmes per a arbres binaris.
És recomanable la lectura del capítol corresponent a arbres del llibre esmentat anteriorment:
ALGORITMOS + ESTRUCTURAS DE DATOS = PROGRAMAS
NIKLAUS WIRTH
EDICIONES DEL CASTILLO
21 8 9
11 13 4
5 2 7
91
13 15 26 3
1 6 10 12
23 19