Arvores, Percursos não recursivos, Arvores heterogêneas Aula 19
Arvores binárias encadeadas Percorrer uma árvore é uma operação muito comum e seria útil encontrar um método mais eficiente para implementar percursos. Em nosso algoritmo de percurso não recursivo usamos uma pilha que é desempilhada quando: O programa desceu pelas ramificações esquerdas até encontrar um ponteiro NULL, empilhando o ponteiro a cada passagem pelos nós (se mantemos q auxiliar no nó anterior não teremos que usar a pilha). Em outro caso onde p é NULL é ao alcançar um nó com uma subrárvore direita vazia, executando p->right e repetindo o do while. Nesse ponto teríamos perdido nosso trajeto caso ele não fosse a pilha cujo topo aponta para a árvore cuja sub-árvore esquerda acaba de ser percorrida
#define MAXSTACK 100 void intrav2(nodeptr tree){ struct stack{int top; NODEPTR item [MAXSTACK];}s; s.top = -1;p = tree; do{ /*percorre desv. esq o max possível salvando ponteiro para nós passados*/ while(p!=null){ push(s, p); p = p->left; }//fim-while //verifica termino if (! empty(s)){ D G /*neste ponto a subarv esq esta vazia*/ p = pop(s); printf( %d\n, p->info);//visita a raiz p = p->right;//percorre subarv direito }//fim-if } while (!empty(s) && p!=null);} A B C E H I F
A solução: Em vez de ter um ponteiro a NULL no campo right, um nó com uma subárvore direita vazia contivesse um ponteiro para o nó que estaria no topo da pilha nesse momento (isto é um ponteiro para seu sucessor em ordem) A B A B C C D D E F E F G H G H I I J K L
Arvores encadeados a direita Desse jeito não teríamos necessidade de uma pilha, uma vez que o último nó visitado durante um percurso de uma subárvore esquerda aponta diretamente para seu sucessor em ordem. Esse ponteiro é chamado de linha e deve ser diferenciado de um ponteiro de uma árvore para associar um nó a sua subárvore esquerda ou direita. Esses árvores são chamados de encadeados à direita.
Arvores encadeados a direita Para a implementação um campo lógico extra deverá ser incluído em cada nó (rthread) para indicar se seu ponteira à direita é ou não uma linha. Para coerência o campo rthread do nó extrema direita de uma árvore (isto é, o último nó na travessia em ordem de uma árvore) é definido como TRUE, embora seu campo right permaneça NULL. Assim um nó é definido como: struct nodetype { int info; struct nodetype *left; //pont. p/ filho esq. struct nodetype *right; //pont. p/ filho dir. int rthread; //será TRUE se right //for NULL ou uma //linha não-null };
Arvores encadeados a direita Exercício: Implemente uma rotina par percurso em ordem de uma árvore binária encadeada à direita. Observação: Numa árvore encadeada à direita, o sucessor em ordem de qualquer nó pode ser encontrado com eficiência
Percurso usando o campo father Se cada nó da árvore contiver um campo father, não será necessário nem uma pilha nem linhas para o percurso não recursivo Quando o processo de percurso atingir um nó folha o campo father poderá ser usado para reescalar a árvore de volta. Quando node(p) for alcançado a partir de um filho esquerdo, sua subárvore direita precisara ainda ser percorrida, portanto, o algoritmo continuará em right(p). Quando node(p) for alcançado a partir de seu filho da direita, suas duas subárvores terão sido percorridos e o algoritmo retornará para o father(p)
Percurso usando o campo father Os percursos em pre e pos ordem para árvores encadeadas à direita são similares. Os percursos usando o campo father para subir de volta é menos eficiente em termos de tempo, do que no caso do percurso de árvores encadeado. Uma linha aponta diretamente para o sucessor do nó enquanto uma seqüência inteira de ponteiros father talvez precise ser seguida para alcançar esse sucessor numa árvore encadeada Exercício: Implemente o percurso em ordem usando o campo father
Percurso usando o campo father Outra opção é a medida que descemos na árvore inverter a o ponteiro do filho, assim sempre é possível encontrar o caminho de volta sem precisar o campo father, no percurso de subida retornamos o ponteiro no seu valor original. Exercício: Implemente a rotina de percurso em ordem usando essa opção.
Arvores binárias heterogêneas Frequentemente as informações contidas em diferentes nós de uma árvore binária não são todas do mesmo tipo. Exemplo, expressões aritméticas representadas pela árvore onde, nós folha representam os operandos e nós não folhas os operadores. Como representaríamos isso?? + + 3 3 / * 5 4-6 6
Arvores binárias heterogêneas Usando uma união para representar a parte da informação do nó. Cada nó deverá conter um campo que indica o tipo de informação é. #define OPERATOR 0 #define OPERAND 1 struct nodetype{ short int utype; union { char chinfo; float numinfo; } info; struct nodetype *left; struct nodetype *right; }; typedef struct nodetype *NODEPTR;
Função que aceita um ponteiro a uma árvore desse tipo e devolve o valor da expressão representada pela árvore. float evalbintree(nodeptr tree){ float opnd1, opnd2 char symb; if (tree->utype == OPERAND) return (tree->numinfo); opnd1 = evalbintree(tree->left); opnd2 = evalbintree(tree->rigth); symb = tree->chinfo; return = oper (symb, opnd1, opnd2); }
float oper(char symb,float op1, float op2){ switch(symb) { case '+': return op1 + op2; case '-': return op1 - op2; case '*': return op1 * op2; case '/': if (op2) return op1/op2; else { printf("divisao por zero"); exit(1); } } exit(1); }
Outros tipos de árvores Arvores gerais e sua representação binária Arvores balanceadas, eficiência nos percursos e busca de elementos. Operações de rotação, inserção e eliminação numa árvore balanceada.