Você está na página 1de 26

Universidade de Cruz Alta

Cincia da Computao Estrutura de Dados II

1 Definies

1.1 Algoritmo
Um algoritmo definido pela matemtica como um processo de clculo, ou de resoluo de um grupo de problemas semelhantes, em que se estipulam regras formais para a obteno do resultado, ou da soluo do problema. Em computao, comum definirmos um algoritmo como um conjunto de passos necessrios para resolver um problema. Outra definio a de que um algoritmo, intuitivamente, uma seqncia finita de instrues ou operaes bsicas(...), cuja execuo, em tempo finito, resolve um problema computacional(Salvetti e Barbosa, 1998). Um algoritmo, na computao, qualquer procedimento computacional que recebe como entrada um valor (ou conjunto de valores) e produz como sada outro valor (ou um conjunto de valores). Finalmente, ento, podemos dizer que um algoritmo uma seqncia de passos computacionais que transforma entrada em sada. Por exemplo: Problema: Encontrar o maior e o menor valor de um vetor com n elementos. Entrada: Vetor com n elementos Sada: O elemento com o menor valor de todos e o elemento com o maior valor de todos.
Figura 1 . Exemplo de instncia da execuo de um algoritmo

1.1.1 Instncias de execuo de algoritmos


Uma instncia de um problema consiste de um conjunto de dados de entrada e sada utilizado durante uma nica execuo. Por exemplo, as Figuras 2 e 3 mostram diferentes instncias da execuo do mesmo algoritmo, cujo problema foi especificado na Figura 1. Problema: Encontrar o maior e o menor valor de um vetor com n elementos. Entrada: {1,34,56,32,3,54,3,356,3,2,23,78,65,32,11,1,43,356,66} Sada: Menor valor = 1 Maior valor = 356
Figura 2 . Exemplo de instncia da execuo de um algoritmo

Problema: Encontrar o maior e o menor valor de um vetor com n elementos. Entrada: {2,54,67,93,54,23,345,67,42,447,4,983,10,76,31,15,57,83,45,794,346,44} Sada: Menor valor = 2 Maior valor = 983
Figura 3 . Exemplo de outra instncia da execuo de um algoritmo

Prof. Marco Antonio Barbosa 1

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Por esses exemplos podemos verificar que em diferentes instncias de execuo, os dados de entrada podem ser bastante variados, podendo inclusive ter uma grande variao de volume. Ou seja, na instncia apresentada na Figura 1 o vetor de entrada tinha 19 valores, enquanto na instncia apresentada na Figura 2 o vetor de entrada tinha 22. Da mesma, em uma outra instncia possvel que eu tenha 500 elementos de entrada. A questo que se levanta ento : dado um algoritmo que funciona de forma eficiente para 10 elementos, ele continuar funcionando de forma eficiente para 10.000 ou mais? O algoritmo deve trabalhar corretamente sobre todas as instncias para as quais foi projetado para solver. Portanto, um algoritmo para o qual possvel achar uma instncia de dados de entrada para a qual ele no consegue achar uma resposta correta um algoritmo incorreto. No entanto, provar o contrrio, que o algoritmo correto para qualquer instncia, no to simples. Para isso, o algoritmo deve ser avaliado utilizando alguns critrios.

Avaliao de Algoritmos
Algoritmos podem ser avaliados utilizando-se vrios critrios. Geralmente o que interessa a taxa de crescimento ou espao necessrio para resolver instncias cada vez maiores de um problema. Podemos associar um problema a um valor chamado de tamanho do problema, que mede a quantidade de dados de entrada. O tempo que um algoritmo necessita expresso como uma funo do tamanho do problema chamado de complexidade de tempo do algoritmo. O comportamento assinttico dos algoritmos (ou funes) representa o limite do comportamento de custo quando o tamanho cresce. O comportamento assinttico pode ser definido como o comportamento de um algoritmo para grandes volumes de dados de entrada. A complexidade de tempo de um algoritmo pode ser dividida em 3 aspectos: 1. Melhor caso o melhor caso representa uma instncia que faz o algoritmo executar utilizando o menor tempo possvel. 2. Pior caso o maior tempo demorado pelo algoritmo para executar alguma instncia. 3. Caso mdio a mdia de tempo que o algoritmo demora para executar. Geralmente, o mais importante avaliar o pior caso (porque pode inviabilizar o algoritmo) e o caso mdio, porque representa como o programa vai se comportar, na prtica, na maioria dos casos.

Avaliao Emprica
A forma mais simples de se avaliar um algoritmo implement-lo em um computador e execut-lo com vrias instncias do problema. Define-se ento um critrio para a eficincia, como por exemplo o tempo gasto para execuo. Esse tipo de avaliao chamada de emprica. Com base na observao, pode-se calcular o pior caso (a instncia de execuo que levou mais tempo), o melhor caso (a instncia de execuo que gastou menos tempo) e o caso mdio (a mdia do tempo gasto em todas as instncias de execuo). O problema com esse tipo de avaliao que o tempo gasto vai depender do computador utilizado, do compilador, da linguagem de programao, etc.

Prof. Marco Antonio Barbosa 2

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Avaliao Assinttica
Ao invs de calcular exatamente os tempos de execuo em mquinas especficas, a maioria das anlises conta apenas o nmero de operaes fundamentais. Tal fato deriva do fato de que ao realizar uma medida emprica do tempo de execuo de um algoritmo particular em um computador particular o resultado obtido fica estritamente ligado linguagem de programao que foi utilizada na codificao do algoritmo e na mquina onde o programa foi executado. Desta forma, uma pequena mudana no programa poderia no causar mudana significativa no programa, mas poderia representar uma significativa mudana no tempo de execuo do programa. Um fator importante na medida de complexidade o crescimento assinttico da contagem de operaes executadas. Por exemplo, num algoritmo para achar o elemento mximo entre n objetos, a operao fundamental seria a comparao dos objetos, e a complexidade seria o nmero de comparaes efetuadas pelo algoritmo, para valores grandes de n. A complexidade assinttica de tempo de computao de um algoritmo minimiza os efeitos de fatores que so dependentes da linguagem de programao e da mquina e concentra-se em determinar a ordem de magnitude da freqncia de execuo das operaes. O comportamento assinttico de um algoritmo o mais procurado, j que para um volume grande de dados que a complexidade torna-se mais importante. O algoritmo assintoticamente mais eficiente melhor para todas entradas exceto entradas relativamente pequenas. Vrias medidas de complexidade assinttica podem ser definidas, mas as mais usadas so 0, e . A notao 0 (L-se Big Oh): Esta notao define um limite assinttico superior, isto f(n) 0(g(n)) sss (c) (n0) (n n0) f(n) c.g(n), Isto , a partir de um certo n0, f(n) no cresce mais que c.g(n); ou f(n) tem o mesmo tipo de crescimento que g(n). Onde um parmetro que caracteriza a entrada e/ou a sada do algoritmo. A notao : Esta notao define um limite assinttico exato, isto f(n) (g(n)) sss (c) (c) (n0) (n n0) c.g(n) f(n) c.g(n), Isto , existem constantes c, c e n0 tal que para todo valor de n maior ou igual a n0, o valor de f(n) est entre os produtos c.g(n) e c.g(n). A notao : Esta notao define um limite assinttico inferior, isto f(n) (g(n)) sss (c) (n0) (n n0) 0 f(n) c.g(n), Isto , existem constantes c e n0 tal que para valores de n iguais ou superiores a n0, f(n) menor ou igual ao produto c.g(n). Se f(n) 0(g(n)), por abuso de linguagem tambm se escreve f(n) = 0(g(n)), ocorrendo o mesmo com as outras definies de ordem. Assim o clculo da complexidade se concentra em determinar a ordem de magnitude do nmero de operaes fundamentais na execuo do algoritmo. Relao entre as Ordens Assintticas Prof. Marco Antonio Barbosa 3

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

A seguir apresentada uma relao hierrquica, da menor para a maior, entre as ordens assintticas. 0(1) ordem constante 0(log n) ordem logartmica 0(n) ordem linear 0(n log n) ordem n log n 0(n2) ordem quadrtica 0(n3) ordem cbica 0(2n) ordem exponencial Isto , em termos do comportamento assinttico do crescimento das funes pode ser dizer que: 0(1) < 0(log n) < 0(n) < 0(n log n) < 0(n2) < 0(n3) < 0(2n)

Na Figura 4, pode-se observar graficamente como esta relao se apresenta. Verifica-se, atravs das curvas, o crescimento das funes relacionadas s ordens de complexidade. Para instncias suficientemente grandes, inegvel a eficincia de um algoritmo cuja complexidade possua ordem logartmica (log n), por outro lado, para estas mesmas instncias, um algoritmo cuja complexidade seja dada por uma funo de ordem exponencial (2n) totalmente ineficiente. Note tambm que a base do logaritmo irrelevante nas ordens assintticas, pois a troca de base se faz multiplicando por uma constante.

Figura 4 . Relao entre as Ordens Assinttica (crescimento das funes)

Prof. Marco Antonio Barbosa 4

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Figura 5 . Grfico da relao entre as ordens assintticas

No exemplo da TABELA 2.1, est sendo considerada uma relao dos tempos de execuo de um algoritmo de uma ordem dada para entradas de tamanho dado. O desempenho do algoritmo que soluciona o problema ser dado para quatro entradas de diferentes tamanhos, sendo elas: 10, 102, 103 e 104. Pode-se notar que nas primeiras linhas da Tabela a complexidade aumenta pouco em relao ao tamanho do problema, na ltima linha, porm, assustador o aumento verificado. Atravs da Tabela pode-se avaliar que, o desempenho de um algoritmo cuja complexidade seja dada por uma funo logartmica bastante superior aos demais, sendo que as funes polinomiais apresentam desempenho aceitveis para grandes instncias e algoritmos exponenciais apresentam desempenho totalmente insatisfatrio, o que comprova sua ineficincia quando utilizados para instancias suficientemente grandes. Isto tambm justifica a dicotomia que existe em complexidade na qual, algoritmos polinomiais so eficientes e algoritmos exponenciais ineficientes. E ento os problemas: problemas com algoritmos polinomiais so tratveis, problemas com algoritmos exponenciais so intratveis.

Prof. Marco Antonio Barbosa 5

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Algoritmos de Classificao
Antes de estudarmos algoritmos de classificao, vamos analisar a sua importncia considerando um algoritmos simples de busca com base numa tabela no ordenada de elementos. 2.1 Operao de Busca Seqencial A forma mais simples de uma busca a busca seqencial. Essa busca aplicvel a uma tabela organizada como um vetor ou uma lista ligada. Suponhamos que k seja um vetor de n chaves, k[0] a k[n-1], e r um vetor de registros, r[0] a [n-1], tal que k[i] seja a chave de r[i]. Suponhamos que key seja uma argumento de busca. Desejamos retornar o menor inteiro i tal que k[i] seja a key, se tal inteiro i existir, e -1, caso contrrio. Veja a seguir o algoritmo que fez essa operao:
for (i=0; i<n; i++) if (key == k[i]) return (i); return (-1);

O algoritmo examina uma chave por vez; ao encontrar uma que coincida com o argumento de busca, seu ndice (que atua como ponteiro para seu registro) retornado. Se nenhuma coincidncia for encontrada, ser retornado -1. Esse algoritmo pode ser facilmente modificado de modo a incluir na tabela um registro rec com chave key se key ainda no estiver includa. O ltimo comando pode ser alterado para:
K[n] = key; /* insere a nova chave e */ r[n] = rec; /* registro */ n++ /* aumenta o tamanho da tabela */ return (n-1);

Um mtodo de busca ainda mais eficiente requer a insero da chave de argumento no final do vetor antes de iniciar a operao de busca, garantindo assim que a chave seja encontrada. Eficincia da Operao de Busca Seqencial Qual a eficincia de uma busca seqencial? Examinemos o nmero de comparaes feitas por uma busca seqencial ao procurar determinada chave. No presumiremos nenhuma insero ou eliminao, de modo que pesquisaremos uma tabela de tamanho constante n. O nmero de comparaes depender de onde o registro com a chave do argumento aparecer na tabela. Se o registro for o primeiro na tabela, s ser efetuada uma comparao; se o registro for o ltimo da tabela, sero necessrias n comparaes. Se for igualmente provvel que o argumento aparea em qualquer posio determinada da tabela, uma busca de sucesso exigir (no caso mdio) (n+1)/2 comparaes, e um

Prof. Marco Antonio Barbosa 6

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

busca sem xito exigir n comparaes. Em qualquer caso, o nmero de comparaes ser O(n).

2.2 Classificao por Insero


2.2.1 - Insero Simples
A ordenao por insero inicialmente ordena os dois primeiros elementos de um vetor, em seguida, o algoritmo insere o terceiro membro na sua posio ordenada com relao aos dois primeiros membros. Ento, insere o quarto elemento na lista dos trs elementos. O processo contiua at que todos os elementos tenham sido ordenados. Por exemplo, dado o vetor: x d c a b cada passo de ordenao por insero mostrado aqui: incial 4 3 1 passo 1 3 4 1 passo 2 1 3 4 passo 3 1 2 3 2 2 2 4

O cdigo para um verso da ordenao por insero dado a seguir:


void insertsort (int x[], int n) { int i, k, y; /* inicialmente x[0] considerado um arq classificado de 1 elemento */ /* Aps cada interao, os elementos x[0] a x[k] estaro em sequencia*/ for (k=1; k < n; k++){ /* Insere x[k] no arquivo classificado */ y = x[k]; /* Move 1 posio para baixo todos elementos maiores que y*/ for (i = k-1; i >= 0 && y < x[i]; i--) x[i+1] = x[i]; /* Insere na posio correta */ x[i+1] = y; } /* fim for */ } /* fim insertsort */

Prof. Marco Antonio Barbosa 7

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

2.2.2 - Insero de Shell


A ordenao Shell assim chamada devido a seu inventor, D. L. Shell. O mtodo derivado da ordenao por insero e baseado na diminuio dos incrementos. Considere a Figura abaixo. Primeiro todos os elementos que esto trs posies afastados um do outro so ordenados. Em seguida, todos os elementos que esto duas posies afastados so ordenados. Finalmente, todos os elementos adjacentes so ordenados. Passo1 f d a c b e Passo2 Passo3 Resultado c a a b b b a c c f d d d e e e f f

A seqncia exata para os incrementos pode mudar. A nica regra que o ltimo incremento deve ser 1. Por exemplo, a seqncia: 9,5,3,2,1 funciona bem e usada na ordenao Shell mostrada aqui. Evite seqncias que so potncias de 2 por razes matemticas complexas, elas reduzem a eficincia do algoritmo de ordenao (porm a ordenao ainda funciona)
/* A ordenao Shell. */ void shell (char *item, int count) { register int i, j, gap, k; char x, a[5]; a[0] = 9; a[1] = 5; a[2] = 3; a[3] = 1; for (k=0; k < 5; k++){ gap = a[k] ; for (i = gap; i < count ; i++) { x = item[i]; for (j=i-gap; x < item[j] && j >= 0; j = j - gap) item[j+gap] = item[j]; item[j+gap] = x; } } }

O lao for mais interno tem duas condies de teste. A comparao x< item[j] evita que os limites da matriz item sejam ultrapassados. Essas verificaes extras degeneraro at certo ponto o desempenho da ordenao Shell. A anlise da ordenao Shell apresenta alguns problemas matemticos que esto fora do escopo desta discusso, porm, importante destacar que este mtodo possui tempo de

Prof. Marco Antonio Barbosa 8

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

execuo proporcional a n1.2 para se ordenar n elementos. Essa uma reduo significativa com relao s ordenaes quadrticas n2.

Prof. Marco Antonio Barbosa 9

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

2.3 Classificao por Troca


2.3.1 Mtodo Bolha
A ordenao mais conhecida (e mais difamada) a ordenao bolha. Sua popularidade vem de seu nome fcil e de sua simplicidade. Porm, uma das piores ordenao j concebidas. A ordenao bolha a ordenao por trocas. Ela envolve repetidas comparaes e, se necessrio, a troca de dois elementos adjacentes. Os elementos so como bolhas em um tanque de gua cada uma procura o seu prprio nvel. A forma mais simples de ordenao bolha mostrada aqui:
/* A ordenao bolha. */ void bubble(char *item, int count) { register int a, b; register char t; for (a=1; a < count; a++) for (b = count-1; b >= a; b--){ if (item[b-1] > item[b]){ /* trocas os elementos */ t = item[b-1]; item[b-1] = item[b]; item[b] = t; }

} }

A anlise do mtodo bolha simple: existem n-1 passagens e n-1 comparaes em cada passagem, sendo assim, o nmero total de comparaes (n-1) x (n-1) = n2 2n + 1, que, considerando-se a ordem assinttica equivale a 0(n2).

2.3.2 Mtodo Quicksort


O QuickSort baseado em uma estratgia de dividir para conquistar e um dos algoritmos de ordenao mais populares. O algoritmo QuickSort pode ser dividido nos seguintes passos: O array A[p..r] subdividido em dois arrays A[p..q] e A[q+1..r] no vazios tal que cada elemento de A[p..q] menor ou igual a cada elemento de A[q+1..r]. O ndice q calculado como parte deste particionamento. Os dois subarrays A[p..q] e A[q+1..r] so ordenados por recursivas chamadas do QuickSort.

Por exemplo, dado o array f e d h a c g b, e tomando o valor d para partio, o primeiro passo do quicksort rearranja o array da seguinte forma: Prof. Marco Antonio Barbosa 10

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Incio: passo 1:

fedhacgb bcadhegf

Esse processo ento repetido para cada seo isto , b c a e h e g f.


void quicksort(int *A, int left, int right) { register int i, j; int x,y; i=left; j=right; x=A[(left+right)/2];

// Elemento intermedirio como pivot //

do{ while(A[i]<x && i<right) i++; while(A[j]>x && j>left) j--; if(i<=j){ y = A[i] ; A[i] = A[j] ; A[j] = y; i++; j--; } }while(i<=j); if(left<j) quicksort(A,left,j); if(i<right) quicksort(A,i,right); }

A figura abaixo ilusta o funcionamento do QuickSort: pivot = 5 (elemento intermedirio)

O tempo de execuo do QuickSort varia conforme o particionamento: balanceado ou no. Se particionamento balanceado o QuickSort roda to rpido quanto o MergeSort, com a vantagem que no precisar de array auxiliar. O pior caso do QuickSort ocorre quando o particionamento gera um conjunto com 1 elemento e outro com n-1 elementos para todos os passos do algoritmo. Desde que o particionamento custa a recorrncia neste caso torna-se Prof. Marco Antonio Barbosa 11

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

e como

no difcil mostrar que

Se o particionamento gera dois sub-conjuntos de tamanho n/2 temos a recorrncia que pelo teorema master nos d

Pode ser mostrado que o caso mdio do QuickSort muito prximo ao caso acima, ou seja O(n lg n). Na implementao acima, o elemento "pivot'' foi tomado como o elemento intermedirio do conjunto. Outras escolhas podem ser obtidas para gerar um melhor particionamento. Algumas verses do QuickSort escolhem o "pivot'' aleatoriamente e se demonstra que esta conduta gera resultados prximos do ideal. Pergunta: um algoritmo baseado em comparaes pode rodar em tempo menor que O(n lg n)? Resposta abaixo:

Limite inferior para ordenao


Um algoritmo baseado em comparaes pode rodar em tempo menor que O(nlgn)? A resposta no! Considere um algoritmo de ordenao arbitrrio baseado em comparaes. Podemos assumir, sem perda de generalidade, que todas as comparaes feitas pelo algoritmo so do tipo A rvore de deciso desse algoritmo uma rvore binria cujos vrtices internos esto rotulados por pares de elementos da sequncia de entrada, denotados por exemplo por "ai : aj" . As arestas de um vrtice interno para os seus filhos esto rotuladas uma por e a outra por >. O rtulo da raiz da rvore corresponde primeira comparao efetuada pelo algoritmo. A sub rvore esquerda da raiz descreve as comparaes subsequentes, caso o resultado desta primeira comparao, digamos, ai aj , seja verdadeiro. J a sub rvore direita descreve as comparaes caso o resultado dessa comparao seja falso. Com isso, cada sequncia (a1, . . . , an) corresponde a um caminho da raiz at uma folha da rvore de deciso.

Prof. Marco Antonio Barbosa 12

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Exemplo: A rvore de deciso do algoritmo de insero para n = 3 :

Por exemplo, se a1 = 10, a2 = 5 e a3 = 7, o caminho do algoritmo na rvore de deciso acima o caminho vermelho. Existem n! permutaes de (1..n). Cada uma delas deve aparecer em alguma das folhas da rvore de deciso. Portanto, a rvore de deciso deve ter pelo menos n! folhas. Por outro lado, uma rvore binria de altura h tem no mximo 2h folhas. Assim, se h a altura da rvore de deciso de um algoritmo de ordenao baseado em comparaes, ento 2h n! Sabemos, pela frmula de Stirling, que Ento, podemos concluir que

Portanto, de fato, qualquer algoritmo de ordenao baseado em comparas tem complexidade de pior caso (n log n)

Prof. Marco Antonio Barbosa 13

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

2.4 Classificao por Seleo e por rvore


2.4.1 Ordenao por Seleo
A ordenao por seleo seleciona o elemento de menor valor e troca-o pelo primeiro elemento. Ento, para os n-1 elementos restantes, encontrado o elemento de menor chave, trocado pelo segundo elemento e assim por diante. As trocas continuam at os dois ltimos elementos. Por exemplo, se o mtodo de seleo fosse utilizado no vetor bdac, cada passo se apresentaria como: inicial passo 1 passo 2 passo 3
/* A ordenao por seleo. void select(char *item, int count) { register int a, b, c; int exchange; char t; for (a = 0; a < count-1; a++){ exchange = 0; c = a; t = item[a]; for (b = a +1; b < count; b++) { if (item[b] < t) { c = b; t = item[b]; exchange = 1; } } if (exchange) { item[c] = item[a]; item[a] = t; } } }

b a a a

d d b b

a b d c
*/

c c c d

Da mesma forma que na ordenao por Bolha, o lao mais externo executado n-1 vezes e o lao interno 1/2(n) vezes. Como resultado, a ordenao por seleo requer: 1/2(n2- n) comparaes, que a tornam muito lenta para um nmero grande de itens. O nmero de trocas para o melhor caso e o pior caso so: melhor: 3(n-1) pior: n2/4 + 3(n-1) Para o melhor caso, quando a lista est inicialmente ordenada, apenas n-1 elementos precisam ser movimentadose cada movimento requer trs trocas. O pior caso aproximase do nmero de comparaes. O caso mdio difcil de ser determinado e seu desenvolvimento est alm do escopo desta disciplina, no entanto igual a: n(log n + y) onde y a constante de Euler, aproximadamente 0,577216.

Prof. Marco Antonio Barbosa 14

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Embora o nmero de comparaes para a ordenao bolha e para a ordenao por seleo seja o mesmo, o nmero de trocas, no caso mdio, muito menor para a ordenao por seleo.

2.4.2 rvores Binrias


Embora possa haver muitos tipos diferentes de rvores, rvores binrias so especiais porque, quando ordenadas, elas conduzem a pesquisas, inseres e excluses rpidas. Cada item em uma rvore consistem em informao juntamente com um elo ao membro esquerdo e um elo ao membro direito. A Figura abaixo mostra uma pequena rvore. N raiz info

. .
subrvore esquerda info subrvore direita info 0 .

. .
info 0 0

info info 0 0 0 0 ns terminais Exemplo de uma rvore binria de altura trs Uma terminologia especial necessria quando se discutem rvores. A raiz o primeiro item em uma rvore. Cada item, de dado chamado de n (ou, as vezes, de folha) da rvore e qualquer parte da rvore chamada uma subrvore. Um n que no tem subrvores ligadas a ele chamado de n terminal. A altura da rvore igual ao nmero de camadas que as razes atingem. Lembre-se, porm, de que uma rvore apenas uma maneira de estruturar dados na memria e que a memria tem sempre formato linear. Em certo sentido, a rvore binria uma forma especial de lista encadeada. Pode-se inserir, excluir e acessar itens em qualquer ordem. Embora rvores sejam fceis de visualizar, elas apresentam alguns difceis problemas de programao. A maioria das funes que usam rvores recursiva porque a prpria rvore uma estrutura de dados recursiva. Isto , cada subrvore ela prpria uma rvore. A forma como uma rvore ordenada depende de como ela ser referenciada. O procedimento de acesso a cada n na rvore chamado de transversalizao da rvore.

Prof. Marco Antonio Barbosa 15

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Considere a rvore a seguir: d b f

Existem trs maneiras de percorrer uma rvore: de forma ordenada, preordenada e psordenada. Usando a forma ordenada, voc visita a subrvore da esquerda, a raiz e, em seguida, a subrvore da direita. Na maneira preordenada, voc visita a raiz, a subrvore da esquerda e, em seguida, a subrvore da direita. Com a ps-ordenada, voc visita a subrvore da esquerda, a subrvore da direita e, depois, a raiz. A ordem de acesso rvore usando cada mtodo : Ordenada: abcdefg preordenada: d b a c f e g ps-ordenada: a c b e g f d Uma rvore binria ordenada aquela em que a subrvore da esquerda contm ns que so menores ou iguais raiz e os da direita so maiores que a raiz. A funo seguinte, stree(), constri uma rvore binria ordenada:
struct tree { char info; struct tree *left; struct tree *right; }; struct tree *stree{ struct tree *root; struct tree *r; char info} { if (!r) { r = (struct tree *) malloc (sizeof(struct tree)); if (!r) { printf(sem memoria \n); exit(0); } r -> left = NULL; r -> right = NULL; r -> info = info; if (!root) return r; /* primeira entrada */ if (info < root -> info) root -> left = r; else root -> right = r; return r; } if (info < r -> info) stree (r, f -> left, info); else stree(r, r-> right, info);

Prof. Marco Antonio Barbosa 16

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

O algoritmo anterior simplesmente segue os elos atravs da rvore indo para a esquerda ou para a direita baseado no campo info. Para utilizar essa funo voc precisa de uma varivel global que contenha raiz da rvore. Essa varivel deve ser inicializada com NULL e um ponteiro raiz ser definido na primeira chamada a stree(). Chamada subseqentes a stree() no necessitam redefinir a raiz. Assumindo que o nome dessa varivel global seja rt, para chamar a funo stree() voc usaria:
/* Chama stree() */ if (!rt ) rt = stree(rt, rt, info); else stree(rt, rt, info);

Dessa forma, tanto o primeiro quanto os elmentos subsequentes podem ser inseridos corretamente. Para percorrer a rvore construda com stree() de forma ordenada e imprimir o campo info de cada n, voc poderia usar a funo inorder(), mostrada a seguir:
void inorder(struct tree *root){ if (!root) return; inorder (root -> left); if (root -> info) printf(%c , root -> info); inorder (root -> right); }

Essa funo recursiva retorna quando um n terminal (um ponteiro nulo) encontrado. As funes que percorrem a rvore de forma preordenada e ps-ordenada so mostradas na listagem a seguir:
void preorder(struct tree *root){ if (!root) return; if (root -> info) printf(%c , root -> info); preorder (root -> left); preorder (root -> right); } void postorder(struct tree *root){ if (!root) return; postorder (root -> left); postorder (root -> right); if (root -> info) printf(%c , root -> info); }

Neste momento, considere um programa simples, porm interessante, para construir uma rvore binria e imprimir uma rvore lateralmente na tela. O programa requer apenas uma pequena modificao na funo inorder(). Essa nova funo chamada de print_tree(), como mostrado aqui:

Prof. Marco Antonio Barbosa 17

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

void print_tree(struct tree *r, int l){ int i; if (r == NULL) return; print_tree(r -> right, l+1); for (I = 0; I < l; i++) printf( ); printf(%c\n, r -> info); print_tree(r -> left, l+1); }

O programa completo de impresso de rvores visto a seguir. Tente inserir vrias rvores para ver como cada uma construda.
/* Este programa mostra uma rvore binria. #include <stdlib.h> #include <stdio.h> struct tree{ char info; struct tree *left; struct tree *right; }; struct tree *root; /* primeiro n da rvore*/ struct tree *stree(struct tree *root, struct tree *r, char info); void print_tree(struct tree *root, int l); void main(void){ char s[80]; root = NULL; /* inicializa a raiz*/ do { printf(insira uma letra: ); gets(s); if (!root) root = stree (root, root, *s); else stree (root, root, *s); } while (*s); printf_tree(root, NULL); } struct tree *stree( struct tree *root, struct tree *r, char info) { if (!r) { r = (struct tree *) malloc(sizeof(struct tree)); if (!r) { printf(Sem memria\n); exit(0); } r -> left = NULL; r -> right = NULL; */

Prof. Marco Antonio Barbosa 18

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

r -> info = info; if (!root) return r; /* primeira entrada */ if (info < root -> info) root -> left = r; else root -> right = r; return r; } if (info < r -> info) stree (r, r-> left, info); else stree (r, r-> right, info); } void print_tree(struct tree *r, int l) { int i; if (!r) return; printf_tree(r -> right, l+1); for (i = 0; i < l; i++) printf( ); printf(%c\n, r -> info); print_tree(r -> left, l +1); }

Quando as subrvores tem a mesma (ou quase a mesma) altura so chamadas rvores balanceadas. As rvores que s apresentam subrvore esquerda ou a direita so denominadas rvores degeneradas. a b c d Exemplo de rvore degenerada

Prof. Marco Antonio Barbosa 19

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Teoria dos Grafos


Exemplos de grafos: - Mapas - Redes (computadores, telefonia, etc) - Conexes em Circuitos Integrados - Diagrama de Fluxo de Dados - Redes Neurais - Qumica Orgnica (desenho de molculas) Definio (Informal) de Grafo. Um grafo um conjunto no vazio de ns (vrtices) e um conjunto de arcos (arestas) tais que cada arco conecta dois ns. a3 a2 a1 1 a5 3 a6 4 5 2

a4

Figura 1 Exemplo de Grafo Definio (Formal) de Grafo. Um grafo uma tripla ordenada (N,A,g), onde: N = um conjunto no vazio de ns (vrtices); A = um conjunto de arestas (arcos) g = uma funo que associa cada arco a a um par no ordenado (x,y) de ns, chamamos a este par as extremidades de a. Por exemplo, para o grafo acima temos a seguinte definio: G1 = ({1,2,3,4,5},{a1, a2, a3, a4, a5, a6},g1), tal que: g1(a1) = (1,2), g1(a2) = (1,2), g1(a3) = (2,2), g1(a4) = (2,3), g1(a5) = (1,3) e g1(a6) = (3,4) Definio Grafo Direcionado (Dgrafo). Um Grafo Direcionado uma uma tripla ordenada (N,A,g), onde: N = um conjunto no vazio de ns (vrtices); A = um conjunto de arestas (arcos) g = uma funo que associa cada arco a a um par ordenado <x,y> de ns, onde x o n de origem e y o n de destino.

Prof. Marco Antonio Barbosa 20

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Exemplo 2, considere o dgrafo da Figura 2, a sua definio dada por: G2 = ({1,2,3,4},{a1, a2, a3, a4},g2), tal que: g2(a1) = <1,2>, g2(a2) = <1,4>, g2(a3) = <1,3>, g2(a4) = <3,1>. 2 a1 1 a3 a4 3 a2 4

Figura 2 Exemplo de Grafo Direcionado

Terminologia
- Dois ns em um grafo so ditos adjacentes se ambos so extremidades de algum arco. Por exemplo, os ns 1 e 3 na Figura 1 so adjacentes, j os ns 1 e 4 no o so. - Um lao em um grafo um arco com extremidades n-n para algum n n. Por exemplo o arco a3 na Figura 1, um arco com extremidades 2-2. - Dois arcos com as mesmas extremidades so chamados arcos paralelos. Por exemplo, os arcos a1 e a2 na Figura 1. - Um grafo simples um grafo que no tem laos nem arcos paralelos. - Um n isolado um n que no adjacente a nenhum outro. Por exemplo, o n 5 na Figura 1. - O grau de um n o nmero de arcos que terminam naquele n. Por exemplo, considerando-se a Figura 1, temos: grau(1) = 3, grau(2) = 5, grau(3) = 3, grau(4) = 1 e grau(5) = 0. - Um grafo completo um grafo no qual dois ns distintos quaisquer so adjacentes. - Um caminho do n n0 para o n nk uma sequencia n0, a0, n1, a1,...,nk-1, ak Por exemplo, na Figura 1 temos como um caminho do n 1 para o n 4 1, a5,3,a6,4 - O comprimento de um caminho o nmero de arcos que ele contm. - Um ciclo em um grafo um caminho de algum n para ele mesmo, tal que nenhum outro n aparea mais de uma vez. Por exemplo, na Figura 1, o caminho: 1,a1,2,a4,3,a5,1 forma um ciclo. - Um grafo sem ciclos chamado de acclico.

Prof. Marco Antonio Barbosa 21

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

- Um grafo dito conexo se existe um caminho de qualquer n para qualquer outro. Por exemplo, o grafo da Figura 3 um grafo conexo, os grafo das Figuras 1 e 2 no so grafos conexos. a3 a2 a1 1 a5 3 a6 4 2

a6

Figura 3 Grafo Conexo

Representaes de Grafos no Computador


Matriz de Adjacncia Suponha que um grafo tem n ns, numerados n1, n2,nn. Essa numerao impe uma ordem arbitrria nos ns. Aps a ordenao dos ns, podemos formar uma matriz n x n, onde os elementos de i,j o nmero de arcos entre os ns ni e nj. Essa matriz chamada de matriz de adjacncia. Assim aij= p, se existem p arcos entre ni e nj. Por exemplo:

1 3 A =

1 1 0 1 1 0 1 0 0 1 0 2 1 0 2 0

Figura 4 Grafo e sua Matriz de Adjacncia O elemento 1,1 1 devido ao lao no n 1.

Prof. Marco Antonio Barbosa 22

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Lista de Adjacncia.
Forma alternativa para representao de grafos. Neste caso temos uma lista de ns e para cada ns uma lista dos ns atingveis. Por exemplo, a Figura 5 apresenta uma lista de adjacncia para o grafo da Figura 4. 1 2 3 4

1 2 4 1 3 2 4 4 1 3 3 Figura 5 Lista de Adjacncia

Problemas de Grafos
O Caminho de Euler - As Pontes de Knigsberg
No sculo XIII havia na cidade de Knigsberg um conjunto de sete pontes (a,b,c,d,e,f e g) que cruzavam o rio Pregel . Elas conectavam duas ilhas (A e D) entre si e as ilhas com as margens (B e C). Por muito tempo os habitantes daquela cidade perguntavam-se se era possvel cruzar as sete pontes numa caminhada contnua sem que se passasse duas vezes por qualquer uma delas.

(a)

(b)

Figura 6 O problema Original e sua Representao em Grafo

Definio Caminho de Euler. Um Caminho de Euler em um grafo G um caminho que usa cada arco em exatamente uma vez. Teorema sobre Caminhos de Euler. Existe um Caminho de Euler em um grafo conexo, se e somente se, no existirem ns mpares ou existirem exatamente dois ns mpares.
Deste fato conclui-se que o passeio de Knigsberg no possvel, o pois todos os ns do grafo da Figura 6 (b) so mpares.

Prof. Marco Antonio Barbosa 23

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Algoritmo CaminhoEuler CaminhoDeEuler (matriz A n x n) // Determinar se existe um caminho de Euler em um grafo conexo // com matriz de adjacncia A variveis locais: inteiro total // nmero de ns mpares encontrados inteiro grau // o grau de um n inteiro i,j // ndices dos arranjos total = 0 i=1 enquanto total <= 2 e i <= n faa grau = 0 para j = 1 at n faa grau = grau + A[i,j] // encontra o grau do n i fim_para se grau mpar ento total = total + 1 // n de grau mpar encontrado fim_se i=i+1 fim_enquanto se total > 2 ento escreva(No existe um Caminho de Euler) seno escreva(Existe um Caminho de Euler) fim_se fim

O Problema do Circuito Hamiltoniano


Willian Rowan Hamilton (1805-1865), colocou um problema muito semelhante ao de Euler. Neste caso a questo reside em percorrer um ciclo percorrendo cada n exatamente uma vez. Embora este problema seja muito semelhante ao de Euler, ao contrrio deste, no foi encontrado um algoritmo eficiente para responder tal resto. Como exemplo, considere o grafo abaixo:

Figura 7 Exemplo Circuito Hamiltoniano Existe um Circuito Hamiltoniano para o grafo da Figura 7. Resposta:. Sim, 0 1 4 2 3 0 e 0 3 2 4 1 0

Prof. Marco Antonio Barbosa 24

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Ciclo de Hamilton - Tentando Todas as Possibilidades


- A maneira mais tradicional de resolver problemas deste tipo e testando todas as possibilidades.

procedure Visita (k: integer ) ; var j : integer ; begin Tempo := Tempo + 1; d[k ] := Tempo; for j := 0 to Grafo.NumVertices - 1 do i f Grafo.Mat[k, j ] > 0 then i f d[ j ] = 0 then Visita ( j ) ; Tempo := Tempo -1; d[k] := 0; end;
- O custo de execuo de tal algoritmo proporcional ao nmero de chamadas para o procedimento Visita. Para um grafo completo, (arestas ligando todos os pares de ns) existem N! ciclos simples. Desta forma conclui-se que o custo da execuo de tal algoritmo proibitivo para instncia de problemas muito grandes. Um soluo alternativa para tais problemas so os mtodos heursticos.

Heursticas
Heurstica um algoritmo que pode produzir um bom resultado (ou at a soluo tima), mas pode tambm no obter soluo ou obter uma distante da tima. A caracterstica principal dos mtodos heursticos dar um resposta em tempo viavl, entretanto no temos a garantia da qualidade da soluo encontrada. Como exemplo de um mtodo heurstico vamos considerar uma variao do problema do Circuito Hamiltoniano. Consideremos um grafo com pesos e reformulemos a questo da seguinte forma: Se existe um Circuito Hamiltoniano para o grafo, podemos encontrar um de peso mnimo? Essa reformulao conhecida como o Problema do Caixeiro Viajante (PCV).

Heurstica para o PCV - Algoritmo do vizinho mais prximo, heurstica gulosa simples: 1. Inicie com um vrtice arbitrrio. 2. Procure o vrtice mais prximo do ltimo vrtice adicionado que no esteja no caminho e adicione ao caminho a aresta que liga esses dois vrtices. 3. Quando todos os vrtices estiverem no caminho, adicione uma aresta conectando o vrtice inicial e o ltimo vrtice adicionado. - Complexidade: 0(n2), sendo n o nmero de cidades, ou 0(d), sendo d o conjunto de distncias entre cidades. - Aspecto negativo: embora todas as arestas escolhidas sejam localmente mnimas, a aresta final pode ser bastante longa.

Prof. Marco Antonio Barbosa 25

Universidade de Cruz Alta


Cincia da Computao Estrutura de Dados II

Para este mtodo heurstico, considere o grafo da Figura 8 abaixo:

(a)

(b)

Figura 9 O Problema do Caixeiro Viajante Na Figura (a), temos a cidades (ns) e as estradas que ligam estas cidades (arestas), na Figra 9 (b), temos os pesos, ou a distncia em Km entre as cidades, por exemplo: a distncia entre a cidade 0 e 1 3. Para este problema, por ser um problema de instncia pequena, sabe-se que a soluo tima obtida percorrendo-se o caminho: 0 1 2 5 3 4 0 (comprimento 58). Resolvendo este problema usando a heurstica gulosa (vizinho mais prximo) procedemos da seguinte forma: - se iniciarmos pelo vrtice 0, o vrtice mais prximo o 1 com distncia 3. - a partir do 1, o mais prximo o 2, a partir do 2 o mais prximo o 4, a partir do 4 o mais prximo o 3, a partir do 3 restam o 5 e o 0. - Desta forma, o comprimento do caminho 0 1 2 4 3 5 0 60. Como podemos notar a soluo pior do que a melhor soluo, entretanto est prxima do resultado esperado e a resposta pde ser obtida em tempo polinomial, enquanto que a soluo tima para ser obtida pela mtodo tentantiva e erro levaria tempo n! para ser obtida, o que na prtica invivel como soluo algortmica.

Prof. Marco Antonio Barbosa 26