19.04.2013 Views

Técnicas de Programação Avançada

Técnicas de Programação Avançada

Técnicas de Programação Avançada

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

PROF. MATEUS CONRAD BARCELLOS DA COSTA<br />

TÉCNICAS DE PROGRAMAÇÃO AVANÇADA<br />

VITÓRIA<br />

2008


Governo Fe<strong>de</strong>ral<br />

Ministro <strong>de</strong> Educação<br />

Fernando Haddad<br />

CEFETES – Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

Diretor Geral<br />

Jadir José Pela<br />

Diretor <strong>de</strong> Ensino<br />

Denio Rebello Arantes<br />

Coor<strong>de</strong>nadora do CEAD – Centro <strong>de</strong> Educação a Distância<br />

Yvina Pavan Baldo<br />

Coor<strong>de</strong>nadoras da UAB – Universida<strong>de</strong> Aberta do Brasil<br />

Yvina Pavan Baldo<br />

Maria das Graças Zamborlini<br />

Designer Instrucional<br />

Danielli Veiga Carneiro<br />

Curso <strong>de</strong> Tecnologia em Análise e Desenvolvimento <strong>de</strong> Sistemas<br />

Coor<strong>de</strong>nação <strong>de</strong> Curso<br />

Isaura Nobre<br />

Professor Especialista/Autor<br />

Mateus Conrad Barcellos da Costa<br />

DIREITOS RESERVADOS<br />

Cefetes – Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

Av. Vitória - Jucutuquara - Vitória - ES - (27) 3331.2139<br />

Créditos <strong>de</strong> autoria da editoração<br />

Capa: Leonardo Tavares Pereira<br />

Projeto gráfico: Danielli Veiga Carneiro<br />

Iconografia: Moreno Cunha<br />

Editoração eletrônica: Mateus Conrad B. da Costa, Humberto Wanke, Monia Vignati<br />

Revisão <strong>de</strong> texto: Karina Bersan Rocha<br />

COPYRIGHT – É proibida a reprodução, mesmo que parcial, por qualquer meio, sem<br />

autorização escrita dos autores e do <strong>de</strong>tentor dos direitos autorais.<br />

Catalogação na fonte: Rogeria Gomes Belchior - CRB 12/417<br />

1.<br />

C837 Costa, Mateus Conrad Barcellos da<br />

<strong>Técnicas</strong> <strong>de</strong> programação avançada. / Mateus Conrad Barcellos da. – Vitória:<br />

CEFETES, 2008.<br />

132 p. : il.<br />

1. <strong>Programação</strong> (Computadores). I. Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do<br />

Espírito Santo. II. Título.<br />

CDD 005.1


Olá, Aluno(a)!<br />

É um prazer tê-lo conosco.<br />

O Cefetes oferece a você, em parceria com as Prefeituras e com o<br />

Governo Fe<strong>de</strong>ral, o Curso Tecnologia em Análise e Desenvolvimento<br />

<strong>de</strong> Sistemas, na modalida<strong>de</strong> a distância. Apesar <strong>de</strong> este curso ser<br />

ofertado a distância, esperamos que haja proximida<strong>de</strong> entre nós, pois,<br />

hoje, graças aos recursos da tecnologia da informação (e-mails, chat,<br />

vi<strong>de</strong>oconferência, etc.) po<strong>de</strong>mos manter uma comunicação efetiva.<br />

É importante que você conheça toda a equipe envolvida neste curso:<br />

coor<strong>de</strong>nadores, professores especialistas, tutores a distância e tutores<br />

presenciais, porque quando precisar <strong>de</strong> algum tipo <strong>de</strong> ajuda, saberá a<br />

quem recorrer.<br />

Na EaD – Educação a Distância, você é o gran<strong>de</strong> responsável pelo<br />

sucesso da aprendizagem. Por isso, é necessário que se organize para<br />

os estudos e para a realização <strong>de</strong> todas as ativida<strong>de</strong>s, nos prazos<br />

estabelecidos, conforme orientação dos Professores<br />

Especialistas e Tutores.<br />

Fique atento às orientações <strong>de</strong> estudo que se encontram<br />

no Manual do Aluno!<br />

A EaD, pela sua característica <strong>de</strong> amplitu<strong>de</strong> e pelo uso <strong>de</strong><br />

tecnologias mo<strong>de</strong>rnas, representa uma nova forma <strong>de</strong> apren<strong>de</strong>r,<br />

respeitando, sempre, o seu tempo.<br />

Desejamos-lhe sucesso!<br />

Equipe do CEFETES


ICONOGRAFIA<br />

Veja, abaixo, alguns símbolos utilizados neste material para guiá-lo em seus estudos.<br />

Fala do professor.<br />

Conceitos importantes. Fique atento!<br />

Ativida<strong>de</strong>s que <strong>de</strong>vem ser elaboradas por você, após a leitura dos textos.<br />

Indicação <strong>de</strong> leituras complementares, referentes ao conteúdo estudado.<br />

Destaque <strong>de</strong> algo importante, referente ao conteúdo apresentado. Atenção!<br />

Reflexão/questionamento sobre algo importante, referente ao conteúdo<br />

apresentado.<br />

Espaço reservado para as anotações que você julgar necessárias.


Sumário<br />

1. ABSTRAÇÃO DE DADOS.......................................................................................................................... 9<br />

1.1 INTRODUÇÃO .......................................................................................................................................... 9<br />

1.2 CONCEITOS DE ABSTRAÇÃO DE DADOS ................................................................................................. 12<br />

1.2.1 Abstração em Computação.............................................................................................................. 12<br />

1.2.2 Abstração <strong>de</strong> Procedimentos ........................................................................................................... 14<br />

1.2.3 Tipos Abstratos <strong>de</strong> Dados................................................................................................................ 16<br />

1.2.4 Implementação <strong>de</strong> Tipos Abstratos <strong>de</strong> Dados.................................................................................. 23<br />

1.2.5 Avaliação <strong>de</strong> Implementações <strong>de</strong> Tipos Abstratos <strong>de</strong> Dados .......................................................... 25<br />

2. TIPOS ABSTRATOS DE DADOS FUNDAMENTAIS. ......................................................................... 30<br />

2.1 PILHAS.................................................................................................................................................. 30<br />

2.1.1 Especificação do TAD Pilha............................................................................................................ 32<br />

2.1.2 Implementação <strong>de</strong> Pilhas em Arranjos............................................................................................ 34<br />

2.2 FILAS .................................................................................................................................................... 37<br />

2.2.1 Especificação do TAD FILA............................................................................................................ 39<br />

2.2.2 Implementação <strong>de</strong> Filas em arranjos com <strong>de</strong>slocamento................................................................ 42<br />

2.2.3 Implementação <strong>de</strong> Filas com Arranjos circulares........................................................................... 45<br />

2.3 IMPLEMENTAÇÃO DE TADS COM ALOCAÇÃO DINÂMICA DE MEMÓRIA................................................. 47<br />

2.3.1 Revisão <strong>de</strong> Alocação Dinâmica <strong>de</strong> Memória................................................................................... 47<br />

2.3.2 Implementação do TAD Pilha ......................................................................................................... 55<br />

2.3.3 Implementação do TAD Fila ........................................................................................................... 59<br />

3. LISTAS E ÁRVORES................................................................................................................................ 65<br />

3.1 LISTAS CIRCULARES............................................................................................................................. 65<br />

3.2 LISTA CIRCULAR DUPLAMENTE ENCADEADA ....................................................................................... 75<br />

3.3 ÁRVORES BINÁRIAS.............................................................................................................................. 82<br />

3.3.1 Árvore Binária <strong>de</strong> Pesquisa............................................................................................................. 83<br />

3.3.2 O TAD Árvore Binária <strong>de</strong> Pesquisa ................................................................................................84<br />

3.3.3 Implementação do TAD árvore Binária <strong>de</strong> Pesquisa ...................................................................... 85<br />

4. PESQUISA EM MEMÓRIA PRIMÁRIA................................................................................................ 99<br />

4.1 PESQUISA SEQÜENCIAL...................................................................................................................... 101<br />

4.1.1 Implementação da Pesquisa Seqüencial........................................................................................ 102<br />

4.1.2 Tempo <strong>de</strong> execução <strong>de</strong> algoritmos.................................................................................................103<br />

4.2 PESQUISA BINÁRIA ............................................................................................................................. 106<br />

4.3 TABELAS HASH................................................................................................................................... 108<br />

4.3.1 Operações <strong>de</strong> Inserção e Pesquisa em Tabelas Hash.................................................................... 111<br />

4.3.2 Tratamento <strong>de</strong> Colisões................................................................................................................. 114<br />

4.3.3 Tratamento <strong>de</strong> Colisão usando en<strong>de</strong>reçamento Aberto................................................................. 116<br />

5. ORDENAÇÃO EM MEMÓRIA PRIMÁRIA. ...................................................................................... 118<br />

5.1 CONCEITOS BÁSICOS DE ORDENAÇÃO ................................................................................................ 118<br />

5.1.1 Operações Típicas <strong>de</strong> processos <strong>de</strong> Or<strong>de</strong>nação ............................................................................ 119<br />

5.2 MÉTODOS DE ORDENAÇÃO................................................................................................................. 119<br />

5.2.1 Or<strong>de</strong>nação por Seleção ................................................................................................................. 120<br />

5.2.2 Método da Inserção....................................................................................................................... 121<br />

5.2.3 Método da Bolha ........................................................................................................................... 123<br />

5.2.4 Desempenho dos métodos <strong>de</strong> Seleção, Inserção e Bolha............................................................... 124<br />

5.2.5 Método <strong>de</strong> Shell ............................................................................................................................. 124<br />

5.2.6 O Método Quicksort ...................................................................................................................... 128


Olá!<br />

Meu nome é Mateus Barcellos Costa, sou professor<br />

e pesquisador do CEFET-ES <strong>de</strong>s<strong>de</strong> 2005. Atuo na<br />

área <strong>de</strong> Engenharia <strong>de</strong> Software e sou professor <strong>de</strong><br />

disciplinas <strong>de</strong> <strong>Programação</strong>. Se você quiser mais<br />

informações sobre mim e sobre os trabalhos que<br />

<strong>de</strong>senvolvo, po<strong>de</strong> visitar minha página pessoal em<br />

http://www.sr.cefetes.br/~mcosta.<br />

Produzi o material que ora lhe apresento como<br />

instrumento básico para o estudo da disciplina <strong>de</strong><br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong>. Nesta<br />

disciplina iremos aprofundar nossos<br />

conhecimentos em <strong>Programação</strong> <strong>de</strong><br />

Computadores usando uma linguagem imperativa<br />

ou procedimental. Exemplos <strong>de</strong>stas linguagens<br />

são C e Pascal. Como <strong>de</strong> costume no nosso<br />

Curso, iremos adotar a linguagem C, em nossos<br />

exemplos e implementações. No entanto, é<br />

preciso que você saiba que os conceitos estudados<br />

aqui vão além da linguagem e po<strong>de</strong>m ser<br />

aplicados em diversos cenários, na programação<br />

e na Engenharia <strong>de</strong> Software como um todo.


1. ABSTRAÇÃO DE DADOS.<br />

Olá! Neste Capítulo iremos discutir e apren<strong>de</strong>r<br />

sobre Abstração <strong>de</strong> Dados. Abstração <strong>de</strong> Dados é<br />

uma técnica <strong>de</strong> programação que visa simplificar<br />

o <strong>de</strong>senvolvimento <strong>de</strong> programas. Sua aplicação<br />

po<strong>de</strong> se dar no <strong>de</strong>senvolvimento <strong>de</strong> programas<br />

menores. Mas po<strong>de</strong>mos afirmar que seria<br />

impossível o <strong>de</strong>senvolvimento <strong>de</strong> sistemas que<br />

temos hoje, com milhões <strong>de</strong> linhas <strong>de</strong> código,<br />

sem o uso <strong>de</strong> abstração <strong>de</strong> dados.<br />

1.1 INTRODUÇÃO<br />

Um programa <strong>de</strong> computador <strong>de</strong>senvolvido para aten<strong>de</strong>r alguma<br />

finalida<strong>de</strong> prática é, normalmente, um artefato complexo. Por esse<br />

motivo, a ativida<strong>de</strong> <strong>de</strong> <strong>de</strong>senvolvimento <strong>de</strong>sses artefatos, a<br />

programação <strong>de</strong> computadores, está entre as ativida<strong>de</strong>s mais<br />

complexas <strong>de</strong>sempenhadas pelo homem. Se você cursou disciplinas<br />

introdutórias <strong>de</strong> programação, po<strong>de</strong> estar se questionando: Ora,<br />

<strong>de</strong>senvolver um programa não é tão complexo assim! Basta<br />

compreen<strong>de</strong>r o problema, suas entradas e suas saídas e construir a<br />

solução usando uma linguagem <strong>de</strong> programação. Simples não? Não!<br />

A história da programação tem dado provas que programar não é tão<br />

simples assim.<br />

Figura 1: O Gap Semântico.<br />

Programar é uma ativida<strong>de</strong> complexa por diversos aspectos, tanto <strong>de</strong><br />

cunho teórico quanto prático. Dentre estes aspectos <strong>de</strong>stacamos os<br />

seguintes:<br />

1. Programar um computador significa <strong>de</strong>senvolver programas <strong>de</strong><br />

computadores (formais e precisos) para aten<strong>de</strong>r a finalida<strong>de</strong>s<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 9


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 10<br />

práticas <strong>de</strong>finidas em termos <strong>de</strong> conceitos do mundo real<br />

(informais e imprecisos). Existe um abismo entre o mundo<br />

dos problemas reais e o mundo das soluções. Esse abismo é<br />

chamado na computação <strong>de</strong> gap semântico. Transpor o abismo<br />

é um dos <strong>de</strong>safios centrais da programação <strong>de</strong> computadores e<br />

da Engenharia <strong>de</strong> Software como um todo. A Figura 1 é uma<br />

alegoria que busca mostrar a função do <strong>de</strong>senvolvedor <strong>de</strong><br />

software: Transpor o abismo entre o mundo informal (dos<br />

problemas) e o mundo formal (das soluções computacionais).<br />

Nessa transposição existem muitos <strong>de</strong>safios e perigos que<br />

po<strong>de</strong>m dificultar a trajetória do <strong>de</strong>senvolvedor.<br />

2. Muitas vezes, em disciplinas iniciais <strong>de</strong> programação,<br />

<strong>de</strong>paramo-nos com o <strong>de</strong>senvolvimento <strong>de</strong> programas mais<br />

simples, <strong>de</strong> cunho didático. Por exemplo, programas para<br />

calcular médias ou somatórios. Em outros momentos fazemos<br />

programas para apren<strong>de</strong>r a usar um certo mecanismo, por<br />

exemplo, apontadores. Aqui, estamos nos referindo a<br />

programas <strong>de</strong> computadores para ajudar pessoas a resolverem<br />

problemas do mundo real, problemas gran<strong>de</strong>s e complexos!<br />

Exemplos <strong>de</strong>sses problemas incluem:<br />

a. Gerenciar as operações financeiras <strong>de</strong> uma empresa;<br />

b. Controlar uma aeronave;<br />

c. Controlar os trens <strong>de</strong> uma malha ferroviária;<br />

d. Produzir edições diárias <strong>de</strong> um jornal;<br />

e. Gerenciar o processo <strong>de</strong> produção <strong>de</strong> um filme.<br />

3. Problemas como esses não são simples <strong>de</strong> se resolver.<br />

Conseqüentemente, programas voltados para essas finalida<strong>de</strong>s<br />

são complexos, levam muito tempo para ficar prontos, têm <strong>de</strong><br />

ser <strong>de</strong>senvolvidos por uma equipe <strong>de</strong> pessoas, utilizando<br />

diversas tecnologias e seguindo um processo <strong>de</strong><br />

<strong>de</strong>senvolvimento sistemático.<br />

4. Para aten<strong>de</strong>r às funcionalida<strong>de</strong>s esperadas, um programa <strong>de</strong>ve<br />

apresentar um conjunto <strong>de</strong> características que juntas vão<br />

tornar o programa efetivamente útil e <strong>de</strong>terminarão a<br />

qualida<strong>de</strong> do mesmo. Essas características são as seguintes:<br />

a. Um programa <strong>de</strong>ve estar correto, livre <strong>de</strong> erros;<br />

b. Um programa <strong>de</strong>ve ser robusto. Um programa robusto<br />

ou sistema robusto é aquele que consegue funcionar,<br />

mesmo que precariamente, diante <strong>de</strong> uma adversida<strong>de</strong>.<br />

Por exemplo, suponha que um programa precise dos<br />

dados x, y e z para realizar uma tarefa. Se este for<br />

robusto, na falta <strong>de</strong> um dos dados, o programa po<strong>de</strong><br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


tentar realizar o processamento possível com a<br />

ausência dado;<br />

c. Um programa <strong>de</strong>ve ser eficiente. A eficiência <strong>de</strong> um<br />

programa está relacionada ao seu tempo <strong>de</strong> execução<br />

(eficiência na execução) ou ao espaço em memória<br />

(eficiência na ocupação) <strong>de</strong> que necessita para executar<br />

(área <strong>de</strong> dados). Para um problema há infinitas<br />

soluções (programas). Quanto menores esses valores<br />

mais eficiente o programa. Em computação po<strong>de</strong>-se<br />

verificar se uma solução é ótima (mais eficiente<br />

possível) para um problema;<br />

d. Um programa <strong>de</strong>ve ser compatível com outros<br />

programas;<br />

e. Um programa <strong>de</strong>ve ser fácil <strong>de</strong> usar;<br />

f. Um programa <strong>de</strong>ve ser portável, po<strong>de</strong>ndo funcionar em<br />

diferentes plataformas ou sistemas operacionais;<br />

g. Um programa <strong>de</strong>ve ser íntegro, ou seja, <strong>de</strong>ve evitar que<br />

os dados sejam corrompidos ou violados;<br />

h. O processamento realizado pelo programa <strong>de</strong>ve ser<br />

verificável;<br />

i. O programa ou partes <strong>de</strong>le <strong>de</strong>vem po<strong>de</strong>r ser utilizados<br />

em outros cenários diferentes daquele para o qual o<br />

programa foi originalmente <strong>de</strong>senvolvido.<br />

5. Por último, <strong>de</strong>vemos consi<strong>de</strong>rar a ativida<strong>de</strong> <strong>de</strong> programação<br />

como ativida<strong>de</strong> econômica. Assim como outras ativida<strong>de</strong>s<br />

econômicas, o <strong>de</strong>senvolvimento <strong>de</strong> software é regido por leis<br />

<strong>de</strong> mercado que impõem severas exigências aos<br />

<strong>de</strong>senvolvedores. De acordo com essas leis, não basta apenas<br />

<strong>de</strong>senvolver um programa que atenda à finalida<strong>de</strong> esperada.<br />

Esses programas <strong>de</strong>vem ser <strong>de</strong>senvolvidos <strong>de</strong>ntro dos prazos e<br />

custos estabelecidos. Além disso, o programa precisa ter<br />

outras características importantes que permitam a sua<br />

evolução. Essas características são chamadas <strong>de</strong> fatores<br />

internos. São eles:<br />

a. Facilida<strong>de</strong> <strong>de</strong> manutenção;<br />

b. Facilida<strong>de</strong> <strong>de</strong> evolução;<br />

c. Facilida<strong>de</strong> <strong>de</strong> entendimento;<br />

d. Modularida<strong>de</strong>.<br />

Pelos motivos discutidos acima, o <strong>de</strong>senvolvimento <strong>de</strong> programas<br />

requer a aplicação <strong>de</strong> princípios, métodos e técnicas que diminuam a<br />

complexida<strong>de</strong> <strong>de</strong>sse <strong>de</strong>senvolvimento. A Abstração <strong>de</strong> Dados envolve<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 11


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 12<br />

uma série <strong>de</strong> conceitos e princípios para esse fim. A seguir<br />

discutiremos esses conceitos.<br />

Ativida<strong>de</strong>s<br />

Nesta introdução foram levantados cinco aspectos que<br />

tornam o <strong>de</strong>senvolvimento <strong>de</strong> software uma tarefa<br />

difícil. Para atacar esses aspectos e tornar o<br />

<strong>de</strong>senvolvimento <strong>de</strong> software mais simples são<br />

consi<strong>de</strong>radas três dimensões: Processo <strong>de</strong><br />

<strong>de</strong>senvolvimento, Pessoas (partes interessadas:<br />

clientes, <strong>de</strong>senvolvedores) e Tecnologias <strong>de</strong><br />

<strong>de</strong>senvolvimento. Releia os cinco motivos e tente<br />

escrever um texto resumido, estabelecendo uma<br />

relação entre esses 5 motivos e essas três dimensões.<br />

1.2 CONCEITOS DE ABSTRAÇÃO DE DADOS<br />

1.2.1 Abstração em Computação<br />

A abstração é um dos conceitos mais importantes da computação.<br />

Sem o uso <strong>de</strong>ste conceito po<strong>de</strong>mos afirmar que a evolução<br />

apresentada pela computação teria sido mais lenta.<br />

Segundo o dicionário Michaelis, temos a seguinte <strong>de</strong>finição para a<br />

palavra Abstração:<br />

1. O ato ou efeito <strong>de</strong> abstrair. Consi<strong>de</strong>ração das qualida<strong>de</strong>s<br />

in<strong>de</strong>pen<strong>de</strong>ntemente dos objetos a que pertencem. Abstrair: Consi<strong>de</strong>rar<br />

um dos caracteres <strong>de</strong> um objeto separadamente; 2. Excluir, prescindir<br />

<strong>de</strong>.<br />

A finalida<strong>de</strong> principal <strong>de</strong> uso <strong>de</strong> abstração em qualquer área do<br />

conhecimento é colocarmos foco em um sub-conjunto dos aspectos<br />

<strong>de</strong> um sistema ou processo complexo. Consi<strong>de</strong>re, por exemplo, o<br />

processo <strong>de</strong> pintar um quadro. A Figura 2 mostra, à esquerda um<br />

esquema inicial mostrando as linhas principais do quadro que se<br />

encontra do lado direito (A Virgem, o Menino e Sant’Ana, Leonardo<br />

Da Vinci).<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 2: Abstração na Pintura.<br />

Observe que, no <strong>de</strong>senho, as proporções, os <strong>de</strong>talhes das posturas e<br />

feições são já <strong>de</strong>terminados. Esse processo ajuda o pintor, pois, no<br />

momento da i<strong>de</strong>alização do quadro, ele não precisa se preocupar com<br />

outros aspectos complexos, como cores, nuances <strong>de</strong> sombras e<br />

reflexos, para <strong>de</strong>finir a estrutura e a sua aparência geral. Po<strong>de</strong>mos<br />

dizer então que o <strong>de</strong>senho à esquerda é uma abstração do quadro.<br />

Em computação, abstração também possui finalida<strong>de</strong>s semelhantes.<br />

Em programação, especificamente, esse conceito está presente quase o<br />

tempo todo, seja nas construções existentes nas linguagens, seja nos<br />

métodos <strong>de</strong> programação empregados.<br />

Uma das principais abstrações das linguagens <strong>de</strong> programação é o<br />

conceito <strong>de</strong> variável. Uma variável é um conceito abstrato que<br />

escon<strong>de</strong> diversos aspectos técnicos que não interessam ao<br />

programador. Quando <strong>de</strong>claramos uma variável em um programa, essa<br />

<strong>de</strong>claração implica em “coisas” que não irão interferir no seu<br />

<strong>de</strong>senvolvimento. Por exemplo, por trás <strong>de</strong> uma variável do tipo<br />

inteiro em C, (ex. int x;), estão “escondidas” as características físicas<br />

do armazenamento da variável em memória, a saber:<br />

- A forma <strong>de</strong> representação <strong>de</strong> números inteiros usando base 2<br />

(por exemplo, complemento <strong>de</strong> 2);<br />

- O padrão usado pelo hardware (ex. little endian, big endian);<br />

- O número <strong>de</strong> bytes que uma variável do tipo inteiro ocupa;<br />

- O en<strong>de</strong>reço da variável na memória principal;<br />

- O conjunto <strong>de</strong> bits responsável por armazenar o sinal do<br />

número inteiro.<br />

Para o programador em C, geralmente, nenhuma <strong>de</strong>ssas<br />

informações é importante. O que o programador <strong>de</strong>seja é utilizar a<br />

variável realizando as operações permitidas aos números inteiros<br />

(operações aritméticas e comparações), atribuir, recuperar e<br />

modificar o valor contido na variável.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 13


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 14<br />

Assim po<strong>de</strong>mos dizer que uma variável permite ao programador<br />

abstrair-se <strong>de</strong> <strong>de</strong>talhes que não interessam e não irão influenciar no<br />

comportamento do programa.<br />

Ativida<strong>de</strong>s<br />

Os itens 1,2 e 3 são abstrações. Para cada um <strong>de</strong>les<br />

<strong>de</strong>screva os aspectos que estão sendo escondidos e<br />

os aspectos que realmente importam ao<br />

programador:<br />

1. O comando<br />

2. Um arquivo<br />

3. A função scanf<br />

1.2.2 Abstração <strong>de</strong> Procedimentos<br />

Po<strong>de</strong>mos afirmar que a gran<strong>de</strong> maioria dos elementos <strong>de</strong> linguagem<br />

utilizados em uma linguagem <strong>de</strong> programação <strong>de</strong> alto nível são<br />

abstrações. Essas abstrações facilitaram o <strong>de</strong>senvolvimento <strong>de</strong><br />

programas mais complexos e sofisticados, evitando que<br />

programadores precisassem manipular bits e en<strong>de</strong>reços <strong>de</strong> memória e<br />

interagir diretamente com o sistema operacional.<br />

Uma outra abstração <strong>de</strong> programação importante é a Abstração <strong>de</strong><br />

Procedimento.<br />

Abstração <strong>de</strong> Procedimento<br />

A abstração <strong>de</strong> procedimento permite que o programador<br />

crie, ele mesmo, sua “forma <strong>de</strong> abstrair”, utilizando<br />

os comandos disponíveis na linguagem. A possibilida<strong>de</strong><br />

<strong>de</strong> criar procedimentos permite ao programador criar<br />

funcionalida<strong>de</strong>s mais abstratas que “escon<strong>de</strong>m” a<br />

sequencia <strong>de</strong> operações necessária para a realização <strong>de</strong><br />

uma tarefa complexa.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Por exemplo, sabemos que na linguagem C não existe um comando<br />

que seja capaz <strong>de</strong> or<strong>de</strong>nar um vetor <strong>de</strong> inteiros em or<strong>de</strong>m crescente.<br />

Seria muito bom que pudéssemos contar com esse comando, certo?<br />

Mas, como não temos esse comando, iremos criar uma função<br />

(abstração <strong>de</strong> procedimento) que realize essa tarefa para nós. A função<br />

or<strong>de</strong>na na Figura 3 é essa abstração.<br />

Figura 3: Função or<strong>de</strong>na: Cria uma abstração do procedimento <strong>de</strong> or<strong>de</strong>nação.<br />

Após a implementação da função or<strong>de</strong>na, quando o programador<br />

precisar or<strong>de</strong>nar um vetor, basta que ele invoque a função, passando<br />

os parâmetros necessários. Ou seja, ao usar o procedimento, o<br />

programador irá se preocupar apenas com o que a função faz e não<br />

mais com os <strong>de</strong>talhes <strong>de</strong> sua implementação.<br />

Uma abstração <strong>de</strong> procedimento <strong>de</strong>ve realizar uma tarefa (por<br />

exemplo, or<strong>de</strong>nar um vetor <strong>de</strong> inteiros) que <strong>de</strong>ve ser in<strong>de</strong>pen<strong>de</strong>nte da<br />

forma como este vai ser implementado. O programador <strong>de</strong>ve antes <strong>de</strong><br />

tudo ver o procedimento como uma caixa preta, cuja especificação<br />

<strong>de</strong>ve conter três elementos básicos (Figura 4):<br />

Entrada: O conjunto <strong>de</strong> dados <strong>de</strong> entrada necessário;<br />

Saída: O conjunto <strong>de</strong> dados produzido pelo procedimento;<br />

Função: A <strong>de</strong>scrição do que o procedimento faz.<br />

ENTRADA SAÍDA<br />

FUNÇÃO<br />

Figura 4: Os elementos consi<strong>de</strong>rados na <strong>de</strong>finição <strong>de</strong> um procedimento.<br />

Na especificação do procedimento, o programador não <strong>de</strong>ve estar<br />

preocupado com a implementação, mas sim com o comportamento<br />

(função) do mesmo. Qualquer implementação que realize a função<br />

especificada irá servir como solução. Para o caso da or<strong>de</strong>nação,<br />

veremos adiante neste curso que existem diferentes métodos <strong>de</strong><br />

or<strong>de</strong>nar um vetor. O método utilizado na função or<strong>de</strong>na se chama<br />

or<strong>de</strong>nação por inserção. A implementação interna po<strong>de</strong>ria ser<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 15


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 16<br />

substituída por qualquer outro método <strong>de</strong> or<strong>de</strong>nação. A própria<br />

linguagem C oferece em sua biblioteca padrão stdlib, uma função<br />

chamada qsort, que po<strong>de</strong> ser usada para or<strong>de</strong>nar vetores. Essa<br />

função utiliza um outro método <strong>de</strong> or<strong>de</strong>nação muito conhecido e<br />

também muito rápido, chamado <strong>de</strong> Quick Sort.<br />

Ativida<strong>de</strong>s<br />

1. Suponha que você tenha disponíveis as<br />

seguintes funções em C:<br />

Int CalculaMedia(int *notas);<br />

Void<br />

DeterminaMaiorEMenorNotas(int<br />

*v, int * maior, int *menor);<br />

Void leNotas(int *notas);<br />

Void MostraResultados(int<br />

media,int<br />

menorNota);<br />

maiorNota, int<br />

Utilizando essas funções, <strong>de</strong>senvolva um programa em<br />

C que leia as notas <strong>de</strong> 5 turmas e para cada uma <strong>de</strong>las,<br />

imprima a média, a maior e a menor nota.<br />

1.2.3 Tipos Abstratos <strong>de</strong> Dados<br />

A abstração <strong>de</strong> dados visa aos mesmos objetivos que a abstração <strong>de</strong><br />

procedimentos, mas com relação às estruturas <strong>de</strong> dados utilizadas nos<br />

programas. A abstração <strong>de</strong> dados visa criar novos tipos <strong>de</strong> dados<br />

<strong>de</strong>finidos em temos <strong>de</strong> seu comportamento. Esses novos tipos são<br />

chamados <strong>de</strong> Tipos Abstratos <strong>de</strong> Dados - TAD.<br />

É muito importante que você perceba que um tipo abstrato <strong>de</strong> dados<br />

não se resume a uma nova estrutura <strong>de</strong> dados. Vamos então discutir<br />

um pouco sobre essa diferença.<br />

Primeiramente, uma estrutura <strong>de</strong> dados po<strong>de</strong> ser <strong>de</strong>finida<br />

simplesmente como uma variável capaz <strong>de</strong> armazenar mais <strong>de</strong> um<br />

valor simultaneamente. Assim, um vetor, uma matriz ou um registro<br />

(struct em C) são exemplos <strong>de</strong> estruturas <strong>de</strong> dados. A combinação<br />

<strong>de</strong>sses elementos em estruturas mais complexas dá origem a outras<br />

estruturas <strong>de</strong> dados. A Figura 5 apresenta as <strong>de</strong>clarações <strong>de</strong> struct<br />

ponto e struct reta, como exemplos <strong>de</strong> tipos <strong>de</strong> estruturas<br />

<strong>de</strong> dado.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 5: Estruturas <strong>de</strong> Dados ponto e reta.<br />

Ao <strong>de</strong>finir a struct ponto, passamos a contar com mais uma<br />

alternativa para <strong>de</strong>finição <strong>de</strong> tipos <strong>de</strong> variáveis e, conseqüentemente,<br />

para a composição <strong>de</strong> novos tipos <strong>de</strong> estruturas. Assim a struct<br />

reta foi <strong>de</strong>finida como uma composição <strong>de</strong> duas variáveis do tipo<br />

struct ponto. Essas estruturas po<strong>de</strong>m ser usadas para <strong>de</strong>clarar<br />

tanto variáveis como argumentos <strong>de</strong> funções.<br />

Em C temos ainda a cláusula type<strong>de</strong>f, que permite <strong>de</strong>finir o nome<br />

do novo tipo. Nesse caso, não precisamos usar a palavra reservada<br />

struct na <strong>de</strong>claração <strong>de</strong> variáveis do tipo <strong>de</strong>finido. Na Figura 6<br />

temos o uso <strong>de</strong> type<strong>de</strong>f. Veja que na <strong>de</strong>finição do tipo Reta, o<br />

nome Ponto é usado sem a palavra reservada struct.<br />

Figura 6: Definição dos tipos Reta e Ponto.<br />

Estrutura <strong>de</strong> Dados versus Tipo Abstrato <strong>de</strong> Dados<br />

A <strong>de</strong>finição <strong>de</strong> uma estrutura <strong>de</strong> dados se preocupa em<br />

<strong>de</strong>monstrar como o objeto é representado na memória<br />

<strong>de</strong> um computador (representação). Nessa <strong>de</strong>finição são<br />

consi<strong>de</strong>rados aspectos do tipo: quais as informações que<br />

serão armazenadas ali e qual a quantida<strong>de</strong> <strong>de</strong>stas<br />

informações. A <strong>de</strong>finição <strong>de</strong> um Tipo Abstrato <strong>de</strong><br />

Dados segue uma abordagem diferente. Essa <strong>de</strong>finição é<br />

feita em termos das operações que po<strong>de</strong>m ser realizadas<br />

sobre o tipo.<br />

A <strong>de</strong>finição <strong>de</strong> um Tipo Abstrato <strong>de</strong> Dado (TAD) é<br />

chamada <strong>de</strong> especificação e consiste, basicamente, em<br />

<strong>de</strong>finir as operações relacionadas ao tipo. Dizemos que<br />

essas operações <strong>de</strong>finem o comportamento do TAD.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 17


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 18<br />

Vamos aplicar o conceito <strong>de</strong> TAD consi<strong>de</strong>rando os objetos Reta e<br />

Ponto. Um passo importante na <strong>de</strong>finição do TAD, que já ajuda na<br />

programação <strong>de</strong> uma maneira geral, é que não conseguimos <strong>de</strong>fini-lo<br />

sem conhecermos a sua finalida<strong>de</strong> e o contexto no qual será usado.<br />

Em nosso exemplo precisamos saber para quê nós queremos um<br />

ponto e uma reta. Geralmente essa informação é conseguida a partir<br />

do enunciado do problema. Assim, vamos supor a seguinte <strong>de</strong>scrição<br />

para o nosso problema envolvendo retas e pontos:<br />

Problema A. É necessário um programa <strong>de</strong> computador que realize<br />

operações geométricas envolvendo pontos e retas localizadas no<br />

plano cartesiano. O programa <strong>de</strong>ve permitir: calcular a distância do<br />

ponto até a origem do plano cartesiano; calcular a distância entre<br />

dois pontos; dada a representação da reta por dois pontos, calcular o<br />

ângulo <strong>de</strong> inclinação da reta, fornecer os parâmetros a e b<br />

correspon<strong>de</strong>ntes a equação da reta ax + b. Determinar a distância <strong>de</strong><br />

uma reta a um ponto. Evi<strong>de</strong>ntemente, o programa <strong>de</strong>ve permitir a<br />

leitura e impressão <strong>de</strong> pontos e retas conforme a necessida<strong>de</strong> das<br />

operações.<br />

Com base na <strong>de</strong>scrição acima po<strong>de</strong>mos i<strong>de</strong>ntificar a necessida<strong>de</strong> do<br />

TAD ponto e do TAD Reta, bem como suas operações. Essas<br />

operações aparecem sublinhadas no texto do problema e são<br />

apresentadas nas Figuras 7 e 8.<br />

Figura 7: operações do TAD Ponto para o problema A.<br />

Figura 8: operações do TAD Reta para o problema A.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Agora, consi<strong>de</strong>re que estejamos <strong>de</strong>senvolvendo um programa para o<br />

problema abaixo:<br />

Problema B. É necessário um programa <strong>de</strong> computador para<br />

apresentar graficamente figuras geométricas formadas por pontos e<br />

retas, usando o monitor do computador como plano. O programa<br />

<strong>de</strong>ve permitir: ler um ponto, plotar um ponto na tela, ligar dois pontos<br />

por um segmento <strong>de</strong> reta; dada uma reta passando por esse ponto,<br />

<strong>de</strong>slocar um outro ponto com base na equação <strong>de</strong>ssa reta; dada uma<br />

reta representada por dois pontos, plotar esta reta no monitor; dada<br />

uma reta e um valor d, criar uma reta paralela à reta dada a uma<br />

distancia d da reta.<br />

As operações necessárias aos TAD Ponto e Reta neste problema são<br />

apresentadas nas Figuras 9 e 10.<br />

Figura 9: operações do TAD Ponto para o problema B.<br />

Figura 10: operações do TAD Reta para o problema B.<br />

Note que nos dois problemas foram <strong>de</strong>finidos os tipos<br />

Ponto e Reta. No entanto, a abstração necessária difere<br />

<strong>de</strong> um problema para o outro, interferindo na <strong>de</strong>finição<br />

das operações. Embora existam essas diferenças, iremos<br />

sempre tentar criar abstrações mais genéricas o quanto<br />

for possível. Quanto mais genérico um TAD, maior o<br />

número <strong>de</strong> problemas em que esse po<strong>de</strong>rá ser aplicado.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 19


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 20<br />

Embora as operações não informem nada a respeito da<br />

representação interna <strong>de</strong> um TAD, elas fornecem ao<br />

programador tudo o que ele precisa para manipular o<br />

TAD. As operações <strong>de</strong> um TAD especificam a sua<br />

interface com o restante do programa. Isso significa que<br />

qualquer ação relacionada ao TAD <strong>de</strong>ve ser feita<br />

mediante uma <strong>de</strong> suas operações.<br />

Temos como resultado prático que o programador, ao<br />

usar o TAD, não vai precisar se preocupar com sua implementação<br />

interna. Iremos agora analisar esse aspecto<br />

consi<strong>de</strong>rando os TAD Ponto e Reta e o problema A.<br />

Na implementação, ponto e reta foram <strong>de</strong>finidos e implementados,<br />

originando dois módulos in<strong>de</strong>pen<strong>de</strong>ntes: o módulo Ponto e o módulo<br />

Reta. Cada módulo em C é normalmente implementado por dois<br />

arquivos: o arquivo .h e o arquivo .c. No arquivo .c teremos a<br />

implementação das operações do TAD e no arquivo .h teremos a<br />

especificação da interface do TAD. A Figura 11 e a Figura 12<br />

apresentam as interfaces dos módulos Ponto e Reta, respectivamente.<br />

Figura 11: Interface do TAD Ponto.<br />

Figura 12: Interface do TAD Reta.<br />

Com a implementação dos TADs Ponto e Reta concluídas e<br />

<strong>de</strong>vidamente testadas, qualquer outro módulo do programa po<strong>de</strong>rá<br />

utilizar esses tipos por meio das operações <strong>de</strong>finidas para os mesmos.<br />

Quando um módulo A utiliza um módulo B em programação,<br />

dizemos que o módulo A é cliente do módulo B. Essa relação <strong>de</strong><br />

“clientela” entre os módulos (ou TADs) <strong>de</strong> um programa po<strong>de</strong> ser<br />

representada graficamente por meio <strong>de</strong> um diagrama <strong>de</strong> estrutura<br />

modular - DEM.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Diagrama <strong>de</strong> Estrutura Modular<br />

Um diagrama <strong>de</strong> estrutura modular é formado por<br />

retângulos e linhas direcionadas relacionando os<br />

retângulos. Cada retângulo representa um módulo. As<br />

linhas direcionadas significam “cliente <strong>de</strong>” e indicam o<br />

acionamento <strong>de</strong> operações contidas no módulo apontado<br />

pelo módulo cliente. Esses digramas também são<br />

chamados diagramas hierárquicos, pois apresentam a<br />

hierarquia dos módulos, iniciando por um módulo que<br />

inicia o programa e acionam os <strong>de</strong>mais módulos.<br />

Como exemplo, suponha que tenhamos também, junto aos módulos<br />

Ponto e Reta, um módulo chamado principal. Esse módulo é cliente<br />

dos módulos Ponto e Reta. A Figura 13 a seguir ilustra o DEM <strong>de</strong>ste<br />

programa. O módulo que inicia o programa é o módulo principal (e<br />

<strong>de</strong>ve conter uma função main()). Ele aciona as operações tanto do<br />

módulo ponto quanto do módulo reta. Reta, por sua vez, também é<br />

cliente do módulo ponto.<br />

Figura 13: DEM com módulos Ponto, Reta e Principal.<br />

A Figura 14 ilustra a implementação <strong>de</strong> um módulo Principal. Note<br />

que a única forma <strong>de</strong> acesso aos TADs Ponto e Reta é por meio das<br />

operações <strong>de</strong>finidas em suas respectivas interfaces.<br />

Use diagramas <strong>de</strong> estrutura modular sempre que for<br />

iniciar um novo projeto. Defina os TADs e estabeleça o<br />

relacionamento entre eles por meio <strong>de</strong> DEMs. Junto às<br />

linhas, você po<strong>de</strong> especificar as operações do TAD que<br />

são acionadas pelo cliente. Isso vai ajudar você na<br />

especificação dos TADs.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 21


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 22<br />

Ativida<strong>de</strong>s<br />

Implementar as operações do TadPonto e do TadReta<br />

consi<strong>de</strong>rando o enunciado do problema 1, da Seção 1.2.3<br />

do texto, e <strong>de</strong>senvolver um programa usando a função<br />

principal (do quadro a seguir) <strong>de</strong> forma a testar as<br />

operações.<br />

Figura 14: Módulo Principal para o Problema A.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Até o momento, em nosso exemplo envolvendo Ponto e<br />

Reta, não abordamos a questão <strong>de</strong> como as operações<br />

serão implementadas propositalmente. É que até a<br />

parte que apresentamos do <strong>de</strong>senvolvimento não<br />

precisamos saber mesmo. Você por acaso lembra como<br />

se calcula a distância entre dois pontos? E a distância<br />

entre uma reta e um ponto? Pois bem, o importante é<br />

que você tenha compreendido a discussão feita e o<br />

exemplo dado, mesmo sem saber respon<strong>de</strong>r essas duas<br />

perguntas. Assim espero!<br />

1.2.4 Implementação <strong>de</strong> Tipos Abstratos <strong>de</strong> Dados<br />

Um dos principais benefícios da abstração <strong>de</strong> dados é<br />

separar o comportamento do TAD, especificado por<br />

meio da <strong>de</strong>finição <strong>de</strong> suas operações, da sua<br />

implementação. Em nosso exemplo, o que <strong>de</strong>finimos a<br />

respeito dos TAD Ponto e Reta foi o seu<br />

comportamento, as suas operações. Nesta seção,<br />

discutiremos melhor como separar em um projeto a<br />

<strong>de</strong>finição do comportamento e da implementação <strong>de</strong> um<br />

TAD.<br />

O projeto completo <strong>de</strong> um TAD consiste <strong>de</strong> dois passos:<br />

1. Especificação - Consiste na especificação do comportamento<br />

do TAD;<br />

2. Implementação – Implementação das operações e estruturas<br />

<strong>de</strong> dados.<br />

Especificação<br />

A especificação <strong>de</strong> um TAD <strong>de</strong>screve o que TAD faz, mas omite<br />

informações sobre como o mesmo é implementado. Por omitir<br />

<strong>de</strong>talhes <strong>de</strong> implementação, uma única especificação permite muitas<br />

implementações diferentes.<br />

A especificação é feita por meio da <strong>de</strong>finição das operações do TAD.<br />

Para <strong>de</strong>talhar melhor cada uma <strong>de</strong>stas operações, <strong>de</strong>vemos estabelecer,<br />

para cada operação, dois elementos:<br />

- Pré-condições: <strong>de</strong>finem as condições necessárias para que a<br />

operação possa ser realizada. Por exemplo, suponha que<br />

<strong>de</strong>sejamos especificar o TAD Conjunto com a operação<br />

listarConjunto. Uma pré-condição para essa operação é<br />

que o Conjunto não esteja vazio.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 23


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 24<br />

- Pós-condições: <strong>de</strong>finem o estado do TAD após a execução da<br />

operação. Por exemplo, suponha a operação<br />

inserirElemento no TAD conjunto. Uma pós-condição<br />

para essa operação seria: elementos no conjunto = elementos<br />

no conjunto + novo elemento. A Figura 15 ilustra a <strong>de</strong>finição<br />

do TAD conjunto.<br />

Figura 15: Especificação do TAD Conjunto.<br />

Implementação<br />

Um TAD é implementado por um módulo <strong>de</strong> um programa. Uma<br />

única especificação permite diferentes implementações para um<br />

mesmo TAD. Uma implementação está correta se esta provê o<br />

comportamento especificado para o TAD. Implementações corretas<br />

po<strong>de</strong>m diferir uma da outra, por exemplo, em termos do algoritmo ou<br />

da estrutura <strong>de</strong> dados que elas usam. Essas diferenças interferem na<br />

eficiência (<strong>de</strong>sempenho em tempo <strong>de</strong> execução, ou ocupação <strong>de</strong><br />

espaço) apresentado pelo TAD para realização das operações.<br />

Encapsulamento<br />

Para uma abstração funcionar corretamente, a sua<br />

implementação <strong>de</strong>ve ser encapsulada. Se a<br />

implementação for encapsulada, nenhum outro módulo<br />

do programa vai <strong>de</strong>pen<strong>de</strong>r <strong>de</strong> <strong>de</strong>talhes <strong>de</strong><br />

implementação do TAD. Encapsulamento garante que<br />

módulos do programa po<strong>de</strong>m ser implementados e reimplementados<br />

in<strong>de</strong>pen<strong>de</strong>ntemente, sem afetar os outros<br />

módulos do programa.<br />

O encapsulamento geralmente é conseguido por meio da separação da<br />

interface e da implementação do módulo. Conforme já vimos<br />

anteriormente, em C a implementação <strong>de</strong> um TAD por meio <strong>de</strong> um<br />

módulo consiste em duas partes: a especificação da interface do<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


módulo por meio do arquivo hea<strong>de</strong>r (com extensão.h) e da implementação<br />

das operações por meio <strong>de</strong> um arquivo com extensão .c.<br />

1.2.5 Avaliação <strong>de</strong> Implementações <strong>de</strong> Tipos Abstratos <strong>de</strong><br />

Dados<br />

É fundamental para um programador saber criticar e<br />

avaliar a qualida<strong>de</strong> <strong>de</strong> uma implementação! Uma<br />

implementação baseada em Abstração <strong>de</strong> Dados é um<br />

indicativo <strong>de</strong> boa qualida<strong>de</strong>. Nesta Seção, discutiremos<br />

elementos que permitem que você verifique se uma<br />

implementação realmente está <strong>de</strong> acordo com essa<br />

técnica.<br />

Embora a linguagem C não ofereça um mecanismo que impeça<br />

realmente que o programador tenha acesso à estrutura interna do TAD,<br />

esta é uma regra fundamental e <strong>de</strong>ve ser respeitada pelo programador.<br />

Esta regra ou característica <strong>de</strong> TADs é o encapsulamento, discutido na<br />

seção anterior. Dizemos que um TAD é encapsulado por suas<br />

operações no sentido <strong>de</strong> que a estrutura interna do TAD fica<br />

preservada e invisível ao restante do programa. Violar essa regra<br />

significa não usar corretamente a Abstração <strong>de</strong> Dados.<br />

Localida<strong>de</strong><br />

O maior benefício do encapsulamento chama-se<br />

princípio da Localida<strong>de</strong>. Esse princípio permite que um<br />

programa seja implementado, entendido e modificado<br />

um módulo <strong>de</strong> cada vez. A localida<strong>de</strong> aumenta a<br />

qualida<strong>de</strong> do software que está sendo <strong>de</strong>senvolvido.<br />

Dentre os benefícios oriundos do princípio da localida<strong>de</strong> temos:<br />

1. O programador <strong>de</strong> uma abstração sabe o que é necessário pelo<br />

que está <strong>de</strong>scrito na especificação. Dessa forma, ele não<br />

precisa interagir com programadores <strong>de</strong> outros módulos (ou,<br />

pelo menos, essa interação vai ser bem limitada).<br />

2. De forma análoga, o programador <strong>de</strong> um módulo que usa a<br />

abstração sabe o que esperar <strong>de</strong>sta abstração, apenas pelo<br />

comportamento <strong>de</strong>scrito em sua especificação.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 25


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 26<br />

Uma ferramenta que po<strong>de</strong> contribuir para esse<br />

entendimento é a documentação do TAD. Ou seja, a<br />

explicação sobre o seu funcionamento e sobre como<br />

utilizá-lo. Procure sempre fazer uma boa documentação<br />

do TAD. Essa documentação po<strong>de</strong> vir como um<br />

documento à parte que acompanha o módulo.<br />

3. É necessário apenas o raciocínio local (por módulo), para saber<br />

o que o programa faz e se está fazendo a coisa certa. Para<br />

estudar e compreen<strong>de</strong>r o programa po<strong>de</strong>mos dividi-lo em<br />

módulos, e analisar um módulo <strong>de</strong> cada vez. Em cada caso,<br />

preocupamo-nos em saber se o módulo faz o que é suposto que<br />

faça. Ou seja, se ele cumpre o que está na especificação.<br />

Po<strong>de</strong>-se assim limitar a atenção para um módulo, ignorando<br />

tanto os módulos usados por este quanto os que o utilizam. Os<br />

módulos que utilizam o módulo estudado po<strong>de</strong>m ser ignorados<br />

porque <strong>de</strong>pen<strong>de</strong>m apenas <strong>de</strong> sua especificação e não da sua<br />

implementação. Os módulos utilizados são ignorados,<br />

raciocinando-se sobre o que eles fazem utilizando apenas sua<br />

especificação em vez <strong>de</strong> sua codificação. Com isso, tem-se<br />

uma gran<strong>de</strong> economia <strong>de</strong> esforço, dado que as especificações<br />

são muito menores que as implementações. Observando-se<br />

apenas as especificações evita-se também um efeito cascata.<br />

Por exemplo, se tivermos que olhar o código do módulo que<br />

utilizamos, teremos que olhar também o código dos módulos<br />

que são utilizados pelos módulos que utilizamos e assim por<br />

diante.<br />

4. Finalmente, a modificação do programa po<strong>de</strong> ser feita módulo<br />

por módulo. Se uma abstração particular necessita ser<br />

reimplementada para prover um melhor <strong>de</strong>sempenho, corrigir<br />

um erro ou prover uma nova funcionalida<strong>de</strong>, o módulo<br />

implementado anteriormente po<strong>de</strong> ser trocado por uma nova<br />

implementação sem afetar os outros módulos.<br />

Prototipagem<br />

Localida<strong>de</strong> também provê uma base firme para a<br />

prototipação ou prototipagem. Um protótipo é uma<br />

implementação inicial, rudimentar e incompleta <strong>de</strong><br />

um programa a ser <strong>de</strong>senvolvido. Se mantivermos o<br />

princípio da localida<strong>de</strong> no <strong>de</strong>senvolvimento do<br />

protótipo, essa implementação inicial po<strong>de</strong> ir sendo<br />

completada ou substituída por implementações<br />

melhores sem gran<strong>de</strong> esforço nem re-trabalho.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Localida<strong>de</strong> também provê suporte para evolução. Abstrações<br />

po<strong>de</strong>m ser utilizadas nesse caso para encapsular modificações<br />

potenciais no programa. Por exemplo, suponha que <strong>de</strong>sejamos um<br />

programa para ser executado em diferentes máquinas. Po<strong>de</strong>mos<br />

tratar esse problema inventando abstrações que escondam as<br />

diferenças entre as máquinas <strong>de</strong> forma que, para mover o<br />

programa para uma máquina diferente, apenas essas abstrações<br />

precisem ser reimplementadas. Um bom princípio <strong>de</strong> projeto é<br />

pensar sobre modificações esperadas e organizar o<br />

<strong>de</strong>senvolvimento utilizando abstrações que encapsulem as<br />

mudanças.<br />

Domínio da complexida<strong>de</strong><br />

Os benefícios da localida<strong>de</strong> são particularmente<br />

importantes para a abstração <strong>de</strong> dados. Estruturas <strong>de</strong><br />

dados são muitas vezes complicadas e a visão mais<br />

abstrata mais simples provida pela especificação torna o<br />

resto do programa mais simples. Ainda, mudanças nas<br />

estruturas <strong>de</strong> armazenamento são uma das principais<br />

formas <strong>de</strong> evolução <strong>de</strong> programas. Portanto, os efeitos<br />

<strong>de</strong>ssas mudanças <strong>de</strong>vem ser minimizados encapsulando<br />

essas estruturas <strong>de</strong> dados em abstrações <strong>de</strong> dados.<br />

Se avaliarmos um programa segundo o critério da abstração <strong>de</strong><br />

dados, <strong>de</strong>vemos observar os seguintes fatores:<br />

1. Os TADs estão realmente encapsulados?<br />

a. O entendimento <strong>de</strong> cada módulo do programa<br />

in<strong>de</strong>pen<strong>de</strong> dos <strong>de</strong>mais módulos?<br />

b. O efeito cascata é evitado na <strong>de</strong>puração?<br />

c. É possível reimplementar um módulo sem afetar os<br />

outros?<br />

2. Potenciais mudanças foram previstas no projeto do TAD?<br />

3. Os TADs oferecem abstrações suficientemente simples das<br />

estruturas <strong>de</strong> dados que encapsulam?<br />

Ativida<strong>de</strong>s<br />

Os códigos a seguir especificam as interfaces <strong>de</strong> dois<br />

módulos: Aluno e Turma.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 27


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 28<br />

/******** aluno.h ***********/<br />

type<strong>de</strong>f struct aluno{<br />

char nome[30];<br />

int matricula;<br />

int cdgcurso;<br />

} Aluno;<br />

void leAluno(Aluno *);<br />

void imprimeAluno(Aluno);<br />

void AlteraAluno(Aluno *);<br />

/******** turma.h **********/<br />

#inclu<strong>de</strong> "aluno.h"<br />

#<strong>de</strong>fine MAXTURMA 100<br />

type<strong>de</strong>f struct turma {<br />

int nalunos;<br />

Aluno alunos[MAXTURMA];<br />

}Turma;<br />

void insereAluno(Turma *,Aluno); /*insere o<br />

aluno passado como paramentro na turma */<br />

void localizaAluno(Turma *, char *); /*<br />

localiza um aluno na turma pelo nome */<br />

void imprimeTurma(Turma);<br />

void atualizaAlunoDaTurma(Turma *, char *);<br />

Agora, suponha que tenhamos o seguinte módulo principal,<br />

utilizando esses módulos:<br />

/************ Modulo Principal<br />

**************/<br />

#inclu<strong>de</strong> "turma.h"<br />

Turma turma1;<br />

void principal(){<br />

int opcao,i;<br />

aluno a;<br />

char nome[30];<br />

do{<br />

scanf("%d",&opcao);<br />

switch(opcao){<br />

case 1: /* cadastrar aluno */<br />

lealuno(&a);<br />

insereAluno(&turma1,a);<br />

break;<br />

case 2:<br />

scanf("%s",nome);<br />

a= localizaAluno(&turma1, nome);<br />

printf("%s - %d - %d",a.nome,<br />

a.matricula,a.cdgcurso);<br />

break;<br />

case 3: /* imprimir turma */<br />

for (i=0;i


nome);<br />

}<br />

}<br />

scanf("%s",nome);<br />

atualizaAlunoDaTurma(&turma1,<br />

break;<br />

Tarefas:<br />

a) Critique a implementação do módulo acima com<br />

base nos critérios <strong>de</strong> avaliação <strong>de</strong> TADs discutidos<br />

acima.<br />

b) Faça uma implementação da operação<br />

atualizaAlunoDaTurma, respeitando os princípios <strong>de</strong><br />

programação baseada em tipos abstratos <strong>de</strong> dados.<br />

Dijkstra (1930-2002) foi, sem dúvida, um dos<br />

cientistas que mais contribuíram para o<br />

<strong>de</strong>senvolvimento da <strong>Programação</strong> <strong>de</strong> Computadores.<br />

Terminamos este capítulo com uma frase <strong>de</strong>le, que<br />

resume o conceito <strong>de</strong> abstração:<br />

“O propósito da abstração não é ser vaga, mas sim,<br />

criar um novo nível semântico no qual ela possa ser<br />

absolutamente precisa”.<br />

Edsger. W. Dijkstra, The Humble Programmer (O<br />

programador Mediocre), Communications of the<br />

ACM, 15(10), 1972.<br />

Reflita sobre isso!<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 29


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 30<br />

2. TIPOS ABSTRATOS DE DADOS<br />

FUNDAMENTAIS.<br />

Olá! Após o estudo e a compreensão dos conceitos<br />

que <strong>de</strong>finem a técnica <strong>de</strong> Abstração <strong>de</strong><br />

Dados, utilizaremos esses conceitos em nosso estudo<br />

até o final da disciplina. Portanto, se você não<br />

compreen<strong>de</strong>u ou não se sente ainda plenamente<br />

convencido <strong>de</strong> que <strong>de</strong>ve utilzar Abstração <strong>de</strong><br />

Dados em seus programas, recomendo que retorne<br />

ao Capítulo 2. O motivo disso é muito simples.<br />

Daqui para frente estudaremos problemas e soluções<br />

mais complexos, que exigem muito do estudante<br />

em sua capacida<strong>de</strong> <strong>de</strong> abstração. Neste<br />

Capítulo, em particular, iniciamos nosso estudo <strong>de</strong><br />

um conjunto <strong>de</strong> TADs muito comuns e<br />

importantes: Os TAD Pilha e Fila.<br />

No <strong>de</strong>correr da evolução da programação, padrões e práticas comuns<br />

têm sido observadas e transformadas em conceitos, mo<strong>de</strong>los e mecanismos<br />

que po<strong>de</strong>m ser utilizados em mais <strong>de</strong> uma situação diferente.<br />

Dentre esses elementos temos uma coleção <strong>de</strong> tipos abstratos <strong>de</strong> dados<br />

que são comuns a diversas situações e aplicáveis na solução <strong>de</strong> uma<br />

gran<strong>de</strong> quantida<strong>de</strong> <strong>de</strong> problemas. Dois <strong>de</strong>sses TADs são as Pilhas e as<br />

Filas, que estudaremos em profundida<strong>de</strong> neste capítulo.<br />

2.1 PILHAS<br />

No mundo real uma pilha correspon<strong>de</strong> a um agregado <strong>de</strong> objetos que<br />

são acomodados um sobre o outro. A Figura 1 ilustra uma pilha em<br />

que os objetos são caixas.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 1: Uma Pilha <strong>de</strong> Caixas<br />

Os números nas caixas indicam a or<strong>de</strong>m <strong>de</strong> empilhamento. Se<br />

quisermos agora remover as caixas da pilha (<strong>de</strong>sempilhar), temos que<br />

remover a caixa 4 primeiro, <strong>de</strong>pois a 3, a 2 e finalmente a 1. Não<br />

po<strong>de</strong>mos remover as caixas abaixo da que está no topo da pilha sem<br />

antes remover a do topo. Esse comportamento po<strong>de</strong> ser <strong>de</strong>finido pela<br />

estilo: O último a entrar é o primeiro a sair.<br />

No mundo real temos diversas situações em que esse comportamento<br />

<strong>de</strong>ve ser respeitado. Assim, o uso <strong>de</strong> pilhas po<strong>de</strong> facilitar a solução<br />

<strong>de</strong> vários problemas. Um exemplo típico <strong>de</strong> uso <strong>de</strong> pilha é a<br />

operação <strong>de</strong>sfazer, presente na maior parte dos editores <strong>de</strong> texto.<br />

Quando voce executa a operação <strong>de</strong>sfazer, a última operação<br />

realizada é que <strong>de</strong>verá ser <strong>de</strong>sfeita primeiro. Se você executar o<br />

<strong>de</strong>sfazer novamente, a penúltima operação <strong>de</strong>verá ser <strong>de</strong>sfeita, e assim<br />

sucessivamente. Concluindo, precisamos ter sempre a informação <strong>de</strong><br />

qual foi a última operação realizada. Uma pilha é uma forma eficiente<br />

<strong>de</strong> obtermos esa informação. Se tivermos uma pilha em que<br />

empilhamos a operação que foi feita, teremos no topo da pilha sempre<br />

a operação que <strong>de</strong>vemos <strong>de</strong>sfazer.<br />

Realmente, editores texto que possuem a operação <strong>de</strong>sfazer mantém<br />

uma pilha que guarda informações sobre as ações que vão sendo<br />

realizadas durante a edição. Por exemplo, no momento em que este<br />

texto era digitado, as informações passadas via teclado e mouse eram<br />

também empilhadas.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 31


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 32<br />

A aplicação <strong>de</strong> pilhas em editores <strong>de</strong> texto é um<br />

exemplo <strong>de</strong> uma funcionalida<strong>de</strong> muito comum <strong>de</strong> uso <strong>de</strong><br />

pilha. Essa funcionalida<strong>de</strong> está presente em outros<br />

cenários, como em jogos, programas <strong>de</strong> logistica,<br />

robótica e re<strong>de</strong>s <strong>de</strong> computadores. O que esses cenários<br />

têm em comum é a necessida<strong>de</strong> <strong>de</strong> retroce<strong>de</strong>r por um<br />

caminho <strong>de</strong> dados ou ações que tenha realizado. Esse<br />

retocesso se dá também em algoritmos <strong>de</strong> backtracking,<br />

(algo como voltar na trilha) e é muito comum em<br />

algoritmos <strong>de</strong> busca e recuperação <strong>de</strong> informação. Veja<br />

a discussão sobre esses algoritmos em<br />

http://pt.wikipedia.org/wiki/Backtracking.<br />

2.1.1 Especificação do TAD Pilha<br />

O TAD Pilha po<strong>de</strong> ser <strong>de</strong>scrito pela seguinte especificação<br />

apresentada na Figura 2. Nessa especificação são <strong>de</strong>finidas as<br />

operações Empilha, que insere elementos na Pilha e Desempilha, que<br />

remove elementos da Pilha. São <strong>de</strong>finidas também as pré-condições e<br />

pós-condições <strong>de</strong>ssas operações.<br />

Figura 2: especificação do TAD Pilha.<br />

Além das operações empilha e <strong>de</strong>sempilha, temos também a operação<br />

inicializaPilha, que coloca a Pilha em um estado inicial, com a pilha<br />

vazia. A Figura 3 ilustra uma possível implementação do TAD Pilha<br />

limitado a 5 (cinco) posições. Cada posição possui um índice. O topo<br />

mantém o índice do valor que está presente no topo. Nesse exemplo,<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


temos o elemento do topo na posição <strong>de</strong> índice 5, conforme indica a<br />

figura. Se convencionarmos que os elementos foram inseridos na pilha<br />

na or<strong>de</strong>m L I C A F, temos que:<br />

O primeiro elemento empilhado foi o L, seguido do I, C, A e F. Logo,<br />

po<strong>de</strong>mos concluir que nessa implementação <strong>de</strong> pilha a atualização do<br />

topo consiste em incrementar <strong>de</strong> 1 na operação empilha e <strong>de</strong>crementar<br />

<strong>de</strong> 1 na operação <strong>de</strong>sempilha. As pré-condições também po<strong>de</strong>m ser<br />

obtidas da implementação. A operação Pilha Vazia po<strong>de</strong> ser verificada<br />

por meio do teste do valor do topo. Por exemplo, se iniciarmos a pilha<br />

com topo valendo 0, po<strong>de</strong>mos usar o teste topo == 0 para verificar se<br />

pilha está vazia. Já a pré-condição pilha cheia po<strong>de</strong> ser verificada<br />

consi<strong>de</strong>rando o limite da implementação. No nosso exemplo esse<br />

limite é 5. Se o topo == 5, a pilha está cheia. Com isso, fica <strong>de</strong>finida<br />

também a operação inicializaPilha que <strong>de</strong>ve fazer o topo valer 0<br />

(zero).<br />

Figura 3: Implementação <strong>de</strong> uma Pilha.<br />

É importante neste momento notar que existem muitas<br />

implementações possíveis para a especificação do TAD<br />

pilha (conforme discutido no Capitulo 1). Esse exemplo<br />

apresentado é apenas uma das possíveis soluções.<br />

Um outro <strong>de</strong>talhe é que, além das operações empilhar e<br />

<strong>de</strong>sempilhar, as pré-condições <strong>de</strong> pilha Cheia e pilha<br />

Vazia po<strong>de</strong>m também se tornar operações do TAD Pilha<br />

na implementação. Na próxima seção discutiremos<br />

algumas implementações <strong>de</strong> Pilha usando arranjos<br />

estáticos.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 33


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 34<br />

Ativida<strong>de</strong>s<br />

1. Na seqüência a seguir, uma letra significa<br />

empilha e um asterisco significa <strong>de</strong>sempilha.<br />

Determine a seqüência <strong>de</strong> valores retornados<br />

pela operação <strong>de</strong>sempilha quando essa<br />

seqüência <strong>de</strong> operações é realizada sob uma<br />

pilha inicialmente vazia.<br />

E A S * Y * Q U E * * * S T * * * I O * N * * *<br />

2. Suponha que uma seqüência misturada <strong>de</strong><br />

operações empilha e <strong>de</strong>sempilha é realizada.<br />

As operações empilha empilham inteiros <strong>de</strong> 0<br />

até 9 or<strong>de</strong>nadamente. A operação <strong>de</strong>sempilha<br />

<strong>de</strong>sempilha e imprime o valor <strong>de</strong>sempilhado.<br />

Qual das seqüências abaixo não po<strong>de</strong>rá ser<br />

impressa?<br />

(a) 4 3 2 1 0 9 8 7 6 5<br />

(b) 4 6 8 7 5 3 2 9 0 1<br />

(c) 2 5 6 7 4 8 9 3 1 0<br />

(d) 4 3 2 1 0 5 6 7 8 9<br />

2.1.2 Implementação <strong>de</strong> Pilhas em Arranjos<br />

A Figura 3 ilustra por meio <strong>de</strong> um diagrama uma implementação <strong>de</strong><br />

Pilha. Usando arranjos estáticos (vetores) e fazendo algumas<br />

adaptações, po<strong>de</strong>mos criar essa implementação em C. A<br />

implementação, conforme discutido na Seção 1.2.4, será formada <strong>de</strong><br />

duas partes, a interface (arquivo com extensão .h) e a<br />

implementação das operações (arquivo com extensão .c). É<br />

recomendado primeiro a <strong>de</strong>finição do arquivo <strong>de</strong> interface e <strong>de</strong>pois a<br />

implementação das operações. A seguir discutiremos uma possível<br />

implementação do TAD Pilha, utilizando arranjos.<br />

A Figura 4 mostra o arquivo <strong>de</strong> interface tadpilha.h. Foi <strong>de</strong>finido<br />

um novo tipo chamado Pilha, formado por dois elementos<br />

principais: Uma variável inteira topo e o vetor itens, que servirá<br />

<strong>de</strong> contêiner para a pilha. O vetor itens é do tipo Elemento, que é<br />

apenas uma re<strong>de</strong>finição do tipo char. A <strong>de</strong>finição <strong>de</strong>sse tipo<br />

Elemento facilita posteriores modificações no tipo básico da pilha. Se<br />

quisermos, por exemplo, alterar a pilha para que esta empilhe e<br />

<strong>de</strong>sempilhe valores inteiros, basta re<strong>de</strong>finir o tipo Elemento como<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


int. Foram <strong>de</strong>finidas assinaturas (protótipos) das funções<br />

correspon<strong>de</strong>ntes às operações inicializaPilha, empilha,<br />

<strong>de</strong>sempilha e aos testes das pré-condições, pilhaCheia e<br />

pilhaVazia. Observe que estamos seguindo a notação <strong>de</strong><br />

protótipos padrão <strong>de</strong> C. Nessa notação, apenas os tipos dos<br />

argumentos (parâmetros das funções) são i<strong>de</strong>ntificados, sendo os<br />

nomes mesmos <strong>de</strong>sses argumentos apenas na implementação. Note<br />

também que a operação <strong>de</strong>sempilha retorna um Elemento. Isso é<br />

bastante comum, pois normalmente se <strong>de</strong>seja fazer alguma coisa com<br />

o elemento <strong>de</strong>sempilhado.<br />

Figura 4: Interface do TAD Pilha com arranjos estáticos.<br />

Na Figura 5, temos a implementação das operações feitas no arquivo<br />

tadPilha.c. A operação inicializaPilha faz o topo da Pilha<br />

receber 0 (zero). PilhaCheia verifica se topo igual a MAX<br />

(tamanho máximo que a pilha po<strong>de</strong> ter) e PilhaVazia verfica se<br />

topo igual a 0. Na operação empilha temos a seguinte or<strong>de</strong>m: insere<br />

o elemento e <strong>de</strong>pois incrementa o topo. Isso é necessário porque em C<br />

a primeira posição <strong>de</strong> um arranjo é a 0 (zero) e com a Pilha vazia o<br />

topo está em zero. Assim, o topo estará sempre com um índice do<br />

elemento no topo + 1. Para compensarmos isso, na operação<br />

<strong>de</strong>sempilha <strong>de</strong>vemos <strong>de</strong>crementar o topo retornando o elemento<br />

somente após o topo ser <strong>de</strong>crementado.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 35


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 36<br />

Figura 5: Implementação das operações do TAD Pilha.<br />

Ativida<strong>de</strong>s<br />

1. Empilhamento <strong>de</strong>crescente. Uma empilha<strong>de</strong>ira<br />

carrega caixas <strong>de</strong> 7, 5 e 3 toneladas. Há três pilhas<br />

A, B e C. A pilha A é on<strong>de</strong> se encontram todas as<br />

caixas que chegam no <strong>de</strong>pósito. Com um <strong>de</strong>talhe:<br />

caixas maiores não po<strong>de</strong>m ser empilhadas sobre<br />

caixas menores. Elabore uma função<br />

chegaNoDeposito (Caixa* nova,<br />

Pilha* A) que efetue o controle das caixas, <strong>de</strong><br />

forma que, caso uma caixa <strong>de</strong> maior peso do que<br />

uma que já está em A <strong>de</strong>va ser empilhada, então,<br />

todas as caixas que estão em A são movidas para as<br />

pilhas auxiliares B (contendo somente caixas <strong>de</strong> 5<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


toneladas) e C (contendo somente caixas <strong>de</strong> 3<br />

toneladas) até que se possa empilhar a nova caixa.<br />

Depois, todas as caixas são movidas <strong>de</strong> volta para a<br />

pilha A.<br />

2. Implementar o TAD PilhaDupla, usando um<br />

arranjo <strong>de</strong> 100 posições. PilhaDupla <strong>de</strong>ve usar<br />

um único arranjo para implementar as duas pilhas.<br />

Além disso, a ocupação da Pilha <strong>de</strong>ve maximizar o<br />

uso do espaço do arranjo, ou seja, enquanto houver<br />

espaço no arranjo qualquer uma das duas pilhas<br />

po<strong>de</strong> utilizá-lo.<br />

2.2 FILAS<br />

Em nosso cotidiano nos <strong>de</strong>paramos constantemente com filas. Ou<br />

melhor, estamos constantemente entrando e saindo <strong>de</strong> filas! Muitas<br />

pessoas se aborrecem com as filas. Todavia, po<strong>de</strong>mos dizer que elas<br />

são um mal necessário. Por exemplo, imaginem se, para entrarem em<br />

um ônibus em uma cida<strong>de</strong>, as pessoas não fizessem uma fila e<br />

simplesmente se amontoassem na porta. Seria um tumulto, as pessoas<br />

que estivessem no ponto há mais tempo po<strong>de</strong>riam não conseguir entrar<br />

ou ficar sem assento. O mais incrível é que isso acontecia há uns 20<br />

anos e ainda acontece em muitos lugares do Brasil e do mundo!<br />

Figura 6: Fila: O primeiro que chega é o primeiro a ser atendido.<br />

Esse exemplo ilustra a idéia <strong>de</strong> que o emprego <strong>de</strong> filas no dia-a-dia é<br />

fundamental para a organização das ativida<strong>de</strong>s feitas pelas pessoas,<br />

sempre que o número <strong>de</strong> clientes <strong>de</strong> um recurso é maior que a<br />

capacida<strong>de</strong> <strong>de</strong> atendimento <strong>de</strong>sse recurso. No caso do ônibus, o<br />

recurso é a porta <strong>de</strong> entrada, que só aten<strong>de</strong> uma pessoa por vez. Com<br />

mais <strong>de</strong> uma pessoa no ponto esperando para entrar, é necessário que<br />

elas formem uma fila. Um outro exemplo é fila em um banco, cujo<br />

recurso necessário é o caixa. Quando entram mais clientes do que o<br />

número <strong>de</strong> caixas, os clientes <strong>de</strong>vem formar uma fila.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 37


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 38<br />

O comportamento <strong>de</strong> uma fila po<strong>de</strong> ser <strong>de</strong>scrito pelo estilo: O<br />

primeiro que chega é o primeiro a ser atendido. Em uma Fila<br />

organizada e sem priorida<strong>de</strong>s (Figura 6, por exemplo), essa regra é<br />

sempre obe<strong>de</strong>cida.<br />

Na programação aplicamos o conceito <strong>de</strong> fila para tratar casos<br />

semelhantes ao do ônibus e ao do banco. Alguns exemplos <strong>de</strong><br />

aplicação <strong>de</strong> filas são:<br />

Fila <strong>de</strong> espera <strong>de</strong> passagens aéreas – Suponha, por exemplo,<br />

que você esteja <strong>de</strong>senvolvendo um sistema <strong>de</strong> reservas <strong>de</strong><br />

passagens aéreas. Nesse tipo <strong>de</strong> sistema, as pessoas fazem<br />

reservas até o recurso (número <strong>de</strong> assentos no vôo) se esgotar.<br />

Quando isso acontece, se houver mais pessoas que querem<br />

viajar naquele vôo, elas entram em uma fila <strong>de</strong> espera. Se<br />

houver uma <strong>de</strong>sistência das reservas, a primeira pessoa <strong>de</strong>ssa<br />

fila será atendida.<br />

Fila <strong>de</strong> mensagens – Suponha que você esteja <strong>de</strong>senvolvendo<br />

um programa A que envie constantemente mensagens a um<br />

programa B. O programa B precisa receber essas mensagens e<br />

processá-las. Se o número <strong>de</strong> mensagens que o programa A<br />

envia em um tempo x é maior do que a capacida<strong>de</strong> <strong>de</strong><br />

processamento das mensagens no mesmo tempo x pelo<br />

programa B, o programa B <strong>de</strong>verá usar uma fila para guardar<br />

as mensagens até que elas possam ser processadas. Esse é um<br />

caso muito comum na Internet. Quando fazemos a requisição<br />

<strong>de</strong> uma página Web, o navegador (programa A) envia uma<br />

mensagem ao servidor Web (programa B). Esse servidor po<strong>de</strong><br />

receber mais requisições (mensagens) do que a sua capacida<strong>de</strong><br />

<strong>de</strong> processamento. Nesse caso, ele <strong>de</strong>ve enfileirar as<br />

requisições para que elas não sejam perdidas e para que sejam<br />

atendidas na or<strong>de</strong>m <strong>de</strong> chegada.<br />

Teoria das Filas<br />

O estudo das Filas, do ponto <strong>de</strong> vista estatístico e<br />

probabilístico, <strong>de</strong>nomina-se Teoria das Filas e<br />

compreen<strong>de</strong> uma disciplina <strong>de</strong> gran<strong>de</strong> interesse <strong>de</strong><br />

diversas áreas da engenharia, computação e<br />

administração. Essa disciplina permite, <strong>de</strong>ntre outras<br />

coisas, estimar a capacida<strong>de</strong> <strong>de</strong> recursos a serem<br />

construídos (p.exemplo, aeroportos, pontes,<br />

armazéns <strong>de</strong> estocagem), i<strong>de</strong>ntificar gargalos<br />

(pontos <strong>de</strong> atraso) em sistemas e estimar o tempo<br />

para a realização <strong>de</strong> uma tarefa. Em nosso estudo,<br />

nos concentraremos apenas na implementação e<br />

emprego <strong>de</strong> filas em programação, e não na<br />

Teoria das Filas.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


2.2.1 Especificação do TAD FILA<br />

O tipo abstrato <strong>de</strong> dados fila po<strong>de</strong> ser <strong>de</strong>scrito pela especificação da<br />

Figura 7.<br />

Figura 7: Especificação do TAD Fila.<br />

As operações fundamentais do TAD Fila são inserir e remover.<br />

Para garantir o comportamento esperado em uma fila, <strong>de</strong>finimos que a<br />

inserção é feita no final da fila e a remoção é feita no começo da fila.<br />

Temos também a operação inicializaFila, que coloca a fila em um<br />

estado inicial, no qual a fila <strong>de</strong>ve estar vazia. A partir <strong>de</strong>ssas<br />

operações e <strong>de</strong> suas pré e pós-condições po<strong>de</strong>mos <strong>de</strong>duzir que serão<br />

necessárias as operações <strong>de</strong> teste <strong>de</strong> fila cheia e teste <strong>de</strong> fila vazia.<br />

Assim como na implementação da pilha precisamos <strong>de</strong> alguma forma<br />

<strong>de</strong> i<strong>de</strong>ntificar o topo, na fila iremos necessitar <strong>de</strong> alguma maneira <strong>de</strong><br />

i<strong>de</strong>ntificar o seu inicio e seu o fim.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 39


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 40<br />

Figura 8: Inserções e remoções em um TAD Fila.<br />

O diagrama da Figura 8 ilustra uma fila com capacida<strong>de</strong> para cinco<br />

elementos implementada em uma estrutura semelhante a um vetor. O<br />

digrama também ilustra a inserção <strong>de</strong> cinco elementos (M1..M5) e<br />

duas operações <strong>de</strong> remoção. Note que os valores <strong>de</strong> início e fim são<br />

usados para armazenar as posições atuais do início e do fim da fila.<br />

Inicialmente o valor <strong>de</strong> início está em zero. Po<strong>de</strong>mos usar essa<br />

condição para <strong>de</strong>tectarmos se a fila está vazia. Quando o elemento M1<br />

é inserido, este é colocado na posição 1 e o início e o fim são<br />

atualizados, ambos para 1. À medida que outros elementos são<br />

inseridos, o fim é atualizado. Quando o fim chega a 5, temos que a fila<br />

não comporta mais nenhuma inserção, isto é , a fila está cheia. Assim,<br />

po<strong>de</strong>mos usar essa condição para <strong>de</strong>tectarmos se a fila está cheia ou<br />

vazia.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


À medida que as remoções ocorrem, o inicio da fila é atualizado, <strong>de</strong><br />

forma a i<strong>de</strong>ntificar sempre o elemento a ser removido. Um problema<br />

<strong>de</strong>ssa implementação é que uma vez que fim atingiu o valor máximo<br />

(5 neste caso), não são possíveis mais inserções, mesmo havendo<br />

outros espaços. Isso é ruim, pois torna a fila muito limitada. Para<br />

contornarmos esse problema veremos duas implementações<br />

diferentes: Remoção com <strong>de</strong>slocamento e Fila circular. Na remoção<br />

com <strong>de</strong>slocamento, à medida que os elementos são removidos, os<br />

elementos remanescentes são <strong>de</strong>slocados para o início do arranjo.<br />

Figura 9: Implementação com Deslocamento.<br />

A Figura 9 mostra a implementação com <strong>de</strong>slocamento. Note que,<br />

nessa implementação, após a remoção <strong>de</strong> todos os elementos, o valor<br />

<strong>de</strong> início permaneceu com 1 e o valor <strong>de</strong> fim passou a ser zero. Assim,<br />

a condição <strong>de</strong> fila vazia não po<strong>de</strong> ser verificada pelo teste <strong>de</strong> início =<br />

0. Um possível teste é fim = 0 ou fim menor que início. A seguir<br />

vamos ver uma implementação em C usando arranjos para essa fila.<br />

Posteriormente veremos a implementação utilizando fila circular.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 41


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 42<br />

Ativida<strong>de</strong>s<br />

Or<strong>de</strong>nar uma fila utilizando 2 pilhas como variáveis<br />

auxiliares. Ao final da or<strong>de</strong>nação o elemento no início da<br />

fila <strong>de</strong>ve ser menor que o segundo da fila e assim<br />

sucessivamente. A or<strong>de</strong>nação <strong>de</strong>ve ser feita apenas em<br />

termos das operações insere, remove, filaVazia,<br />

empilha, <strong>de</strong>sempilha e pilhaVazia. A função<br />

<strong>de</strong>ve ter a seguinte organização:<br />

void or<strong>de</strong>na_2p (Fila* f){<br />

Pilha p1, p2;<br />

...<br />

}<br />

2.2.2 Implementação <strong>de</strong> Filas em arranjos com<br />

<strong>de</strong>slocamento<br />

De forma similar à da implementação <strong>de</strong> Pilha iremos <strong>de</strong>finir um<br />

módulo em C para implementação da Fila. Na Figura 10 temos a<br />

<strong>de</strong>finição da interface (arquivo filaecd.h) do TAD Fila.<br />

Figura 10: Interface do TAD Fila.<br />

A Figura 11 apresenta o arquivo filaecd.c, contendo a implementação<br />

das operações da Fila com <strong>de</strong>slocamento na remoção.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 11: Implementação do TAD Fila Com <strong>de</strong>slocamento.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 43


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 44<br />

O código está comentado e preten<strong>de</strong> ser auto-explicativo. A operação<br />

insereNaFila insere os elementos passados como parâmetro<br />

(elem). Para inserir na fila, inicialmente o valor <strong>de</strong> fim é<br />

incrementado, visto que este foi inicializado com valor –1. Dessa<br />

forma a primeira inserção insere na posição 0 do vetor, a segunda na<br />

posição 1 e assim sucessivamente. A operação mais complexa é a<br />

remoção. Para remover, inicialmente o elemento no início da fila é<br />

passado por referência por meio do parâmetro (elem *).<br />

Posteriomente os elementos remanescentes na Fila são <strong>de</strong>slocados <strong>de</strong><br />

uma posição. Dessa forma o elemento do início da fila ocupa<br />

novamente a posição 0 do vetor.<br />

O código da Figura 12 (arquivo principal.c) permite testar a Fila.<br />

Figura 12: Módulo para teste do TAD Fila.<br />

A função testeFilaecd <strong>de</strong>clara uma fila (q) inicializa a fila, insere os<br />

inteiros <strong>de</strong> 0 a 9 e <strong>de</strong>pois remove os elementos e os imprime. Note que<br />

todo o processamento é feito usando as operações do Tad Fila.<br />

Ativida<strong>de</strong>s<br />

4. Implementar as operações imprimir Fila para o TAD Fila<br />

implementado anteriormente.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


2.2.3 Implementação <strong>de</strong> Filas com Arranjos circulares<br />

Analisando a implementação do TAD Fila da seção<br />

anterior po<strong>de</strong>mos notar que, embora a fila esteja<br />

corretamente implementada, existe um problema <strong>de</strong><br />

eficiência na remoção <strong>de</strong> um elemento. Estou me<br />

referindo aos <strong>de</strong>slocamentos necessários para que a<br />

remoção seja feita e os espaços vagos no vetor<br />

possam ser reaproveitados. Veremos agora outra<br />

maneira <strong>de</strong> implementar o TAD Fila, em que esses<br />

<strong>de</strong>slocamentos não são necessários. Essa<br />

implementação chama-se Fila Circular.<br />

Função Sucessor<br />

Em um arranjo simples, cujas posições vão <strong>de</strong> 0 a t, po<strong>de</strong>mos<br />

i<strong>de</strong>ntificar facilmente qual o elemento que suce<strong>de</strong> uma <strong>de</strong>terminada<br />

posição i. Por exemplo, se i =0, o sucessor <strong>de</strong> i = 1, se i=1 sucessor <strong>de</strong><br />

i = 2. Ou seja, o sucessor <strong>de</strong> i = i+1, para i variando entre 0 e t-1.<br />

Nesse caso, não existe o sucessor <strong>de</strong> t.<br />

Agora, se <strong>de</strong>finimos que o sucessor <strong>de</strong> t = 0, temos então que todos os<br />

índices do arranjo possuem sucessor. A Figura 13 ilustra essa situação.<br />

Conforme po<strong>de</strong> ser observado, existe uma circularida<strong>de</strong> entre os<br />

índices.<br />

Figura 13: Fila Circular.<br />

Com vetores variando entre 0 a N-1, que é o caso da linguagem C, a<br />

função sucessor po<strong>de</strong> ser obtida elegantemente por meio da função:<br />

sucessor(i) = (i+1) mod n, on<strong>de</strong> mod é o operador<br />

resto.<br />

Quando utilizamos essa função para avançarmos o índice <strong>de</strong> um<br />

arranjo para a próxima, dizemos que estamos usando um arranjo<br />

circular.<br />

TAD FILA baseado em Arranjo Circular<br />

Com um arranjo circular, é possível <strong>de</strong>slocar o início e o fim da fila<br />

por todo o vetor, sem correr o risco da perda <strong>de</strong> espaço e sem a<br />

necessida<strong>de</strong> <strong>de</strong> realocar os elementos da fila. Nessa implementação as<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 45


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 46<br />

operações <strong>de</strong> inserir e remover, bem como as condições <strong>de</strong> Fila Cheia<br />

e Fila vazia precisam ser baseadas na função sucessor. A Figura 14<br />

apresenta a lógica <strong>de</strong>ssas operações, agora <strong>de</strong>finida em termos da<br />

função sucessor.<br />

Figura 14: Especificação das operações <strong>de</strong> um TAD Fila Circular.<br />

A Figura 15 apresenta a implementação das operações. Note que não é<br />

necessário um novo arquivo hea<strong>de</strong>r, pois este é o mesmo da<br />

implementação do módulo filaecd. Esse fato ilustra um aspecto<br />

importante da implementação <strong>de</strong> TADs: A implementação po<strong>de</strong> ser<br />

modificada sem necessida<strong>de</strong> <strong>de</strong> se alterar a interface. Outro aspecto da<br />

implementação com arranjo circular, é que uma das posições do<br />

arranjo é perdida. Isso é necessário para diferenciar as condições <strong>de</strong><br />

Fila Vazia e Fila Cheia.<br />

Ativida<strong>de</strong>s<br />

1. Reimplemente a Fila Circular, consi<strong>de</strong>rando que o elemento a ser<br />

inserido é um paciente <strong>de</strong> hospital (pessoa). Os seguintes dados são<br />

importantes para o paciente: nome, ida<strong>de</strong>, horário <strong>de</strong> chegada e<br />

enfermida<strong>de</strong>.<br />

2. Implemente também a operação imprimeFila para essa nova Fila.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 15: Implementação das operações <strong>de</strong> Fila em um arranjo Circular.<br />

2.3 IMPLEMENTAÇÃO DE TADS COM ALOCAÇÃO<br />

DINÂMICA DE MEMÓRIA<br />

2.3.1 Revisão <strong>de</strong> Alocação Dinâmica <strong>de</strong> Memória<br />

O objetivo da alocação dinâmica <strong>de</strong> memória é utilizar espaços da<br />

memória <strong>de</strong> tamanho arbitrário. Em adição, a alocação dinâmica <strong>de</strong><br />

memória permite criar estruturas <strong>de</strong> dados enca<strong>de</strong>adas. A alocação <strong>de</strong><br />

espaço sob <strong>de</strong>manda é utilizada quando o espaço <strong>de</strong> memória<br />

necessário para um conjunto <strong>de</strong> dados varia durante a execução do<br />

programa. Já o enca<strong>de</strong>amento provê um estilo eficiente <strong>de</strong> representar<br />

conjuntos <strong>de</strong> dados em C e <strong>de</strong> implementar as estruturas <strong>de</strong><br />

armazenamento <strong>de</strong> Tipos Abstratos <strong>de</strong> Dados.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 47


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 48<br />

A alocação dinâmica <strong>de</strong> memória necessita <strong>de</strong> suporte da Linguagem<br />

<strong>de</strong> <strong>Programação</strong>. Em C, esse suporte é fornecido por um conjunto <strong>de</strong><br />

funções disponíveis na biblioteca alloc. As principais funções <strong>de</strong>ssa<br />

biblioteca são a função malloc, que aloca um espaço na memória e<br />

retorna um ponteiro para o espaço alocado e a função free, que<br />

libera espaços <strong>de</strong> memória alocados por meio da função malloc.<br />

Exemplo <strong>de</strong> uso <strong>de</strong> alocação <strong>de</strong> espaço sob <strong>de</strong>manda<br />

Uma das vantagens da alocação dinâmica é permitir a alocação <strong>de</strong><br />

espaço <strong>de</strong> acordo com a necessida<strong>de</strong>. Por exemplo, suponha que um<br />

programa necessite <strong>de</strong> um arranjo para guardar N números inteiros.<br />

Usando alocação estática <strong>de</strong> memória, como não sabemos a<br />

quantida<strong>de</strong> precisa <strong>de</strong> números, <strong>de</strong>vemos fazer uma estimativa do<br />

valor máximo <strong>de</strong> número e <strong>de</strong>clarar o vetor com base nessa estimativa.<br />

A função alocEstatica da Figura 16 ilustra esta situação.<br />

Figura 16: Alocação Estática <strong>de</strong> memória.<br />

Note que o valor lido para n po<strong>de</strong> variar <strong>de</strong> 1 a 100. Quanto menor o<br />

valor <strong>de</strong> n, maior o <strong>de</strong>sperdício <strong>de</strong> espaço alocado.<br />

Usando alocação dinâmica teremos o valor <strong>de</strong> n que, lido, po<strong>de</strong> ser<br />

usado como parâmetro da chamada da função malloc, para alocar um<br />

conjunto <strong>de</strong> n inteiros, ou seja, um vetor <strong>de</strong> n inteiros. A função<br />

alocDinamica na Figura 17 é idêntica em termos <strong>de</strong><br />

funcionalida<strong>de</strong> à função alocEstatica. Contudo, esta utiliza<br />

alocação dinâmica.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 17: Vetor com Alocação dinâmica.<br />

Note que na alocação dinâmica não há limites para n, e este po<strong>de</strong>rá<br />

assumir, em teoria, qualquer valor maior que zero (n>0). A função<br />

malloc possui apenas um parâmetro formal: o número <strong>de</strong> bytes a<br />

serem alocados. Normalmente esse número <strong>de</strong> bytes é <strong>de</strong>terminado<br />

multiplicando o tamanho em bytes do tipo base a ser alocado pelo<br />

número <strong>de</strong> elementos. No exemplo, o tipo base é o int. Foi usada a<br />

macro sizeof para <strong>de</strong>terminar o tamanho exato do tipo int, que<br />

po<strong>de</strong> variar entre diferentes máquinas, sistemas operacionais e<br />

linguagens. Qualquer tipo po<strong>de</strong> ser utilizado como parâmetro <strong>de</strong><br />

sizeof, inclusive tipos <strong>de</strong>finidos pelo usuário.<br />

Listas Simplesmente Enca<strong>de</strong>adas<br />

Uma outra finalida<strong>de</strong> do uso <strong>de</strong> alocação dinâmica e apontadores é<br />

permitir a implementação <strong>de</strong> estruturas enca<strong>de</strong>adas utilizando<br />

estruturas auto referenciadas.Uma estrutura auto referenciada<br />

possui, <strong>de</strong>ntro seus campos, um campo que po<strong>de</strong> referenciar estruturas<br />

idênticas a ela mesma. Portanto, uma estrutura auto referenciada po<strong>de</strong><br />

apontar para outra estrutura do mesmo tipo que ela.<br />

O código na Figura 18 <strong>de</strong>fine um tipo Ponto que é auto referenciável.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 49


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 50<br />

Figura 18: Estrutura auto referenciada.<br />

O tipo Ponto <strong>de</strong>finido pelo type<strong>de</strong>f é um tipo ponteiro para a<br />

struct ponto. Essa struct por sua vez, possui <strong>de</strong>ntro <strong>de</strong>la os<br />

campos x,y (coor<strong>de</strong>nadas do ponto), cor (cor do ponto) e<br />

proximoPonto, que é uma variável do tipo ponteiro para struct<br />

ponto. A variável proximoPonto é que permite que uma variável<br />

do tipo struct ponto possa se ligar (apontar) a outra estrutura do<br />

tipo struct ponto.<br />

Figura 19: A struct ponto.<br />

Lista simplesmente Enca<strong>de</strong>ada<br />

A estrutura <strong>de</strong> dados enca<strong>de</strong>ada mais simples que temos<br />

é a lista simplesmente enca<strong>de</strong>ada. Uma lista<br />

simplesmente enca<strong>de</strong>ada é um agregado <strong>de</strong> elementos<br />

<strong>de</strong> um mesmo tipo, em que cada elemento é armazenado<br />

em um espaço alocado dinamicamente. À medida que<br />

elementos são inseridos na lista, esses espaços são<br />

criados e vão sendo enca<strong>de</strong>ados <strong>de</strong> forma que:<br />

- O en<strong>de</strong>reço do primeiro ou do último elemento da lista<br />

é mantido em uma variável do tipo ponteiro;<br />

- Os <strong>de</strong>mais elementos são acessados a partir do seu<br />

início ou do seu fim. Isto é possível porque cada nodo<br />

da lista possui um campo que aponta para o próximo<br />

elemento da lista.<br />

A struct ponto po<strong>de</strong> servir para implementarmos uma lista.<br />

Visualmente, uma lista enca<strong>de</strong>ada <strong>de</strong> pontos po<strong>de</strong> ser ilustrada pela<br />

Figura 20 a seguir:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 20: Exemplo <strong>de</strong> lista simplesmente enca<strong>de</strong>ada.<br />

lPontos é uma variável do tipo ponteiro para struct ponto<br />

(Ponto com P maiúsculo). Inicialmente a lista está vazia. Assim é<br />

conveniente fazermos a variável lPontos apontar para um valor<br />

“Seguro”, evitando possíveis invasões <strong>de</strong> espaço <strong>de</strong> memória<br />

in<strong>de</strong>vidos. Este valor seguro é NULL.<br />

Para inserirmos um nodo na lista <strong>de</strong> pontos, criamos o nodo,<br />

utilizando malloc e guardamos o en<strong>de</strong>reço <strong>de</strong>ste nodo em<br />

lPontos;<br />

Para manter o enca<strong>de</strong>amento, <strong>de</strong>vemos fazer o proximoPonto do<br />

nodo alocado apontar para o en<strong>de</strong>reço anteriormente contido em<br />

lPontos. O excerto <strong>de</strong> código apresentado na Figura 21 implementa<br />

esta operação <strong>de</strong> inserção.<br />

Figura 21: Inserindo um ponto na lista <strong>de</strong> pontos.<br />

Note que foi necessário guardar o valor <strong>de</strong> lPontos em aux, Isso<br />

ocorre porque, quando malloc é chamada para criar o novo Ponto, o<br />

en<strong>de</strong>reço retornado é atribuído a lPontos fazendo com que o seu<br />

en<strong>de</strong>reço anterior seja perdido.<br />

É conveniente implementar uma lista simplesmente enca<strong>de</strong>ada como<br />

um TAD, encapsulando sua estrutura por meio <strong>de</strong> operações bem<br />

<strong>de</strong>finidas. Essas operações compreen<strong>de</strong>m a inserção, a remoção<br />

e outras que por ventura sejam necessárias (por exemplo,<br />

imprimeLista). A seguir, iremos implementar uma lista enca<strong>de</strong>ada<br />

cujo objetivo é armazenar uma lista <strong>de</strong> pessoas. Cada pessoa é<br />

<strong>de</strong>finida pelo seu nome, seu código e seu telefone. Na implementação<br />

<strong>de</strong>finimos um módulo elemento e o módulo Tadlista. As Figuras 22 e<br />

23 apresentam a <strong>de</strong>finição da interface <strong>de</strong>sses dois módulos:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 51


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 52<br />

Figura 22: Interface do TAD Elemento.<br />

Figura 23: Interface do TAD Lista.<br />

A implementação da operação inicializaLista (Figura 24)<br />

consiste em atribuir o valor NULL a Lista passada por referência.<br />

Segue essa implementação:<br />

Figura 24: Implementação da operação inicializaLista.<br />

A implementação da inserção (Figura 25) segue exatamente o mesmo<br />

procedimento usado no exemplo da lista <strong>de</strong> pontos.<br />

Figura 25: Implementação da operação insereNaLista.<br />

A operação <strong>de</strong> remoção visa remover um elemento da lista tendo sido<br />

informado o código do elemento. Esse procedimento <strong>de</strong>ve também<br />

retornar o elemento a ser removido. A remoção <strong>de</strong>ve usar a função<br />

free para liberar o espaço alocado pelo nodo removido. Além disso,<br />

<strong>de</strong>ve ser também preservado o enca<strong>de</strong>amento da lista. De acordo com<br />

esses objetivos temos que realizar uma pesquisa na lista com o<br />

objetivo <strong>de</strong> encontrar o nodo a ser eliminado. Como resultado <strong>de</strong>ssa<br />

pesquisa existem duas possibilida<strong>de</strong>s:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


* O elemento está na lista. A pesquisa pára quando encontrar o<br />

elemento.<br />

* O elemento não está na lista. A pesquisa chega até o fim da lista<br />

(NULL).<br />

Formulamos então o algoritmo recursivo apresentado na Figura 26.<br />

Figura 26: algoritmo para remoção <strong>de</strong> um elemento na Lista.<br />

A Figura 27 apresenta uma implementação para a operação <strong>de</strong><br />

remoção baseada no algoritmo da Figura 26.<br />

Figura 27: Implementação da operação removeDaLista.<br />

A operação pesquisaNaLista visa retornar um elemento cujo<br />

código é passado como parâmetro. Essa pesquisa é feita por meio <strong>de</strong><br />

um loop, que percorre a lista até encontrar o elemento ou chagar ao<br />

seu final. Temos novamente duas situações: busca com sucesso ou<br />

busca sem sucesso. Na Figura 28, temos a implementação <strong>de</strong>sta<br />

operação.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 53


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 54<br />

Figura 28: Implementação da operação pesquisaNaLista.<br />

A Figura 30 apresenta a implementação da operação<br />

imprimeLista, que utiliza a operação imprimeElemento do<br />

TAD elemento (Figura 29). A operação imprimeLista <strong>de</strong>ve<br />

percorrer a lista até o final imprimindo o conteúdo <strong>de</strong> seus elementos.<br />

Seguem as implementações das operações imprimeElemento<br />

arquivo elemento.c) e imprimeLista (arquivo tadLista.c). A<br />

operação imprimeLista é recursiva. A cada chamada da<br />

recursivida<strong>de</strong>, é passado um apontador para o próximo elemento da<br />

lista. A condição <strong>de</strong> parada da recusivida<strong>de</strong> é chegar ao final da Lista<br />

(NULL).<br />

Figura 29: Implementação da operação imprimeElemento do TAD Elemento.<br />

Figura 30: Implementação da operação imprimeLista.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Ativida<strong>de</strong>s<br />

1. Implementar uma operação para o TAD Lista<br />

que insira os elementos no seu final.<br />

2. Implementar uma operação para o tadLista<br />

que transforme a lista em uma lista or<strong>de</strong>nada.<br />

Essa operação <strong>de</strong>ve inserir os elementos <strong>de</strong><br />

modo que a lista permaneça sempre em or<strong>de</strong>m<br />

crescente a partir <strong>de</strong> seu inicio. A chave <strong>de</strong><br />

or<strong>de</strong>nação a ser utilizada <strong>de</strong>ve ser o código do<br />

elemento.<br />

2.3.2 Implementação do TAD Pilha<br />

A implementação do TAD Pilha com alocação dinâmica <strong>de</strong> memória<br />

possui como vantagem principal o fato <strong>de</strong> po<strong>de</strong>rmos consi<strong>de</strong>rar a sua<br />

capacida<strong>de</strong> <strong>de</strong> armazenamento como sendo infinita. Ou seja, usando<br />

alocação dinâmica não precisamos testar se a Pilha está cheia para<br />

fazer um empilhamento. Outra vantagem está na simplicida<strong>de</strong> da<br />

implementação. O quadro abaixo ilustra a <strong>de</strong>finição do tipo Pilha<br />

como uma estrutura dinâmica enca<strong>de</strong>ada. Po<strong>de</strong>mos afirmar que uma<br />

pilha é uma lista on<strong>de</strong> os elementos são inseridos no início e<br />

removidos do início. Para efeitos <strong>de</strong> implementação da pilha,<br />

chamamos o início <strong>de</strong> topo.<br />

A Figura 31 apresenta o arquivo <strong>de</strong> interface TadPilhaAD.h com a<br />

<strong>de</strong>finição da estrutura <strong>de</strong> dados Pilha e das operações. Note<br />

novamente que as operações são as mesmas apresentadas no módulo<br />

Pilha.h visto anteriormente.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 55


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 56<br />

Figura 31: Interface do TAD Pilha com alocação dinâmica<br />

Para a <strong>de</strong>finição da Pilha dinâmica, é <strong>de</strong>finido o tipo Nodo que é um<br />

tipo ponteiro para struct nodo (em minúsculo). Pilha é uma estrutura<br />

que contém apenas uma variável: o topo. Esse topo, por sua vez, é do<br />

tipo Nodo.<br />

Uma variável do tipo Pilha é uma variável estática. Essa variável é<br />

uma estrutura contendo como único campo o topo. O topo, por sua<br />

vez, é que é um apontador.<br />

Pilha p;<br />

p.topo=NULL;<br />

NULL<br />

topo<br />

Figura 32: Representação do da estrutura <strong>de</strong> dados dinâmica Pilha.<br />

Topo é uma variável do tipo ponteiro para struct nodo. Assim<br />

topo pô<strong>de</strong> ser inicializado com NULL. Essa condição po<strong>de</strong> ser<br />

utilizada para <strong>de</strong>tectarmos se a Pilha está vazia. A Figura 33 apresenta<br />

as operações inicializaPilha e pilhaVazia seguindo exatamente essas<br />

<strong>de</strong>cisões <strong>de</strong> implementação.<br />

p<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 33: Implementação das operações inicializaPilha e pilhaVazia.<br />

A operação empilha po<strong>de</strong> ser especificada da seguinte forma:<br />

1. Cria uma nova estrutura do tipo struct nodo;<br />

2. Faz o prox da nova estrutura apontar para a estrutura para a<br />

qual o topo da Pilha aponta;<br />

3. Faz o topo da pilha apontar para o novo nodo.<br />

Seguindo os passos acima, após inserirmos o primeiro elemento na<br />

pilha (valor 1), e supondo que o nodo foi alocado a partir do<br />

en<strong>de</strong>reço 300h da memória, teremos a configuração apresentada na<br />

Figura 34.<br />

300h<br />

topo<br />

NULL<br />

prox<br />

1<br />

item<br />

p<br />

300h<br />

Figura 34: Diagrama da Pilha com um nodo (um elemento empilhado).<br />

Se empilharmos agora um segundo elemento (valor 2), e supondo que<br />

o novo nodo foi alocado na posição 400h, teremos a seguinte<br />

configuração apresentada na Figura 35.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 57


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 58<br />

Inserindo agora o elemento 3 e supondo a alocação no en<strong>de</strong>reço 500h<br />

teremos a configuração da Figura 36.<br />

500h<br />

topo<br />

p<br />

400h<br />

topo<br />

p<br />

NULL<br />

prox<br />

1<br />

item<br />

Figura 36: inserindo o elemento 3.<br />

300h<br />

prox<br />

A remoção <strong>de</strong> um elemento na pilha dinâmica implica nos seguintes<br />

passos:<br />

1. se a pilha não está vazia:<br />

a. guarda o valor que está no topo;<br />

b. salva o en<strong>de</strong>reço que está no topo em uma variável<br />

auxiliar;<br />

c. atribui o en<strong>de</strong>reço do prox do topo ao topo;<br />

d. libera o espaço ocupado pelo nodo que estava no topo.<br />

Executando esses procedimentos, a regra fundamental da pilha, o<br />

último que chega é o primeiro que sai, é sempre respeitada. A Figura<br />

37 apresenta a implementação das operações empilha e <strong>de</strong>sempilha:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

2<br />

item<br />

300h<br />

400h<br />

Figura 35: Empilhando o elemento 2.<br />

NULL<br />

Prox<br />

1<br />

item<br />

300h<br />

300h<br />

prox<br />

2<br />

item<br />

400h<br />

400h<br />

prox<br />

3<br />

item<br />

500h


Figura 37: Implementação das operações empilha e <strong>de</strong>sempilha.<br />

Ativida<strong>de</strong>s<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Reimplementar o TadPilha, usando o TAD Lista, (visto na<br />

revisão <strong>de</strong> Alocação Dinâmica), como cliente. Nessa nova<br />

implementação o tipo Pilha <strong>de</strong>ve ser <strong>de</strong>finido em termos do<br />

tadLista e as implementações <strong>de</strong> empilha e <strong>de</strong>sempilha e<br />

filaVazia <strong>de</strong>vem ser feitas com base nas operações <strong>de</strong><br />

remoção e inserção.<br />

2.3.3 Implementação do TAD Fila<br />

A Figura 38 presenta o arquivo TadFilaAD.h. Assim como na<br />

implementação da Pilha, foi utilizada uma estrutura chamada nodo<br />

contendo um campo item do tipo Elemento e um campo prox para<br />

estabelecer o enca<strong>de</strong>amento dos nodos. O tad Fila por sua vez é uma<br />

estrutura contendo os campos inicio e fim que são apontadores para struct<br />

nodo (Nodo). Uma variável to tipo TadFila é uma estrutura contendo esses<br />

dois campos. Por exemplo, a seqüência:<br />

Página 59


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 60<br />

TadFila q;<br />

q.inicio=NULL;<br />

q.fim=NULL;<br />

Figura 38: Interface do TAD Fila com alocação dinâmica.<br />

NULL<br />

inicio<br />

NULL<br />

fim<br />

q<br />

Figura 39: diagrama <strong>de</strong> memória da estrura dinâmica Fila.<br />

A Figura 39 ilustra a variável q. Analogamente a uma Pilha, po<strong>de</strong>mos<br />

inicializar a Fila colocando o Valor NULL em inicio e fim. Além<br />

disso, po<strong>de</strong>mos usar a condição início = NULL para <strong>de</strong>tectar se a Fila<br />

está vazia. A Figura 40 apresenta a implementação <strong>de</strong>stas operações.<br />

Figura 40: Implementação das operações inicializaFila e filaVazia.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Após a inserção <strong>de</strong> um elemento (1) na Fila, supondo que o nodo vai<br />

ser alocado na posição 300h, temos a configuração apresentada na<br />

Figura 41.<br />

300h<br />

inicio<br />

300h<br />

fim<br />

NULL<br />

prox<br />

1<br />

item<br />

300h<br />

Figura 41: Inserindo o elemento 1 na Fila.<br />

Inserindo mais um elemento (2) e supondo que o nodo foi alocado no<br />

en<strong>de</strong>reço 400h, temos a inserção apresentada na Figura 42.<br />

300h<br />

inicio<br />

400h<br />

fim<br />

q<br />

Figura 42: Inserindo o elemento 2.<br />

Note que o conteúdo do apontador fim foi atualizado. Essa operação<br />

<strong>de</strong>ve ser cuidadosa para que os ponteiros não fiquem perdidos.<br />

Existem duas situações que <strong>de</strong>vem ser tratadas diferentemente:<br />

quando a Fila está vazia e quando não está vazia. A seguinte<br />

seqüência <strong>de</strong> passos <strong>de</strong>ve ser utilizada:<br />

1. aloca o novo nodo<br />

2. atribui os valores ao novo nodo<br />

3. Se a fila está vazia<br />

a. início = novonodo<br />

b. fim = novonodo<br />

4. Se não<br />

a. o prox do nodo apontado por fim recebe o novonodo<br />

b. fim=novonodo<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

q<br />

400h<br />

prox<br />

1<br />

item<br />

300h<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

NULL<br />

prox<br />

2<br />

item<br />

400h<br />

Página 61


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 62<br />

O código que implementa a operação insere é apresentada na<br />

Figura 43.<br />

Figura 43: Implementação da operação insereNaFila.<br />

A remoção <strong>de</strong>ve guardar o valor que está no fim da Fila, atualizar o<br />

ponteiro <strong>de</strong> Fim e liberar o nodo que estava no fim. Os seguintes<br />

passos <strong>de</strong>vem ser seguidos:<br />

1. Se a Fila não está vazia<br />

a. guarda o valor contido no nodo a ser eliminado (que<br />

está no inicio da Fila)<br />

b. guarda o en<strong>de</strong>reço do nodo a ser eliminado (inicio) em<br />

uma variável auxiliar (aux)<br />

c. inicio recebe o próximo <strong>de</strong> inicio<br />

d. libera a área apontada por aux<br />

O código que implementa a operação removeDaFila é apresentado<br />

na Figura 44.<br />

Figura 44: Implementação da operação removeDaFila.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


O código apresentado na Figura 45 ilustra a utilização do TadFila.<br />

Note que, fora a mudança nos arquivos <strong>de</strong> inclusão, este programa é o<br />

mesmo que foi usado para testar a implementação da fila com<br />

<strong>de</strong>slocamento.<br />

Ativida<strong>de</strong>s<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

1. Adicionar a operação imprimeFila ao<br />

TadFila.<br />

2. Adicionar a operação removePrioritario ao<br />

TadFila. Esta operação <strong>de</strong>ve permitir a remoção<br />

com priorida<strong>de</strong> na Fila. A priorida<strong>de</strong> vai se basear<br />

no valor inserido no campo item. A execução <strong>de</strong><br />

removePrioritario <strong>de</strong>ve remover o nodo da<br />

fila cujo item for o maior.<br />

Figura 45: Módulo para teste do TAD Fila.<br />

Página 63


3. LISTAS E ÁRVORES.<br />

Olá, Neste Capítulo continuaremos o nosso estudo<br />

<strong>de</strong> Tipos Abstratos <strong>de</strong> Dados, focalizando<br />

implementações que utilizam alocação dinâmica <strong>de</strong><br />

memória. Iremos estudar mais sobre listas<br />

enca<strong>de</strong>adas e também sobre estruturas chamadas <strong>de</strong><br />

Árvores. Nosso estudo <strong>de</strong> Listas baseia-se em<br />

variações do TAD Lista estudado no Capítulo 2. Da<br />

mesma forma como fizemos para a lista<br />

simplesmente enca<strong>de</strong>ada, para cada variação iremos<br />

especificar e implementar as operações para<br />

inicializar a lista, inserir um elemento na lista,<br />

remover e recuperar um elemento existente na lista.<br />

3.1 LISTAS CIRCULARES<br />

A primeira variação que estudaremos chama-se Lista Circular. Nessa<br />

Lista, não existe o último elemento. Aquele elemento da lista<br />

simplesmente enca<strong>de</strong>ada que antes apontava para NULL irá agora<br />

apontar para o início da Lista, criando então um conjunto circular <strong>de</strong><br />

nodos.<br />

e prox e prox e prox<br />

Figura 1: Lista Circular Simplesmente Enca<strong>de</strong>ada<br />

Com essa estrutura, em uma lista circular não temos mais a noção <strong>de</strong><br />

Fim <strong>de</strong> Lista (o nodo que apontava para NULL). A Figura 1 ilustra<br />

uma lista enca<strong>de</strong>ada circular contendo três nodos. Conforme po<strong>de</strong>mos<br />

observar, essa estrutura po<strong>de</strong> ser implementada com nodos idênticos<br />

aos <strong>de</strong> uma Lista simples. Entretanto a interface do TAD<br />

ListaCircular irá apresentar algumas mudanças. A Figura 2<br />

apresenta a implementação <strong>de</strong>ssa interface.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 65


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 66<br />

Figura 2: Interface do TAD Lista (Circular). Similar a Lista simples.<br />

Em uma Lista circular irá ter sempre um elemento <strong>de</strong> acesso, apontado<br />

por uma variável do Tipo Lista, por exemplo:<br />

Lista l;<br />

A operação <strong>de</strong> inicialização atribui NULL à variável l. A Figura 3<br />

Apresenta essa implementação, que é idêntica à <strong>de</strong> uma lista<br />

simplesmente enca<strong>de</strong>ada.<br />

Figura 3: Implementação <strong>de</strong> inicializaLista para lista circular.<br />

As inserções na lista po<strong>de</strong>rão ser feitas <strong>de</strong> duas formas: à direita<br />

(<strong>de</strong>pois do nodo apontado por l) e à esquerda (antes do nodo apontado<br />

por l). A Figura 4 ilustra as operações <strong>de</strong> inicialização e inserção em<br />

uma lista circular, supondo uma variável l do tipo Lista. Na Figura os<br />

valores 100h, 200h, .. representam os en<strong>de</strong>reços <strong>de</strong> memória <strong>de</strong> cada<br />

nodo da lista. Inicialmente l é inicializada e não aponta para nenhum<br />

en<strong>de</strong>reço <strong>de</strong> memória. Em seguida, é feita uma inserção, que po<strong>de</strong> ser<br />

tanto à direita quanto à esquerda. Nessa primeira inserção, l passa a<br />

apontar para o nodo inserido e o prox do nodo inserido aponta para ele<br />

mesmo. Em seguida é feita uma inserção à direita (<strong>de</strong>pois) do nodo<br />

apontado por l. Nesse caso, o prox do novo nodo recebe o valor do<br />

prox <strong>de</strong> l e prox <strong>de</strong> l passa a ser o nodo inserido.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


NULL<br />

l<br />

100h<br />

l<br />

100h<br />

l<br />

300h<br />

l<br />

inicializa Lista l<br />

100h<br />

e prox<br />

1<br />

100h<br />

100h<br />

e prox<br />

1<br />

200h<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Inserção - à direita ou à esquerda<br />

e<br />

200h<br />

2<br />

prox<br />

300h 100h<br />

200h<br />

e prox<br />

e prox<br />

e prox<br />

3 100h<br />

1 200h<br />

2<br />

Figura 4: Seqüência <strong>de</strong> operações em uma Lista Circular<br />

A última operação é uma inserção à esquerda, (antes) do nodo<br />

apontado por l. Nessa operação, o procedimento se inicia <strong>de</strong> forma<br />

idêntica à inserção à direita:<br />

1. Cria novo nodo;<br />

2. Próximo <strong>de</strong> novo nodo recebe o próximo <strong>de</strong> l;<br />

3. Próximo <strong>de</strong> l recebe o en<strong>de</strong>reço do novo nodo;<br />

No entanto, ao final da operação <strong>de</strong> inserção à esquerda o<br />

início da lista passa a apontar para o nodo recém criado<br />

(próximo <strong>de</strong>la).<br />

100h<br />

A Figura 5 apresenta as implementações <strong>de</strong>ssas operações.<br />

Note que a operação insereaDireita é usada na operação<br />

insereaEsquerda. Após a sua chamada apenas o valor <strong>de</strong><br />

l é atualizado passando a apontar para o nodo recém<br />

adicionado.<br />

Inserção à direita<br />

300h<br />

Inserção à esquerda<br />

Página 67


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 68<br />

Figura 5: Implementação das operações <strong>de</strong> inserção<br />

Para a operação <strong>de</strong> remoção iremos consi<strong>de</strong>rar duas possibilida<strong>de</strong>s:<br />

remoção à direita e Remoção <strong>de</strong> um elemento arbitrário.<br />

300h<br />

l<br />

300h<br />

l<br />

300h<br />

l<br />

NULL<br />

l<br />

300h 100h<br />

200h<br />

e prox<br />

e prox<br />

e prox<br />

3 100h<br />

1 200h<br />

2<br />

300h<br />

e prox<br />

3<br />

200h<br />

300h<br />

e prox<br />

3<br />

300h<br />

e<br />

200h<br />

2<br />

prox<br />

300h<br />

Figura 6: Seqüência <strong>de</strong> Remoções à Direita<br />

A remoção à direita implica em remover o elemento que está à direita<br />

(<strong>de</strong>pois) do elemento inicial da lista. Esse elemento é exatamente o<br />

elemento apontado pelo prox <strong>de</strong> do nodo inicial. A Figura 6 ilustra<br />

uma seqüência <strong>de</strong> operações <strong>de</strong> remoção à direita sobre a lista l.<br />

Conforme po<strong>de</strong>mos observar, o valor <strong>de</strong> l permanece o mesmo<br />

durante as remoções, mudando apenas quando a lista fica vazia. A<br />

seguintes alterações nos apontadores são necessárias:<br />

se (l é igual ao próximo <strong>de</strong> l)<br />

l recebe NULL<br />

senão<br />

próximo <strong>de</strong> l recebe o próx do nodo a ser<br />

eliminado.<br />

A implementação <strong>de</strong> removeaDireita é apresentada na figura 7.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

300h


Figura 7: Implementação da Remoção à direita<br />

Pergunta: Por que não optamos por remover sempre<br />

o elemento inicial da lista? Para respon<strong>de</strong>r a essa<br />

pergunta, tente implementar essa operação. Você<br />

verá que vai necessitar alterar o valor do prox do<br />

elemento que aponta para l. Como não temos acesso<br />

direto a esse elemento por meio <strong>de</strong> l, será necessário<br />

percorrer a lista inteira até encontrá-lo. Assim essa<br />

operação fica ineficiente.<br />

A remoção <strong>de</strong> um elemento arbitrário da lista é a operação <strong>de</strong><br />

eliminação <strong>de</strong>sse elemento, dada uma chave <strong>de</strong> busca do mesmo. Por<br />

exemplo, suponha que queiramos eliminar o nodo da lista l com valor<br />

2 (vi<strong>de</strong> Figura 6) Nesse caso, o nodo que contém o elemento 2 <strong>de</strong>ve<br />

ser inicialmente localizado. Além do nodo em si, é necessário<br />

encontrar o nodo cujo prox aponta para o nodo a ser eliminado. Para<br />

isso po<strong>de</strong>mos usar dois apontadores auxiliares.<br />

Na exclusão, as seguintes situações <strong>de</strong>vem ser consi<strong>de</strong>radas:<br />

Remoção <strong>de</strong> um elemento genérico da lista que seja diferente<br />

do elemento apontado por l;<br />

Remoção do elemento da lista que é apontado por l (início da<br />

lista);<br />

Remoção do último elemento da lista.<br />

A Figura 8 ilustra esses casos.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 69


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 70<br />

300h<br />

l<br />

Remoção do elemento 2<br />

300h<br />

l<br />

Remoção do elemento 3<br />

Remoção do elemento 1<br />

300h 100h<br />

200h<br />

e prox<br />

e prox<br />

e prox<br />

3 100h<br />

1 200h<br />

2<br />

300h<br />

l<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

300h<br />

300h 100h<br />

e<br />

3<br />

prox<br />

200h<br />

e<br />

1<br />

prox<br />

Situação 1: Remoção <strong>de</strong> um elemento Genérico da lista<br />

300h 100h<br />

200h<br />

e prox<br />

e prox<br />

e prox<br />

3 100h<br />

1 200h<br />

2<br />

200h<br />

l<br />

300h<br />

300h<br />

200h 100h<br />

e<br />

2<br />

prox<br />

100h<br />

e<br />

1<br />

prox<br />

Situação 2: Remoção <strong>de</strong> elemento apontado por l<br />

100h<br />

l<br />

100h<br />

e prox<br />

100h<br />

1<br />

NULL<br />

Situação 3: Remoção do último elemento da lista<br />

Figura 8: Situações para Remoção <strong>de</strong> um Elemento Arbitrário da Lista<br />

Na Situação 1, o elemento que se <strong>de</strong>seja eliminar é o 2. Esse caso<br />

po<strong>de</strong> ser consi<strong>de</strong>rado o caso normal da lista. Para eliminar esse<br />

elemento, o prox do nodo anterior a ele (100h) <strong>de</strong>verá apontar para o<br />

próximo <strong>de</strong>le (300h). Na segunda situação, temos que o nodo a ser<br />

eliminado é o mesmo apontado por l (início da lista). Nesse caso o<br />

valor <strong>de</strong> l precisa ser atualizado, apontando para o anterior do nodo<br />

que será eliminado.<br />

Na terceira situação o nodo a ser eliminado é o último. Logo, l precisa<br />

ser atualizado para NULL. O seguinte algoritmo po<strong>de</strong> ser utilizado<br />

para a remoção:<br />

l<br />

Elemento removeElemento(Lista l, int codigo){<br />

se l está vazia<br />

1. retorna elemento vazio<br />

senão<br />

2. localizo elemento<br />

Se elemento procurado não existe<br />

retorno elemento vazio<br />

senão<br />

3. o próximo do anterior do elemento procurado<br />

recebe o próximo do elemento procurado<br />

4. guardo o elemento a ser retornado<br />

se o elemento procurado é o elemento apontado por<br />

l<br />

200h


se a lista possui mais <strong>de</strong> um elemento<br />

5. l recebe o anterior do elemento procurado<br />

senão<br />

6. l recebe NULL<br />

7. retorna elemento<br />

Figura 9: Implementação da operação removeElemento<br />

A Figura 9 apresenta a implementação da operação removeElemento,<br />

que remove um elemento arbitrário da lista circular. paux1 e<br />

paux2 são apontadores auxiliares utilizados para percorrer a lista,<br />

localizar o nodo que contém o elemento procurado (paux2) e o<br />

nodo anterior a ele (paux1).<br />

Inicialmente, paux1 recebe o valor <strong>de</strong> l e paux2 o próximo <strong>de</strong> l. O<br />

do{ ..}while(paux2!=*l); realiza a busca do elemento. Se o<br />

elemento é encontrado, a busca pára <strong>de</strong>vido ao comando break. Se o<br />

elemento não existe, a lista toda é percorrida e a busca pára quando<br />

paux2 obtém o valor <strong>de</strong> *l.<br />

Ao terminar a busca, <strong>de</strong>vemos verificar se o elemento foi realmente<br />

encontrado. Se não foi encontrado, a operação retorna um elemento<br />

vazio. Caso contrário, a eliminação se dá, fazendo o prox do paux1<br />

apontar para o prox <strong>de</strong> paux2. Em seguida as situações 2 e 3 (vi<strong>de</strong><br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 71


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 72<br />

Figura 8) <strong>de</strong> remoção são verificadas e, se necessário, o valor <strong>de</strong> l será<br />

atualizado.<br />

As <strong>de</strong>mais operações do TAD (imprime e pesquisa) baseiam-se em<br />

varreduras na lista a partir do nodo inicial. Para essas operações, é<br />

sempre utilizado um apontador auxiliar, que ao final <strong>de</strong> cada repetição<br />

no laço <strong>de</strong> busca é atualizado recebendo o próximo nodo da lista.<br />

Esse apontador é inicializado com o valor do nodo inicial da lista e a<br />

condição <strong>de</strong> parada é ele voltar a ser igual ao en<strong>de</strong>reço do nodo inicial.<br />

Por isso, utilizamos sempre o do{ .. }while(..); em vez <strong>de</strong><br />

while(..){ .. }. Mesmo sendo iniciado com o valor do<br />

en<strong>de</strong>reço inicial, quando o teste do laço é atingido, o apontador<br />

auxiliar é atualizado, passando a apontar para o seu próximo. Assim,<br />

somente <strong>de</strong>pois <strong>de</strong> percorrer a lista inteira é que o apontador auxiliar<br />

valerá novamente o en<strong>de</strong>reço do nodo inicial. As figuras 10 e 11<br />

apresentam as implementações das operações pesquisaNaLista,<br />

imprimeLista e imprimeLista<strong>de</strong>Nomes, que utilizam essa<br />

técnica.<br />

Figura 10: Implementação da operação pesquisaNaLista<br />

A operação pesquisaNaLista visa procurar um elemento cujo<br />

código é igual ao valor <strong>de</strong> codigo (argumento da função).<br />

O TAD Elemento<br />

A estrutura do elemento armazenado na lista é a<br />

mesma do TAD elemento armazenado na lista<br />

simplesmente enca<strong>de</strong>ada. Foi adicionada mais uma<br />

operação, chamada imprimeNome, que imprime<br />

apenas o nome do elemento. Assim, a interface do<br />

TAD Elemento utilizado nessa implementação <strong>de</strong><br />

listas circulares possui a seguinte estrutura:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 11: Operações imprimeLista e imprimeListaDeNomes<br />

As duas operações <strong>de</strong> impressão adotam a mesma estratégia. A<br />

diferença entre elas é que a operação imprimeLista vai imprimir<br />

todos os dados <strong>de</strong> cada elemento. Já a operação<br />

imprimeListasDeNomes vai imprimir somente os nomes <strong>de</strong> cada<br />

elemento, usando uma formatação que facilita a <strong>de</strong>puração e o<br />

entendimento das operações. O programa módulo principal da<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 73


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 74<br />

implementação <strong>de</strong> listas circulares utiliza essa operação e apresenta<br />

sua impressão. A Figura 12 apresenta a função testeListaCircular.<br />

Figura 12: Implementação do Módulo Principal para Testar a ListaCircular.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Após a execução <strong>de</strong> testeListaCircular a seguinte saída é<br />

gerada:<br />

Cada linha da saída é o resultado <strong>de</strong> uma chamada <strong>de</strong><br />

imprimeListaDeNomes.<br />

Ativida<strong>de</strong>s<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

a) Implementar uma operação insere<br />

or<strong>de</strong>nado. Utilizando essa operação para inserir<br />

elementos, a partir do nodo inicial da lista, o<br />

próximo <strong>de</strong> cada elemento <strong>de</strong>ve ser maior que o<br />

seu antecessor;<br />

b) Implementar uma operação para concatenar duas<br />

listas circulares A e B. O resultado <strong>de</strong>ve ser uma<br />

lista contendo todos os elementos das duas listas,<br />

<strong>de</strong> forma alternada. Ou seja: o primeiro da lista A,<br />

o primeiro da lista B, o segundo da lista A, o<br />

segundo da lista B, e assim sucessivamente.<br />

3.2 LISTA CIRCULAR DUPLAMENTE ENCADEADA<br />

Muitas operações envolvendo listas enca<strong>de</strong>adas po<strong>de</strong>m ser facilitadas<br />

se cada nodo conhecer o seu antecessor. Uma lista circular duplamente<br />

enca<strong>de</strong>ada possui exatamente essa característica. Além do apontador<br />

para o próximo elemento, cada nodo da lista vai ter um apontador para<br />

o elemento anterior.<br />

Página 75


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 76<br />

NULL<br />

l<br />

100h<br />

l<br />

200h<br />

l<br />

300h<br />

l<br />

inicializaLista<br />

ant<br />

100h<br />

ant<br />

100h<br />

300h<br />

e prox<br />

3 200h<br />

ant<br />

100h<br />

e prox<br />

100h<br />

e prox<br />

1 100h<br />

200h 100h<br />

ant<br />

2 100h<br />

200h 1<br />

ant<br />

300h<br />

e prox<br />

e prox<br />

Insere(1)<br />

200h<br />

2 100h<br />

200h 1<br />

Insere(2)<br />

200h 100h<br />

ant<br />

Figura 13: lista duplamente enca<strong>de</strong>ada<br />

e prox<br />

300h<br />

Insere(3)<br />

A Figura 13 ilustra uma seqüência <strong>de</strong> inserções em uma lista circular<br />

duplamente enca<strong>de</strong>ada. Inicialmente a lista está vazia: l possui o valor<br />

NULL. Os campos ant e prox são respectivamente os apontadores<br />

para os nodos anterior e próximo <strong>de</strong> cada nodo da lista. No modo <strong>de</strong><br />

inserção adotado, temos duas situações distintas ao inserir um<br />

elemento: A lista está vazia ou a lista não está vazia. Quando a lista<br />

está vazia, l, o próximo e o anterior do novo nodo recebem todos o<br />

mesmo valor: o en<strong>de</strong>reço do novo nodo. Isto é mostrado na inserção<br />

do elemento 1 (um) na lista). Quando a lista não está vazia, além dos<br />

apontadores do prox e ant do novo nodo, os apontadores do nodo<br />

anterior e posterior ao nodo apontado por l (além do próprio l) <strong>de</strong>verão<br />

também ser atualizados. Observe as inserções dos elementos 2 e 3 na<br />

lista e veja como os ponteiros são modificados. Para manter o<br />

enca<strong>de</strong>amento correto, as seguintes atualizações são necessárias:<br />

1. O próximo do anterior a l recebe o novo<br />

nodo;<br />

2. O próximo do novo nodo recebe l;<br />

3. O anterior do novo nodo recebe o<br />

anterior <strong>de</strong> l;<br />

4. O ponteiro anterior <strong>de</strong> l recebe p;<br />

5. l recebe o en<strong>de</strong>reço do novo nodo.<br />

Esses passos estão i<strong>de</strong>ntificados com os número <strong>de</strong> 1 a 5 na<br />

implementação da inserção (Figura 15).<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Atenção: Na Figura 13, a variável l é do tipo ponteiro<br />

para o nodo da lista. Na implementação que iremos<br />

apresentar, essa variável irá sempre apontar para o<br />

último nodo inserido na lista. Contudo, outras<br />

implementações são possíveis.<br />

Como <strong>de</strong> costume, implementaremos a lista circular duplamente<br />

enca<strong>de</strong>ada como um Tipo Abstrato <strong>de</strong> Dados. A Figura 14 apresenta a<br />

interface <strong>de</strong>sse TAD.<br />

Figura 14: Interface do TAD Lista Circular Duplamente Enca<strong>de</strong>ada<br />

A inicialização segue o mesmo princípio <strong>de</strong> uma lista simplesmente<br />

enca<strong>de</strong>ada. O apontador inicial da lista aponta para NULL. A<br />

implementação da operação inicializaLista é idêntica à da lista<br />

Circular (Figura 3). Na implementação da operação <strong>de</strong> inserção,<br />

iremos consi<strong>de</strong>rar o modo apresentado na Figura 13. A Figura 15<br />

apresenta a implementação das operações inicializaLista e<br />

insereNaLista. Iremos consi<strong>de</strong>rar uma lista <strong>de</strong> elementos do tipo<br />

Elemento <strong>de</strong>finido para a lista circular (vi<strong>de</strong> TAD Elemento, pag. 59).<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 77


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 78<br />

Figura 15: Implementação das operações inicializa e insere<br />

Iremos apresentar duas formas <strong>de</strong> remoção: Remoção do elemento<br />

apontado por l e remoção <strong>de</strong> um elemento arbitrário.<br />

Figura 16: Eliminação do nodo apontado por l<br />

A Figura 15 apresenta a implementação da operação <strong>de</strong> remoção do<br />

nodo apontado por l. Para tornar a implementação mais simples foram<br />

usados dois apontadores (do tipo Lista) auxiliares: proximo e<br />

anterior. O elemento a ser removido (apontado por l) é o<br />

elemento entre o próximo e o anterior. Assim, para manter o<br />

enca<strong>de</strong>amento, próximo e anterior <strong>de</strong>verão apontar um para o outro.<br />

Como o nodo apontado por l vai ser eliminado, o valor contido em l<br />

<strong>de</strong>ve ser atualizado, passando a apontar para proximo.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


300h<br />

l<br />

200h<br />

l<br />

ant<br />

100h<br />

300h<br />

e prox<br />

3 200h<br />

ant<br />

100h<br />

ant<br />

300h<br />

e prox<br />

e prox<br />

2 100h<br />

200h 1<br />

200h<br />

proximo<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

200h 100h<br />

2 100h<br />

200h 1<br />

ant<br />

200h 100h<br />

ant<br />

Figura 16: Remoção do nodo apontado por l<br />

e prox<br />

200h<br />

e prox<br />

300h<br />

100h<br />

anterior<br />

Novamente, caso a lista não esteja vazia, duas situações são possíveis<br />

e necessitam <strong>de</strong> tratamentos diferentes: A lista possui apenas um (1)<br />

nodo ou a lista possui mais <strong>de</strong> um nodo. Caso a lista possua apenas<br />

um nodo, basta guardar o conteúdo do elemento a ser eliminado,<br />

liberar o nodo e atribuir NULL a variável l.<br />

Já quando a lista possui mais <strong>de</strong> um nodo, a seguinte seqüência <strong>de</strong><br />

atualizações dos apontadores <strong>de</strong>ve ocorrer:<br />

1. anterior recebe o anterior <strong>de</strong> l;<br />

2. próximo recebe o próximo <strong>de</strong> l;<br />

3. próximo <strong>de</strong> anterior recebe próximo;<br />

4. Anterior <strong>de</strong> próximo recebe anterior;<br />

5. Libera o nodo apontado por l;<br />

6. Faz l apontar para próximo.<br />

Na Figura 15 essa seqüência é enumerada com os número <strong>de</strong> 1 a 6.<br />

A operação <strong>de</strong> remoção <strong>de</strong> um elemento arbitrário é semelhante à<br />

eliminação em uma lista circular simplesmente enca<strong>de</strong>ada. O nodo<br />

contendo o elemento procurado <strong>de</strong>ve ser localizado na lista por meio<br />

<strong>de</strong> uma busca seqüencial. Em caso <strong>de</strong> sucesso (se o elemento existir<br />

na lista), tanto os apontadores do nodo anterior ao eliminado quanto os<br />

do nodo posterior <strong>de</strong>vem ser atualizados também. A Figura 17<br />

apresenta a implementação <strong>de</strong>ssa operação. São consi<strong>de</strong>radas as<br />

seguintes situações:<br />

1. A lista está vazia;<br />

2. O elemento procurado não existe;<br />

3. Só existe um nodo na lista (a lista vai ficar vazia!);<br />

4. O nodo a ser eliminado é o nodo apontado por l.<br />

Página 79


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 80<br />

Figura 17: Operação <strong>de</strong> remoção <strong>de</strong> um elemento arbitrário<br />

Para imprimir a lista, implementamos duas operações:<br />

imprimeSentidoHorario e<br />

imprimeSentidoAntiHorario. A Figura 18 apresenta essas<br />

operações.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 18: Operações <strong>de</strong> Impressão da Lista.<br />

Por último, temos a operação pesquisaNaLista. Essa operação<br />

po<strong>de</strong> ser feita <strong>de</strong> forma idêntica à operação pesquisaNaLista<br />

para listas circulares simplesmente enca<strong>de</strong>adas (Figura 10). Assim,<br />

não iremos repetir seu código aqui. No entanto, esse código po<strong>de</strong> ser<br />

encontrado no módulo ListaDupla, implementado e<br />

disponibilizado no ambiente <strong>de</strong> aprendizado.<br />

Ativida<strong>de</strong>s<br />

2. Implementar uma operação para remover os elementos<br />

<strong>de</strong> uma lista cujo código é maior que 10.<br />

Implementar uma função removeEntrePares que<br />

remove os elementos que são pares da lista. Ou seja, o<br />

anterior e o próximo do elemento são elementos cujo<br />

código é um número par.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 81


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 82<br />

3.3 ÁRVORES BINÁRIAS<br />

Até o presente momento, focalizamos o estudo <strong>de</strong><br />

estruturas <strong>de</strong> dados enca<strong>de</strong>adas lineares ou<br />

seqüenciais. Nessas estruturas, existe a noção <strong>de</strong><br />

antecessor e próximo, para cada nodo. Nesta Seção<br />

iremos estudar uma nova estrutura enca<strong>de</strong>ada<br />

<strong>de</strong>nominada Árvore. Essas estruturas são chamadas<br />

assim pela semelhança <strong>de</strong> sua estrutura com as<br />

ramificações <strong>de</strong> uma árvore.<br />

Conceitualmente, uma árvore é um tipo especial <strong>de</strong> um mo<strong>de</strong>lo<br />

matemático <strong>de</strong>nominado grafo. Em termos <strong>de</strong> estrutura <strong>de</strong> dados,<br />

uma árvore é um conjunto <strong>de</strong> nodos ligados. Um <strong>de</strong>sses nodos é<br />

<strong>de</strong>nominado raiz. Os <strong>de</strong>mais nodos da árvore são acessados a partir<br />

do nodo raiz.<br />

Árvore Binária<br />

Uma árvore binária é um tipo especial <strong>de</strong> árvore que<br />

possui uma estrutura formada por:<br />

Um nodo raiz<br />

Uma sub árvore esquerda<br />

Uma sub árvore direita<br />

A Figura 20 apresenta uma árvore binária. O nodo contendo o valor<br />

10 é o nodo raiz. Esse nodo permite o acesso as subárvores esquerda e<br />

direita. Note que a <strong>de</strong>finição é recursiva. Assim como o nodo raiz, o<br />

nodo 5 também possui ligações para as suas subárvores esquerda e<br />

direita. Os nodos que não possuem nenhuma subárvore são chamados<br />

<strong>de</strong> nodos folha. Na Figura 20, os nodos 1, 3, 6, 8, 11, 14 e 20 são<br />

nodos folha. Um nodo po<strong>de</strong> também possuir apenas a subárvore<br />

esquerda ou a subárvore direita. Na Figura 20, o nodo 18 possui<br />

apenas a subárvore direita. Em árvores, usamos também a<br />

terminologia Filho e Pai para caracterizar os nodos. Um nodo pai em<br />

uma árvore binária po<strong>de</strong> possuir dois filhos, o filho esquerdo (raiz da<br />

subárvore esquerda) e o nodo direito (raiz da subárvore direita).<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


3.3.1 Árvore Binária <strong>de</strong> Pesquisa<br />

Uma árvore binária po<strong>de</strong> ser usada como uma estrutura altamente<br />

eficiente para a busca <strong>de</strong> informações. Essa estrutura chama-se árvore<br />

binária <strong>de</strong> pesquisa.<br />

Árvore Binária <strong>de</strong> Pesquisa<br />

Uma Árvore Binária <strong>de</strong> Pesquisa é uma Árvore Binária<br />

na qual:<br />

Os nodos possuem uma chave <strong>de</strong> busca única;<br />

Consi<strong>de</strong>rando a chave <strong>de</strong> busca, os elementos da<br />

sub-arvore esquerda são menores que o<br />

elemento do nodo raiz e os elementos da<br />

subárvore direita são maiores que a do nodo<br />

raiz.<br />

A árvore da Figura 20 é uma árvore <strong>de</strong> binária <strong>de</strong> pesquisa.<br />

sub-árvore<br />

esquerda<br />

5<br />

Nodo RAIZ<br />

2 7 12<br />

18<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

10<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

15<br />

sub-árvore<br />

direita<br />

1 3 6 8 11 14<br />

NULL<br />

20<br />

Figura 20: Árvore Binária <strong>de</strong> Pesquisa<br />

Note que todos os elementos da subárvore esquerda do nodo raiz são<br />

menores que 10 e todos os elementos da subárvore direita são maiores<br />

que 10. Essa regra é aplicável em todas as ramificações das<br />

subárvores. Por exemplo, a subárvore esquerda do nodo 5 só possui<br />

elementos menores que 5. Já a subárvore direita <strong>de</strong>sse nodo só possui<br />

elementos maiores que 5.<br />

Página 83


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 84<br />

Ativida<strong>de</strong>s<br />

1. Desenhe uma árvore binária <strong>de</strong> busca cuja<br />

seqüência <strong>de</strong> inserções <strong>de</strong> elementos foi: 30 10 20<br />

50 35 e 44.<br />

2. Desenhe uma árvore binária <strong>de</strong> pesquisa, em que<br />

as chaves são letras para a inserção da seqüência<br />

<strong>de</strong> letras que compõem o seu nome e seu<br />

sobrenome, excluindo as repetições e partindo <strong>de</strong><br />

uma árvore inicialmente vazia.<br />

3. Desenhe uma árvore resultante da retirada da 3ª e<br />

da 8ª letra da seqüência composta por seu nome +<br />

sobrenome.<br />

3.3.2 O TAD Árvore Binária <strong>de</strong> Pesquisa<br />

Para tornar a manipulação <strong>de</strong> árvores binárias <strong>de</strong> pesquisa mais<br />

simples, iremos <strong>de</strong>finir um tipo abstrato <strong>de</strong> dados, consi<strong>de</strong>rando suas<br />

operações primitivas. Essas operações são apresentadas na Figura 21.<br />

Figura 21: especificação do TAD Árvore.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


A inicialização da árvore leva essa árvore para o estado <strong>de</strong> vazia, sem<br />

nenhum nodo. Essa operação só <strong>de</strong>ve ser utilizada no início da<br />

operação da árvore. Após isto, uma árvore só se tornará vazia se todos<br />

os seus elementos forem excluídos.<br />

As operações <strong>de</strong> inserção <strong>de</strong>vem respeitar a regra básica da árvore<br />

binária <strong>de</strong> pesquisa: Toda subárvore à esquerda <strong>de</strong> um nodo N só<br />

possui elementos com chaves <strong>de</strong> busca menores que a do nodo N; e<br />

toda subárvore à direita <strong>de</strong> um nodo N só possui elementos com<br />

chaves <strong>de</strong> busca maiores que a do nodo N. Assim, ao inserir um<br />

elemento na lista, <strong>de</strong>ve-se <strong>de</strong>finir a posição em que ele será inserido,<br />

com base nessa regra.<br />

A operação <strong>de</strong> pesquisa visa encontrar um elemento contido na árvore<br />

dada, um valor <strong>de</strong> chave <strong>de</strong> busca. Essa pesquisa <strong>de</strong>ve se basear<br />

também nessa regra. Isso tornará a pesquisa extremamente eficiente se<br />

comparada com a pesquisa em uma lista seqüencial. Por exemplo,<br />

suponha que estejamos procurando o elemento 8 na árvore da Figura<br />

20. A pesquisa inicia-se pelo nodo raiz (10). As chaves são<br />

comparadas. Como 8 é menor que 10, sigo a pesquisa pela árvore<br />

esquerda e comparo 8 agora com o nodo (5). Dessa vez (8) é maior e<br />

agora sigo pela subárvore direita, comparando com o nodo (7). Mais<br />

uma vez, 8 é maior e sigo procurando na subárvore direita on<strong>de</strong><br />

finalmente o nodo 8 é encontrado.<br />

A remoção <strong>de</strong>ve seguir o mesmo princípio da pesquisa para localizar o<br />

nodo a ser removido. A remoção em si também <strong>de</strong>ve ser<br />

implementada <strong>de</strong> forma que a regra <strong>de</strong> árvores binárias <strong>de</strong> pesquisa<br />

não seja violada.<br />

3.3.3 Implementação do TAD árvore Binária <strong>de</strong> Pesquisa<br />

A Figura 22 apresenta a implementação da interface do TAD<br />

árvoreBinária.<br />

Figura 22: Interface do TAD Árvore Binária<br />

O nodo da árvore possui, como nas implementações anteriores, o<br />

campo <strong>de</strong> informação e o do tipo Elemento. Cada nodo da árvore<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 85


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 86<br />

possui também dois apontadores, esq e dir, que apontam<br />

respectivamente para as subárvores à esquerda e à direita do nodo.<br />

Além das operações já previstas no TAD, incluímos a operação<br />

imprimeOr<strong>de</strong>mCrescente.<br />

Quando <strong>de</strong>claramos uma variável do tipo Arvore, estamos <strong>de</strong>finindo<br />

uma variável capaz <strong>de</strong> armazenar um ponteiro para struct nodo.<br />

Usaremos essa variável para apontar para o nodo raiz da árvore. Se<br />

ainda não existe nenhum nodo na árvore, esse ponteiro <strong>de</strong>ve apontar<br />

para NULL. A operação <strong>de</strong> inicialização é idêntica às operações <strong>de</strong><br />

inicialização <strong>de</strong> listas enca<strong>de</strong>adas e atribui NULL ao ponteiro que<br />

aponta para a raiz da árvore. A Figura 23 apresenta está operação<br />

Figura 23: Implementação da operação <strong>de</strong> inicicalização da árvore.<br />

Recursivida<strong>de</strong><br />

Árvores são estruturas <strong>de</strong>finas recursivamente. Dessa<br />

forma, a recursivida<strong>de</strong> será um mecanismo<br />

fundamental para simplificar a implementação das<br />

operações sobre essas estruturas. Caso você não esteja<br />

se sentido seguro quanto à recursivida<strong>de</strong>, recomendo<br />

que volte ao material <strong>de</strong> programação II e estu<strong>de</strong> o<br />

assunto.<br />

Inserção em Árvores Binárias <strong>de</strong> Pesquisa<br />

A operação <strong>de</strong> inserção po<strong>de</strong> ser <strong>de</strong>scrita pelo seguinte algoritmo:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Esse algoritmo tenta inserir o elemento em uma subárvore ainda vazia.<br />

O algoritmo se inicia recebendo um ponteiro para o nodo raiz da<br />

árvore. Se o ponteiro não apontar para NULL, a árvore não estará vazia<br />

e “a raiz já está ocupada”. Assim, existem duas opções: Inserir o<br />

elemento à esquerda da raiz ou à direita. Essa <strong>de</strong>cisão é tomada<br />

comparando-se a chave <strong>de</strong> busca (código) do nodo a ser inserido com<br />

a chave <strong>de</strong> busca do nodo da raiz. Se a chave do nodo a ser inserido é<br />

menor que a do nodo raiz, o algoritmo tenta inserir na sub arvore à<br />

esquerda. Se for maior tenta-se inserir na subárvore à direita. Lembrese<br />

<strong>de</strong> que na árvore binária <strong>de</strong> pesquisa as chaves <strong>de</strong> busca são únicas.<br />

Ou seja, não po<strong>de</strong> haver dois ou mais nodos com a mesma chave.<br />

Assim se o código do nodo a ser inserido for igual ao código do nodo<br />

raiz, o algoritmo termina e não corre a inserção.<br />

Por exemplo, suponha a inserção <strong>de</strong> um elemento com código igual a<br />

4 na árvore da Figura 20. Nesse caso, o algoritmo se inicia<br />

comparando 4 com 10 (raiz da árvore). Como 4 é menor, a operação<br />

é novamente executada (recursivamente), agora tentando inserir o 4 na<br />

subárvore à esquerda da raiz. Esse processo se repete recursivamente<br />

até que a subárvore à direita do nodo 3 seja atingida. Essa subárvore é<br />

vazia. Conseqüentemente, o ponteiro dir do nodo 3 está apontando<br />

para NULL. O algoritmo então aloca o nodo para o elemento com<br />

código igual a 4 e atribui o en<strong>de</strong>reço <strong>de</strong>sse novo nodo ao ponteiro<br />

dir do nodo 3.<br />

A Figura 24 ilustra a Árvore da Figura 20, agora com o nodo 4<br />

inserido. A linha pontilhada mostra o “percurso” das chamadas<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 87


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 88<br />

recursivas até a inserção do elemento. A Figura 25 apresenta a<br />

implementação da função <strong>de</strong> inserção. Essa operação recebe como<br />

parâmetros um ponteiro para Árvore (*t) e o elemento a ser inserido<br />

(e).<br />

sub-árvore<br />

esquerda<br />

5<br />

Nodo RAIZ<br />

2 7 12<br />

18<br />

10<br />

15<br />

sub-árvore<br />

direita<br />

1 3 6 8 11 14<br />

NULL<br />

20<br />

4<br />

Figura 24: Inserção do elemento 4 na árvore.<br />

O primeiro if testa se o valor <strong>de</strong> *t é NULL. Nesse caso o elemento<br />

é inserido. Caso contrário, ocorrem chamadas recursivas da função<br />

insere, para a esquerda ou para a direita, conforme algoritmo. É ainda<br />

possível que o elemento já exista na árvore. Nesse caso a mensagem<br />

Já existe um nodo com esta chave! é impressa.<br />

As chamadas recursivas são feitas passando o en<strong>de</strong>reço dos<br />

apontadores: insere(&(*t)->esq,e); ou insere(&(*t)-<br />

>dir,e); Isso é necessário, pois o conteúdo <strong>de</strong>sses apontadores<br />

<strong>de</strong>verá ser alterado na inserção.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 25: TAD Arvore Binária: implementação da operação Insere.<br />

Figura 26: TAD Arvore Binária: implementação da operação Pesquisa<br />

Ativida<strong>de</strong>s<br />

4. Desenhar a inserção dos elementos 13, 0 e 16 na<br />

árvore da Figura 24.<br />

Pesquisa em Árvores Binárias <strong>de</strong> Pesquisa<br />

A operação pesquisa visa retornar um elemento contido na árvore<br />

t, sendo passado um valor <strong>de</strong> código (chave <strong>de</strong> busca). O Elemento<br />

encontrado <strong>de</strong>ve ser passado por meio do parâmetro ret, que é um<br />

ponteiro para Elemento. Essa operação também é implementada <strong>de</strong><br />

forma recursiva e <strong>de</strong>ve receber o en<strong>de</strong>reço da raiz da árvore. Como a<br />

árvore não sofre alterações na pesquisa, apenas o valor do en<strong>de</strong>reço do<br />

nodo raiz e (e das subárvores nas chamadas recursivas) é suficiente.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 89


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 90<br />

Eficiência da Pesquisa<br />

A eficiência da pesquisa em uma árvore binária <strong>de</strong><br />

pesquisa é a sua maior vantagem. Para localizar um<br />

elemento em uma árvore com N elementos e construída<br />

aleatoriamente, são necessárias aproximadamente log2<br />

N consultas. Para se ter um idéia do que isso representa,<br />

uma pesquisa seqüencial em uma lista linear contendo<br />

1,000,000 <strong>de</strong> elementos realiza em média 500,000<br />

consultas para encontrar um elemento. Em uma árvore<br />

essa mesma pesquisa gastaria log2 1,000,000 20<br />

consultas.<br />

Isso acontece porque, a cada verificação, meta<strong>de</strong> dos<br />

dados a serem pesquisados é <strong>de</strong>scartada<br />

(correspon<strong>de</strong>nte a subárvore à direita ou à esquerda do<br />

elemento verificado).<br />

A pesquisa só é <strong>de</strong>gradada quando a árvore é construída<br />

<strong>de</strong> tal forma que a sua estrutura se torna uma lista<br />

linear. Isso acontece, por exemplo, se os elementos da<br />

árvore forem inseridos em or<strong>de</strong>m, <strong>de</strong> acordo com a<br />

chave <strong>de</strong> busca. Por Exemplo, a construção <strong>de</strong> uma<br />

árvore com a seqüência 1, 2,3,20,30, 40,41,42, vai<br />

produzir uma lista linear.<br />

Remoção em Árvores Binárias <strong>de</strong> Pesquisa<br />

Para a remoção <strong>de</strong> um elemento da árvore <strong>de</strong>vemos consi<strong>de</strong>rar dois<br />

casos distintos:<br />

1. O elemento a ser removido está em um nodo que possui, no<br />

máximo, uma subárvore;<br />

Nesse caso, a remoção é mais simples, po<strong>de</strong>ndo ocorrer:<br />

a) quando esq do nodo aponta para NULL;<br />

b) quando dir do nodo aponta para NULL;<br />

c) quando esq e dir apontam para NULL.<br />

2. O elemento a ser removido está em um nodo que possui as<br />

duas subárvores.<br />

A Figura 27 apresenta a remoção do elemento 11 que não possui<br />

filhos, ou seja, não possui nem subárvore à direita nem à esquerda.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


12<br />

11 14<br />

15<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

18<br />

20<br />

Remoção do elemento 11<br />

12<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

14<br />

Figura 27: Removendo Elemento sem Filhos.<br />

Nesse caso a remoção é simples, bastando que o elemento seja<br />

encontrado e o nodo removido.<br />

12<br />

11 14<br />

15<br />

18<br />

20<br />

Remoção do elemento 18<br />

12<br />

11 14<br />

Figura 28: Removendo um Elemento com Apenas um Filho<br />

A Figura 28 apresenta a remoção do elemento 18, que possui<br />

subárvore à direita. Nesse caso, como o nodo não possui subárvore à<br />

esquerda, basta que o filho direito (20) ocupe o lugar do elemento 18.<br />

Isso po<strong>de</strong> ser realizado fazendo o apontador dir do nodo 15 apontar<br />

para o filho direito do nodo 18. Quando o nodo só possui subárvore à<br />

esquerda, essa operação ocorre <strong>de</strong> maneira simétrica. Ou seja, o nodo<br />

a ser eliminado é substituído por seu filho esquerdo.<br />

A Figura 29 apresenta agora a remoção <strong>de</strong> um nodo que possui as<br />

duas subárvores. Nessa situação, a remoção po<strong>de</strong> ser feita <strong>de</strong> duas<br />

maneiras: substituindo o elemento removido por seu sucessor ou<br />

substituindo o nodo removido por seu antecessor.<br />

Sucessor e antecessor <strong>de</strong> um nodo em uma árvore<br />

binária <strong>de</strong> busca<br />

Sucessor - Filho mais à esquerda <strong>de</strong> sua subárvore<br />

direita.<br />

Antecessor - Filho mais à direita <strong>de</strong> sua subárvore<br />

esquerda.<br />

15<br />

15<br />

18<br />

20<br />

20<br />

Página 91


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 92<br />

12<br />

11 14<br />

15<br />

18<br />

20<br />

Remoção do elemento 15<br />

Troca pelo sucessor<br />

Troca pelo antecessor<br />

12<br />

11 14<br />

Figura 29:Remoção <strong>de</strong> um Nodo que Possui as Duas Subárvores.<br />

O sucessor do nodo 15 é o nodo 18 e representa o elemento que<br />

suce<strong>de</strong> o 15, consi<strong>de</strong>rando a or<strong>de</strong>nação dos elementos na árvore, pela<br />

chave <strong>de</strong> busca. Consi<strong>de</strong>rando a árvore do exemplo, temos a seguinte<br />

or<strong>de</strong>m <strong>de</strong> sucessão: [11,12,14,15,18,20]. O antecessor, por outro lado,<br />

é o nodo que possui o elemento imediatamente maior que o elemento<br />

a ser removido. No caso, o antecessor <strong>de</strong> 15 é o elemento 14.<br />

11<br />

Por que sucessor e antecessor?<br />

Devemos substituir o nodo por seu sucessor ou seu<br />

antecessor para não violar a regra <strong>de</strong> formação da<br />

árvore binária <strong>de</strong> pesquisa, em que <strong>de</strong>vemos ter<br />

elementos menores à esquerda <strong>de</strong> cada nodo e<br />

elementos maiores à direita <strong>de</strong> cada nodo.<br />

Para implementar a remoção, primeiramente temos que localizar o<br />

nodo a ser removido. Para isso utilizamos recursivida<strong>de</strong>. A<br />

Figura 30 apresenta o início da operação <strong>de</strong> remoção. O elemento a ser<br />

eliminado é o elemento com chave <strong>de</strong> busca igual a código. A<br />

operação faz a busca pelo elemento a ser eliminado na árvore,<br />

posicionando t na variável ponteiro (*t) que aponta para o nodo a<br />

ser eliminado. Por exemplo, no caso da remoção do nodo 11, essa<br />

variável correspon<strong>de</strong> ao ponteiro esq do nodo 12.<br />

12<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

14<br />

18<br />

18<br />

20<br />

20


Figura 30: Início da Operação <strong>de</strong> Remoção: o Nodo é Localizado.<br />

Caso o nodo com o código procurado não exista, a pesquisa segue até<br />

achar um apontador que aponte para NULL. Enquanto isso não ocorre<br />

e o elemento não é encontrado, a busca segue ou para a subárvore à<br />

esquerda ou para a subárvore à direita. Quando o elemento é<br />

encontrado (o último else é executado) , a operação precisa verificar<br />

se o elemento a ser eliminado está no caso 1 (um nodo que possui, no<br />

máximo, uma subárvore) ou no caso 2 (em um nodo que possui duas<br />

subárvores) .<br />

Vamos discutir separadamente os casos 1 e 2.<br />

A Figura 31 apresenta o excerto <strong>de</strong> código <strong>de</strong> que trata o caso 1. Para<br />

esse caso, basta verificar que uma das subárvores é vazia. O Fato das<br />

duas serem vazias é indiferente. Optamos por testar primeiro se a<br />

subárvore à esquerda é vazia. Nesse caso, uma variável auxiliar (aux)<br />

é usada para guardar o valor do en<strong>de</strong>reço do nodo a ser eliminado. O<br />

ponteiro que antes apontava para o nodo a ser eliminado (t) passa a<br />

apontar para o nodo à direita. Caso a subárvore à esquerda não seja<br />

vazia, verificamos se a subárvore à direita é vazia. Nesse caso, o<br />

ponteiro t passa a apontar para o nodo à esquerda do nodo a ser<br />

eliminado. Em ambos os casos, o nodo eliminado é liberado por meio<br />

da função free.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 93


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 94<br />

Figura: 3.31: Remoção: é verificado se o nodo possui no máximo uma subárvore.<br />

Figura 32: Remoção quando o nodo possui as duas subárvores.<br />

A Figura 32 apresenta a implementação do caso 2 (o nodo possui as<br />

duas subárvores). Para facilitar essa operação, em vez <strong>de</strong> eliminarmos<br />

o nodo que contém o elemento a ser eliminado, o conteúdo do nodo a<br />

ser eliminado será substituído pelo seu antecessor e o nodo antecessor<br />

é que será eliminado. Esse procedimento é realizado pela operação<br />

antecessor, chamada pela função remove.<br />

Figura 33: Implementação da Operação Antecessor.<br />

A Figura 33 apresenta a implementação <strong>de</strong>ssa operação, que também<br />

utiliza recursivida<strong>de</strong> para a localização do nodo antecessor. Veja na<br />

Figura 32 que a chamada <strong>de</strong> antecessor passa:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


o en<strong>de</strong>reço do nodo a ser eliminado (*t), no lugar do<br />

parâmetro q;<br />

o ponteiro para subárvore à esquerda do nodo a ser eliminada<br />

((*t)->esq), no lugar do parâmetro r;<br />

Conforme a <strong>de</strong>finição <strong>de</strong> antecessor (pag. 76), a partir da raiz da<br />

subárvore à esquerda, são feitas chamadas recursivas passando sempre<br />

o ponteiro para a subárvore à direita, até que o nodo mais à direita seja<br />

encontrado. Quando isso ocorre, a recursivida<strong>de</strong> <strong>de</strong>termina a execução<br />

das seguintes ações:<br />

r<br />

1. Substituir o elemento do nodo a ser eliminado pelo elemento<br />

<strong>de</strong> seu antecessor : q->e=(*r)->e;<br />

2. Guardar o en<strong>de</strong>reço do antecessor em uma variável auxiliar:<br />

paux = *r;<br />

3. Fazer a referência para o antecessor receber o en<strong>de</strong>reço do<br />

filho esquerdo do antecessor: *r= (*r)->esq;<br />

4. Remover o nodo que possuía o antecessor: free(paux);<br />

q<br />

12<br />

11 14<br />

13<br />

15<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

18<br />

*t<br />

20<br />

antecessor(*t,&((*t)->esq));<br />

q<br />

12<br />

11 14<br />

13<br />

14<br />

r<br />

*r= (*r)->esq;<br />

paux<br />

18<br />

*t<br />

20<br />

q<br />

12<br />

11 14<br />

11<br />

13<br />

14<br />

r<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

18<br />

paux<br />

*t<br />

20<br />

antecessor(q,&(*r)->dir);<br />

..<br />

q->e=(*r)->e;<br />

paux = *r;<br />

q<br />

12<br />

13<br />

14<br />

r<br />

18<br />

free(paux);<br />

Figura 33: Remoção do elemento 15 da lista. O antecessor é o elemento 14.<br />

A Figura 34 ilustra a execução da operação antecessor <strong>de</strong>s<strong>de</strong> a sua<br />

chamada para eliminar o elemento 15. Nessa árvore, veja que o<br />

antecessor é o nodo 14, que possui subárvore à esquerda. Assim, ao<br />

*t<br />

20<br />

Página 95


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 96<br />

final da remoção, o ponteiro dir do nodo que contém o elemento 12<br />

vai ter que apontar para o nodo à esquerda <strong>de</strong> 14.<br />

Ativida<strong>de</strong>s<br />

5. Implementar a função sucessor, equivalente a<br />

antecessor usada na operação remove. Depois,<br />

modificar a operação remove para que o elemento a ser<br />

removido seja substituído por seu sucessor.<br />

Impressão em Árvores <strong>de</strong> Pesquisa Binária<br />

Para imprimir os elementos <strong>de</strong> uma árvore binária <strong>de</strong> busca, basta<br />

percorrer a árvore usando recursivida<strong>de</strong>. É possível, inclusive,<br />

imprimir os elementos da árvore em or<strong>de</strong>m, crescente ou <strong>de</strong>crescente.<br />

O Código da Figura 34 apresenta a implementação da impressão em<br />

or<strong>de</strong>m crescente.<br />

Figura 34: impressão dos elementos em or<strong>de</strong>m crescente.<br />

Essa operação visita todos os nodos da árvore, fazendo chamadas<br />

recursivas à esquerda e à direita <strong>de</strong> cada nodo. Quando um nodo folha<br />

à esquerda é encontrado:<br />

a recursivida<strong>de</strong> é interrompida;<br />

o elemento <strong>de</strong>sse nodo é impresso;<br />

a recursivida<strong>de</strong> é ativada, chamando o nodo à direita do nodo<br />

impresso;<br />

Essa forma <strong>de</strong> impressão baseia-se no caminhamento central, no qual<br />

os elementos serão visitados or<strong>de</strong>nadamente.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


12<br />

11 14<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

18<br />

20<br />

imprime(18)// esq<br />

imprime(12)//esq<br />

imprime(11)//esq<br />

fim da recursao<br />

printf(11)<br />

printf(12)<br />

imprime(14) // dir<br />

fim da recursao<br />

printf(14)<br />

printf(18)<br />

imprime(20)//dir<br />

fim da recursao<br />

printf(20)<br />

Figura 35: Seqüência <strong>de</strong> impressão para o Caminhamento Central.<br />

A Figura 35 ilustra a execução da operação <strong>de</strong> impressão utilizando<br />

caminhamento central. A ocorrências <strong>de</strong> imprime correspon<strong>de</strong>m à<br />

or<strong>de</strong>m das chamadas recursivas da operação imprimeElemento<br />

da Figura 34. Veja que essa representação é meramente algorítmica<br />

(não usamos ponteiros). O comando printf correspon<strong>de</strong> ao<br />

printf na implementação da Figura 34.<br />

Implementamos, além do TAD ArvoreBinaria, um módulo<br />

(principal) para teste. Não apresentamos esse módulo aqui, mas o<br />

mesmo se encontra disponível no material on-line.<br />

Análise da Árvore Binária <strong>de</strong> Pesquisa<br />

Conforme citamos no início <strong>de</strong>ssa seção, árvores<br />

binárias <strong>de</strong> pesquisa são excelentes estruturas para o<br />

armazenamento e a recuperação <strong>de</strong> informações. Isso<br />

se <strong>de</strong>ve à eficiência <strong>de</strong> suas operações <strong>de</strong> inserção,<br />

remoção e pesquisa. Já comentamos a eficiência da<br />

pesquisa. A inserção e a remoção eficientes tornam o<br />

custo <strong>de</strong> manutenção dos dados baixo. Ou seja, novos<br />

dados po<strong>de</strong>m ser inseridos e outros removidos sem<br />

comprometer a eficiência do sistema. Outra facilida<strong>de</strong> é<br />

a listagem or<strong>de</strong>nada dos dados (impressão), que nem<br />

sempre é possível em estruturas <strong>de</strong> pesquisa. Usando<br />

Hashing (Cap. 4) por exemplo, não é possível listar os<br />

dados or<strong>de</strong>nadamente.<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 97


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 98<br />

Ativida<strong>de</strong>s<br />

6. Implementar a operação imprime <strong>de</strong> forma que os<br />

elementos da árvore sejam impressos:<br />

em or<strong>de</strong>m <strong>de</strong>crescente<br />

na or<strong>de</strong>m em que foram inseridos<br />

Utilize o TAD Árvore Binária para implementar uma<br />

agenda telefônica <strong>de</strong> celular. Veja as funções <strong>de</strong> agenda<br />

existentes em um telefone celular e tente implementá-las.<br />

Enfim, terminamos este capítulo. Como é comum no<br />

estudo <strong>de</strong> estruturas <strong>de</strong> dados dinâmicas, este capítulo<br />

<strong>de</strong>ve ter exigido <strong>de</strong> você um gran<strong>de</strong> esforço <strong>de</strong><br />

concentração e <strong>de</strong> raciocínio lógico. Espero que tenha<br />

realizado todas as tarefas e estudado <strong>de</strong> forma<br />

complementar o livro texto, pois, somente assim, o<br />

estudo terá o efeito <strong>de</strong>sejado. Até o próximo capítulo!<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


4. PESQUISA EM MEMÓRIA PRIMÁRIA.<br />

Olá! Iremos neste Capítulo tratar do assunto<br />

pesquisa. A palavra pesquisa, aqui, tem o<br />

significado estrito <strong>de</strong> busca. Iremos buscar objetos<br />

<strong>de</strong> informação. Você certamente já pesquisou<br />

muitas coisas na sua vida: brinquedos, roupas,<br />

chaves, livros e pessoas estão entre as “coisas” que<br />

mais procuramos! Se você já fez isso, já <strong>de</strong>ve ter<br />

se aborrecido ao não encontrar (ou <strong>de</strong>morar muito<br />

para encontrar) o objeto ou pessoa que procura.<br />

Mas, ao <strong>de</strong>morar a encontrar um objeto, você já se<br />

perguntou se está procurando da maneira correta?<br />

Normalmente,, é difícil encontrar algo em um<br />

conjunto <strong>de</strong> elementos se não usarmos a estratégia<br />

<strong>de</strong> busca apropriada para o mo<strong>de</strong>lo <strong>de</strong> organização<br />

do conjunto? Neste Capítulo iremos discutir e<br />

apren<strong>de</strong>r sobre essas estratégias <strong>de</strong> busca e discutir<br />

aspectos da pesquisa <strong>de</strong> diferentes organizações do<br />

conjunto on<strong>de</strong> procuramos. Mão à obra então!<br />

A pesquisa em memória primária envolve a busca por objetos <strong>de</strong><br />

informação armazenados em estruturas <strong>de</strong> dados instanciadas na<br />

memória primária do computador. Não iremos tratar aqui da pesquisa<br />

em memória secundária, que envolve outras técnicas.<br />

Nosso estudo vai compreen<strong>de</strong>r a pesquisa simples, on<strong>de</strong> sabemos<br />

exatamente qual o objeto que queremos. Normalmente sabemos qual é<br />

esse objeto por meio <strong>de</strong> uma informação que o diferencia <strong>de</strong> todos os<br />

<strong>de</strong>mais objetos. Chamaremos essa informação <strong>de</strong> chave <strong>de</strong> pesquisa.<br />

Chave <strong>de</strong> Pesquisa<br />

A pesquisa em memória primária será feita com<br />

base em uma CHAVE DE PESQUISA. A chave<br />

<strong>de</strong> pesquisa po<strong>de</strong> ser um elemento <strong>de</strong> dado do<br />

objeto. Por exemplo, o RG <strong>de</strong> uma pessoa, o CNPJ<br />

<strong>de</strong> uma empresa ou o número <strong>de</strong> matrícula <strong>de</strong> um<br />

estudante. Po<strong>de</strong> ser também o nome <strong>de</strong> um país, ou<br />

o ISBN <strong>de</strong> um livro. O importante da chave <strong>de</strong><br />

pesquisa é que ela seja única, <strong>de</strong> forma a permitir<br />

que cada elemento do conjunto seja unicamente<br />

i<strong>de</strong>ntificável pela sua chave <strong>de</strong> pesquisa.<br />

A idéia básica da pesquisa, portanto, é: dado um conjunto <strong>de</strong> dados e<br />

uma chave, localizar um elemento nesse conjunto cujo valor da chave<br />

<strong>de</strong> pesquisa seja o mesmo da chave usada. A chave <strong>de</strong> pesquisa é<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 99


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 100<br />

escolhida e <strong>de</strong>finida pelo programador com base no problema<br />

específico que ele está tratando. Por exemple, em um conjunto <strong>de</strong><br />

dados que cataloga os computadores <strong>de</strong> uma empresa, a chave <strong>de</strong><br />

pesquisa po<strong>de</strong> ser o número <strong>de</strong> série do computador ou <strong>de</strong> seu<br />

patrimônio.<br />

O conjunto po<strong>de</strong> ser organizado e armazenado usando diferentes<br />

estruturas e tipos abstratos <strong>de</strong> dados: Vetores, listas enca<strong>de</strong>adas,<br />

árvores. Já estudamos no capítulo anterior o algoritmo <strong>de</strong> pesquisa em<br />

listas e em árvores binárias <strong>de</strong> pesquisa. Neste capítulo iremos analisar<br />

em maiores <strong>de</strong>talhes esses e outros algoritmos.<br />

Em uma pesquisa, o conceito <strong>de</strong> eficiência é muito importante. A<br />

eficiência po<strong>de</strong> ser medida pelo número <strong>de</strong> inspeções realizadas até<br />

encontrar o objeto procurado. Evi<strong>de</strong>ntemente, quanto menos inspeções<br />

forem feitas, mais eficiente será a pesquisa.<br />

Inspeção<br />

Uma inspeção consiste na operação <strong>de</strong> comparação<br />

da chave procurada com a chave <strong>de</strong> busca <strong>de</strong> um<br />

objeto, visando verificar se o objeto em questão é<br />

que procuramos.<br />

Iremos estudar algoritmos <strong>de</strong> pesquisa para as três seguintes situações:<br />

1. Os dados estão dispostos <strong>de</strong> forma <strong>de</strong>sor<strong>de</strong>nada e <strong>de</strong>sconhecida<br />

em uma lista linear (um vetor ou uma lista enca<strong>de</strong>ada). Esse<br />

cenário po<strong>de</strong> ser comparado a uma busca on<strong>de</strong> não há pistas<br />

sobre em que lugar o objeto está. Dessa forma, a única<br />

alternativa que temos é realizar uma busca exaustiva visitando<br />

todos os lugares até o objeto ser encontrado. A única forma <strong>de</strong><br />

<strong>de</strong>terminar se o objeto não está presente é inspecionar todos os<br />

lugares.<br />

2. Os dados estão dispostos <strong>de</strong> forma or<strong>de</strong>nada em uma lista<br />

linear (um vetor ou uma lista enca<strong>de</strong>ada). Nesse cenário, a<br />

busca é facilitada porque os objetos foram guardados usando<br />

alguma forma <strong>de</strong> or<strong>de</strong>nação baseada na chave <strong>de</strong> busca. Se os<br />

objetos forem armazenados, por exemplo, colocando os<br />

objetos em uma seqüência iniciando pelo menor e terminando<br />

pelo maior, a busca po<strong>de</strong>rá ser feita sem a necessida<strong>de</strong> <strong>de</strong><br />

inspecionar todos os objetos.<br />

3. Os dados estão dispostos em <strong>de</strong> forma não or<strong>de</strong>nada, porém as<br />

posições on<strong>de</strong> os dados estão localizados po<strong>de</strong>m ser<br />

calculadas usando a chave <strong>de</strong> pesquisa. Essa situação é a que<br />

acontece nas chamadas tabelas Hash on<strong>de</strong> os elementos são<br />

inseridos em posições calculadas a partir <strong>de</strong> sua chave <strong>de</strong><br />

pesquisa.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Iremos estudar a aplicação <strong>de</strong> três métodos <strong>de</strong> or<strong>de</strong>nação aplicáveis a<br />

esses cenários. Os métodos consistem em Pesquisa Seqüencial,<br />

Pesquisa Binária e Hashing. As seções a seguir <strong>de</strong>talham esses<br />

métodos.<br />

Ativida<strong>de</strong>s<br />

Suponha um baralho contendo 40 cartas todas<br />

diferentes, distribuídas <strong>de</strong> forma totalmente aleatória.<br />

Pense em uma carta específica e responda:<br />

a) Qual a propobilida<strong>de</strong> da primeira carta do<br />

baralho ser a que você pensou?<br />

b) Qual a propobilida<strong>de</strong> da segunda carta ser a<br />

que você pensou? E da terceira?<br />

c) Qual a probabilida<strong>de</strong> <strong>de</strong> encontrar a carta após<br />

4 tentativas?<br />

d) Em média quantas cartas terão que ser<br />

inspecionadas para você encontrar a carta que<br />

pensou?<br />

4.1 PESQUISA SEQÜENCIAL<br />

A pesquisa seqüencial é realizada inspecionando-se o conjunto <strong>de</strong><br />

objeto seqüencialmente, do início para o fim ou vice-versa. Duas<br />

situações po<strong>de</strong>m ocorrer:<br />

Pesquisa com sucesso: O elemento é encontrado em uma das<br />

posições do conjunto<br />

Pesquisa sem sucesso: O conjunto e completamente percorrido e o<br />

elemento não é encontrado.<br />

Normalmente fazemos dois testes: verificamos se o elemento<br />

procurado correspon<strong>de</strong> ao objeto inspecionado e verificamos se o<br />

limite do conjunto <strong>de</strong> objetos foi atingido. Por exemplo, o código<br />

abaixo faz isso:<br />

Para melhorar essa implementação, iremos apresentar um algoritmo<br />

<strong>de</strong> pesquisa seqüencial que utiliza uma sentinela. O uso <strong>de</strong> sentinelas<br />

torna a pesquisa seqüencial mais eficiente.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 101


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 102<br />

Sentinela<br />

Sentinela é um valor conhecido que é inserido em<br />

uma posição específica <strong>de</strong> um arranjo ou <strong>de</strong> uma<br />

lista. O objetivo é usarmos o valor do sentinela<br />

para pararmos um processamento ou uma busca.<br />

Por exemplo, quando buscamos um valor x em um<br />

vetor que vai <strong>de</strong> 0 a n, e inserimos o valor x como<br />

sentinela na posição n+1. Assim, a busca po<strong>de</strong> ser<br />

feita até encontrarmos o valor do sentinela, sem a<br />

necessida<strong>de</strong> <strong>de</strong> testarmos se chegamos até o limite<br />

do arranjo. O código abaixo ilustra este cenário:<br />

// buscando x<br />

v[n+1] = x; // atribui o sentinela<br />

i = 0;<br />

while (v[i] != x) {<br />

}<br />

i++;<br />

4.1.1 Implementação da Pesquisa Seqüencial<br />

Continuaremos a apresentar nossas implementações respeitando o<br />

conceito <strong>de</strong> TAD. Neste capitulo, iremos usar o TAD Tabela, que<br />

nada mais é conjunto do tipo Elemento (já visto em listas enca<strong>de</strong>adas).<br />

O tipo tabela é apresentado na Figura 1.<br />

Figura 1: Interface do TAD Tabela<br />

As implementações das operações inicializaTabela, tabelaCheia e<br />

insereElemento são apresentadas na Figura 2. A Figura 3 apresenta a<br />

implementação da operação <strong>de</strong> pesquisaSeqElemento. Essa operação<br />

possui como parâmetro a tabela on<strong>de</strong> pesquisar e o valor da chave<br />

utilizada para a pesquisa. A função retorna -1 em caso <strong>de</strong> pesquisa<br />

sem sucesso e o índice da posição do elemento procurado, em caso <strong>de</strong><br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


pesquisa sem sucesso. Note que foi usado o sentinela (inserido na<br />

posição n+1 da tabela).<br />

Figura 2: Implementação das operações do TAD Tabela<br />

Figura 3: Implementação da operação pesquisaSequencial<br />

4.1.2 Tempo <strong>de</strong> execução <strong>de</strong> algoritmos<br />

Os métodos <strong>de</strong> pesquisa são um bom tópico em nosso curso para<br />

introduzirmos algumas noções sobre <strong>de</strong>sempenho <strong>de</strong> algoritmos. Para<br />

iniciar esta discussão, vamos nos reportar a uma pergunta? É possível<br />

<strong>de</strong>terminar quanto tempo à operação <strong>de</strong> pesquisa seqüencial leva<br />

para encontrar um elemento?<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 103


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 104<br />

Existem importantes elementos estão envolvidos nesta pergunta. A<br />

saber:<br />

1. A Máquina que será usada para executar o programa.<br />

2. O tamanho total dos dados<br />

Evi<strong>de</strong>ntemente, o tempo <strong>de</strong> uma pesquisa vai mudar <strong>de</strong>pen<strong>de</strong>ndo do<br />

computador que for utilizado. Quanto mais rápido o computador, mais<br />

rápida será a pesquisa. A mudança <strong>de</strong> <strong>de</strong>sempenho <strong>de</strong> um computador<br />

para outro po<strong>de</strong> ser expresso por uma constante C. Assim diremos que<br />

o tempo gasto por um programa para executar po<strong>de</strong> ser expresso por<br />

uma função.<br />

T f(n).C<br />

Em que C é a constante associada ao computador. n, por sua vez, é o<br />

tamanho da entrada <strong>de</strong> Dados. Ou seja, o tempo <strong>de</strong> execução da<br />

função <strong>de</strong> pesquisa é uma função do tamanho da entrada <strong>de</strong><br />

dados.<br />

Função <strong>de</strong> complexida<strong>de</strong> <strong>de</strong> tempo<br />

Quando analisamos o tempo <strong>de</strong> execução é comum<br />

<strong>de</strong>ixarmos a constante C <strong>de</strong> lado e nos concentramos na<br />

função f(n). Essa função é importante, pois ela nos dá<br />

uma medida <strong>de</strong> tempo <strong>de</strong> execução in<strong>de</strong>pen<strong>de</strong>nte do<br />

computador que será utilizado para executar a operação.<br />

A função f(n) é <strong>de</strong>nominada <strong>de</strong> função <strong>de</strong> complexida<strong>de</strong><br />

<strong>de</strong> tempo.<br />

Um outro aspecto importante na análise <strong>de</strong> <strong>de</strong>sempenho <strong>de</strong> algoritmos<br />

são os diferentes casos ou cenários <strong>de</strong> execução possíveis. São<br />

geralmente consi<strong>de</strong>rados os seguintes casos:<br />

Melhor Caso: Compreen<strong>de</strong> a melhor situação para o<br />

algoritmo. A situação em que a resposta será obtida <strong>de</strong> forma<br />

mais rápida.<br />

Caso Médio: Compreen<strong>de</strong> a média <strong>de</strong> todos os casos<br />

consi<strong>de</strong>rando as distribuições <strong>de</strong> probabilida<strong>de</strong> típicas para os<br />

dados em questão.<br />

Pior Caso: Compreen<strong>de</strong> a pior situação para o algoritmo em<br />

questão.<br />

Po<strong>de</strong>mos <strong>de</strong>terminar a função <strong>de</strong> complexida<strong>de</strong> <strong>de</strong> tempo para estes<br />

três casos para o algoritmo <strong>de</strong> pesquisa seqüencial:<br />

Melhor caso: f(n) = 1, ocorre quando o elemento procurado é o<br />

primeiro a ser inspecionado.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Pior caso: f(n) = n, ocorre quando o elemento procurado é o último<br />

elemento do conjunto a ser inspecionado.<br />

Caso médio: f(n) = (n+1)/2<br />

O caso médio é obtido da seguinte forma: seja x o elemento procurado<br />

e C = {c1,c2,c3..cn}, o conjunto <strong>de</strong> elementos a ser pesquisado.<br />

Consi<strong>de</strong>ra-se para este cálculo que a possibilida<strong>de</strong> <strong>de</strong> encontrar o<br />

elemento procurado é igualmente provável para qualquer posição do<br />

conjunto <strong>de</strong> elementos pesquisado. Ou seja, P(x=c1) = P(x=c2) = ...<br />

P(x=cn) = 1/n.<br />

Temos ainda que, para encontrar o elemento procurado na i-ésima<br />

posição do conjunto, são necessárias i inspeções. Por exemplo, para<br />

encontrar o elemento na primeira posição é necessária uma<br />

comparação. Para encontrar o elemento na segunda posição são<br />

necessárias duas comparações. E assim por diante.<br />

Sendo assim, temos:<br />

f(n) = 1 * P(x=c1) +2 * P(x=c2)+3* P(x=c3) + .. + n* P(x=cn) f(n) = 1 * 1/n + 2 * 1/n + 3 * 1/n + ... + n * 1/n<br />

f(n) = (1+2+3+...+n) *1/n<br />

f(n) = n*(n+1)/2 *1/n<br />

f(n) = (n+1)/2<br />

Ativida<strong>de</strong>s<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

1. Desenvolva um programa utilizando o TAD<br />

Tabela que meça o tempo <strong>de</strong> execução da pesquisa<br />

seqüencial. Altere o código da tabela para aceitar<br />

um conjunto <strong>de</strong> até 100.000 itens. Pesquise por<br />

alternativas para medir o tempo <strong>de</strong> execução <strong>de</strong><br />

um programa. Planeje sua estratégia para medir o<br />

tempo.<br />

2. No estudo da pesquisa seqüencial, a busca não<br />

leva em conta a organização dos dados no<br />

conjunto. Caso os dados se encontrem or<strong>de</strong>nados,<br />

por exemplo, em or<strong>de</strong>m crescente, a pesquisa<br />

seqüencial po<strong>de</strong> tirar partido <strong>de</strong>sta or<strong>de</strong>nação<br />

interrompendo a busca ao encontrar um elemento<br />

maior que o que foi procurado. Implemente um<br />

nova operação no TAD Tabela que realize esta<br />

pesquisa consi<strong>de</strong>rando que os dados estão<br />

or<strong>de</strong>nados em or<strong>de</strong>m crescente. Para testar esta<br />

operação os dados <strong>de</strong>vem ser inseridos or<strong>de</strong>nadamente<br />

na tabela.<br />

Página 105


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 106<br />

4.2 PESQUISA BINÁRIA<br />

Caso o conjunto <strong>de</strong> dados <strong>de</strong> pesquisa esteja or<strong>de</strong>nado, po<strong>de</strong>mos<br />

utilizar a pesquisa binária para encontrar elementos no conjunto. A<br />

Pesquisa binária consiste em uma técnica do tipo Dividir para<br />

Conquistar (Divi<strong>de</strong> to Conquer), na qual o conjunto <strong>de</strong> dados<br />

pesquisado é sistematicamente reduzido à meta<strong>de</strong> a cada inspeção<br />

realizada.<br />

Figura 4: Divisão dos dados na Pesquisa Binária<br />

A Figura 4 representa o método <strong>de</strong> divisão dos dados que ocorre na<br />

pesquisa binária. O quadro inteiro representa o conjunto completo dos<br />

dados. As inspeções são feitas no elemento central dos dados. Se o<br />

elemento inspecionado é menor que procurado, o algoritmo <strong>de</strong>scarta<br />

todos os elementos que são maiores que o elemento inspecionado. Ou<br />

seja, a meta<strong>de</strong> dos dados é <strong>de</strong>scartada (parte amarela). O algoritmo<br />

segue o mesmo procedimento agora inspecionando o elemento central<br />

dos elementos que sobraram, até encontrar o elemento ou não<br />

restarem mais elementos a serem inspecionados. Utilizando este<br />

método, a pesquisa binária consegue localizar o elemento <strong>de</strong>sejado<br />

com <strong>de</strong>sempenho muito melhor que a pesquisa seqüencial. As divisões<br />

sucessivas levam a localização <strong>de</strong> elemento em tempo logarítmico,<br />

conforme veremos a seguir.<br />

Vejamos agora a pesquisa binária usando como exemplo um vetor<br />

or<strong>de</strong>nado <strong>de</strong> elementos a seguir:<br />

0 1 2 3 4 5 6 7 8<br />

A D E F G J M N O<br />

Suponha que o elemento procurado seja o ‘D’.<br />

Suponha também que os limites inferior e superior do vetor sejam<br />

<strong>de</strong>notados pelas variáveis inicio e fim.<br />

No começo da pesquisa inicio = 0 e fim = 8<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


A pesquisa é iniciada inspecionando o elemento central do Vetor. Este<br />

elemento <strong>de</strong>terminado pela média entre inicio e fim:<br />

3. meio = (inicio+fim)/2 = 4<br />

Para primeira inspeção meio = 4; Como ‘D’ é menor que ‘G’, toda a<br />

meta<strong>de</strong> superior do vetor é <strong>de</strong>scartada. No algoritmo este <strong>de</strong>scarte é<br />

feito recalculando o valor <strong>de</strong> fim:<br />

4. fim = meio-1 = 3<br />

O valor <strong>de</strong> meio é recalculado agora para <strong>de</strong>terminar o meio da<br />

meta<strong>de</strong> inferior:<br />

5. meio = (inicio+fim)/2 = 1<br />

Agora, com meio = 1, o item procurado é encontrado.<br />

O algoritmo completo da pesquisa binária é mostrado na<br />

implementação da operação pesquisaBinária para o TAD Tabela<br />

(Figura 5).<br />

Figura 5: Implementação da operação pesquisaBinária<br />

Conforme já mencionado, a pesquisa binária apresenta<br />

comportamento logarítmico. A Função <strong>de</strong> complexida<strong>de</strong> <strong>de</strong> tempo é<br />

dada por:<br />

f(n) = log2(n) Para ilustrar o que significa esta eficiência logarítmica, suponha que<br />

você esteja procurando registros em uma tabela com 1.000.000 <strong>de</strong><br />

registros. Se você utiliza a pesquisa seqüencial, vai levar em média<br />

(n+1)/2 = ~ 500.000 inspeções para cada pesquisa. Utilizando a<br />

pesquisa binária, cada pesquisa vai ter um custo <strong>de</strong> log2 1.000.000 = ~<br />

20.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 107


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 108<br />

Ou seja, usando pesquisa seqüencial realiza-se em media 500.000<br />

inspeções para encontrar um elemento; ao passo que com a pesquisa<br />

binária serão necessárias, em média, apenas 20 (vinte) inspeções!<br />

Ativida<strong>de</strong>s<br />

1. Desenvolva um programa utilizando o TAD<br />

Tabela que meça o tempo <strong>de</strong> execução da pesquisa<br />

seqüencial. Altere o código da tabela para aceitar<br />

um conjunto <strong>de</strong> até 100.000 itens. Pesquise por<br />

alternativas para medir o tempo <strong>de</strong> execução <strong>de</strong><br />

um programa. Planeje sua estratégia para medir o<br />

tempo.<br />

2. Implemente uma versão recursiva da pesquisa<br />

binária.<br />

4.3 TABELAS HASH<br />

Até o momento, discutimos métodos <strong>de</strong> pesquisa baseados na<br />

comparação explícita da chave <strong>de</strong> busca. Nesses métodos, se<br />

pesquisamos algum objeto com chave <strong>de</strong> pesquisa igual a X,<br />

procuramos até encontrar o objeto com o valor <strong>de</strong> i<strong>de</strong>ntificador igual a<br />

X. Nesta seção iremos estudar uma abordagem completamente<br />

diferente. Trata-se das tabelas Hash, método também conhecido como<br />

espalhamento.<br />

Embora diferente, o método <strong>de</strong> espalhamento é bastante simples e<br />

intuitivo. A fixação dos conceitos <strong>de</strong> en<strong>de</strong>reço e função hash vai<br />

facilitar o entendimento do método.<br />

Tabela Hash: O método <strong>de</strong> espalhamento permite o<br />

armazenamento e recuperação <strong>de</strong> dados em<br />

estruturas on<strong>de</strong> cada posição possui um índice<br />

como, por exemplo, um vetor. Chamaremos estas<br />

estruturas simplesmente <strong>de</strong> tabela hash.<br />

En<strong>de</strong>reço: Correspon<strong>de</strong> ao valor <strong>de</strong> um índice na<br />

tabela. Os en<strong>de</strong>reços irão variar <strong>de</strong> 0 até N-1, on<strong>de</strong><br />

N é o tamanho máximo da tabela hash.<br />

Função hash: é uma função aritmética que tem<br />

como entrada uma chave e que calcula um valor<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


inteiro <strong>de</strong> um en<strong>de</strong>reço válido na tabela Hash.<br />

A função a seguir é um exemplo <strong>de</strong> função hash.<br />

int h (char * chave){<br />

int tam, i,soma=0;<br />

tam = strlen(chave);<br />

for(i=0;i


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 110<br />

O princípio básico do método <strong>de</strong> espalhamento é a geração <strong>de</strong><br />

en<strong>de</strong>reços <strong>de</strong>ntro da tabela por meio da função Hash. Esta geração<br />

ocorre nas seguintes situações:<br />

(1) Inserção <strong>de</strong> um registro: Para inserir um registro, a chave (que<br />

<strong>de</strong>ve se única) é usada para calcular o en<strong>de</strong>reço <strong>de</strong><br />

armazenamento do registro.<br />

(2) Recuperação <strong>de</strong> um registro: Para recuperar um registro<br />

armazenado, com base em um valor chave <strong>de</strong> pesquisa. Neste<br />

caso a chave informada é usada para <strong>de</strong>terminar o possível<br />

en<strong>de</strong>reço <strong>de</strong> armazenamento do registro procurado.<br />

Figura 6: A função hash <strong>de</strong>termina o índice com base na chave <strong>de</strong> pesquisa.<br />

Um problema que fatalmente ocorre em funções Hash é o cálculo <strong>de</strong><br />

um mesmo en<strong>de</strong>reço para dois valores <strong>de</strong> chave diferentes. Este<br />

problema é chamado <strong>de</strong> colisão.<br />

Colisão<br />

A colisão em tabelas hash ocorre quando o mesmo<br />

en<strong>de</strong>reço na tabela é calculado para diferentes<br />

valores <strong>de</strong> chave. Por exemplo, a função Hash<br />

int h (char * chave){<br />

int tam, i,soma=0;<br />

tam = strlen(chave);<br />

for(i=0;i


A colisão não po<strong>de</strong> ser evitada, mas <strong>de</strong>ve ser tratada. A seguir<br />

veremos como tratar colisões em implementações <strong>de</strong> tabelas Hash.<br />

4.3.1 Operações <strong>de</strong> Inserção e Pesquisa em Tabelas Hash<br />

Veremos como é feita a inserção e a pesquisa em uma tabela Hash.<br />

Veremos primeiro uma versão sem tratamento <strong>de</strong> colisão.<br />

Implementaremos estas funções como operações do TAD tabelaHash.<br />

Figura 7: TAD TabelaHash sem tratamento <strong>de</strong> colisão<br />

A Figura 7 apresenta o TAD TabelaHash sem tratamento <strong>de</strong> colisão.<br />

A tabela utilizada consiste <strong>de</strong> um vetor <strong>de</strong> registros do tipo<br />

Elemento. Apenas lembrando, o tipo Elemento possui os seguintes<br />

campos:<br />

char nome[20];<br />

int codigo;<br />

char telefone[10];<br />

O campo nome será usado como chave <strong>de</strong> pesquisa. Assim, fica<br />

convencionado que os registros armazenados na tabela não<br />

po<strong>de</strong>rão ter o mesmo nome.<br />

Usaremos o campo codigo para <strong>de</strong>terminar se um en<strong>de</strong>reço da<br />

tabela está ocupado ou não. Para tanto, na inicialização da tabela,<br />

atribuiremos o valor -1 ao código. O valor -1 no campo código<br />

indicará que o en<strong>de</strong>reço está <strong>de</strong>socupado.<br />

O código da operação inicializaTabela é mostrado na Figura 8.<br />

Conforme po<strong>de</strong> ser observado, a tabela possui também um contador n<br />

que mantém o número <strong>de</strong> elementos da tabela. Implementamos<br />

também uma função lógica chamada <strong>de</strong> en<strong>de</strong>recoOcupado, que<br />

retorna TRUE se um en<strong>de</strong>reço (end) estiver ocupado e FALSE, caso<br />

contrário. Esta função é apresentada na Figura 8.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 111


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 112<br />

Figura 8: Inicialização da Tabela Hash e função en<strong>de</strong>recoOcupado.<br />

A função Hash utilizada é baseada na função h, já apresentada. No<br />

entanto, para calcular o valor da soma, cada caractere foi multiplicado<br />

pela sua posição (i+1), evitando a colisão <strong>de</strong> nomes com mesmo<br />

conjunto <strong>de</strong> caracteres. A Figura 9 apresenta esta implementação.<br />

Figura 9: implementação da função hash<br />

A inserção <strong>de</strong> elementos na tabela segue o seguinte algoritmo:<br />

A implementação da operação <strong>de</strong> inserção é mostrada na Figura 10.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Figura 10: Implementação da operação insereElemento.<br />

Note que, se o en<strong>de</strong>reço calculado já estiver ocupado, ocorre uma<br />

colisão que não é tratada. Um aviso <strong>de</strong> colisão é enviado ao usuário e<br />

a inserção do novo elemento simplesmente não ocorre.<br />

Conforme já mencionado, colisões em tabelas Hash são inevitáveis,<br />

mesmo para uma tabela 10 vezes maior que o número <strong>de</strong> registros a<br />

ser armazenado, a probabilida<strong>de</strong> <strong>de</strong> colisões é ainda muito gran<strong>de</strong>.<br />

Este comportamento é ilustrado pelo paradoxo do aniversário.<br />

Paradoxo do aniversário: Se tomarmos um grupo<br />

<strong>de</strong> mais <strong>de</strong> 25 pessoas <strong>de</strong> forma totalmente aleatório,<br />

a probabilida<strong>de</strong> <strong>de</strong> que haja entre estas pessoas pelo<br />

menos duas que fazem aniversário na mesma data<br />

do ano é maior que 50%. Esta medida é<br />

curiosamente verda<strong>de</strong>ira, consi<strong>de</strong>rando-se que a data<br />

<strong>de</strong> aniversário <strong>de</strong> uma pessoa é uma variável<br />

aleatória, estas datas <strong>de</strong>veriam ser uniformemente<br />

distribuídas entre os dias do ano.<br />

O paradoxo do aniversário está associado com tabelas Hash.<br />

Consi<strong>de</strong>re uma tabela Hash com 365 en<strong>de</strong>reços disponíveis. Cada<br />

en<strong>de</strong>reço correspon<strong>de</strong> a um dia do ano. Ao inserirmos 25 registros<br />

nesta tabela, usando uma função h totalmente aleatória, a chance <strong>de</strong><br />

haver uma colisão é <strong>de</strong> 50%.<br />

Na pesquisa por um elemento, um valor <strong>de</strong> chave <strong>de</strong>ve ser informado.<br />

Com base neste valor o en<strong>de</strong>reço é calculado. Se o en<strong>de</strong>reço estiver<br />

ocupado, o valor do campo nome do elemento é comparado com a<br />

chave <strong>de</strong> pesquisa para verificar se o elemento que lá está é realmente<br />

o elemento procurado. Neste caso o valor <strong>de</strong> end é retornado. Caso<br />

contrário o elemento procurado não existe na tabela. A operação <strong>de</strong><br />

pesquisa é apresentada na Figura 11.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 113


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 114<br />

Figura 11: Implementação da operação pesquisa.<br />

4.3.2 Tratamento <strong>de</strong> Colisões<br />

Na implementação que realizamos até agora, quando uma colisão<br />

ocorre, o dado simplesmente não é inserido. Evi<strong>de</strong>ntemente este não é<br />

o comportamento esperado <strong>de</strong> nenhum sistema <strong>de</strong> armazenamento <strong>de</strong><br />

informações. Assim, ao invés <strong>de</strong> simplesmente abortar a inserção,<br />

iremos tratar o evento da colisão permitindo a inserção <strong>de</strong> vários<br />

elementos com chaves diferentes que mapeiam para o mesmo<br />

en<strong>de</strong>reço.<br />

Conforme observado na TAD TabelaHash apresentado, na inserção,<br />

quando o en<strong>de</strong>reço vazio é obtido, o elemento é inserido no mesmo.<br />

A tabela itens é um vetor <strong>de</strong> Elemento e cada en<strong>de</strong>reço se refere<br />

a apenas um elemento.<br />

Veremos duas formas <strong>de</strong> tratar a colisão:<br />

(1) Com listas enca<strong>de</strong>adas<br />

(2) Com en<strong>de</strong>reçamento aberto<br />

No tratamento <strong>de</strong> colisões com listas enca<strong>de</strong>adas, a estrutura da tabela<br />

e alterada. Ao invés <strong>de</strong> uma tabela <strong>de</strong> registros é <strong>de</strong>finida uma tabela<br />

<strong>de</strong> listas enca<strong>de</strong>adas, on<strong>de</strong> cada lista enca<strong>de</strong>ada será o conjunto <strong>de</strong><br />

registros inseridos cujas chaves <strong>de</strong> pesquisa foram mapeadas para o<br />

mesmo en<strong>de</strong>reço. A Figura 12 ilustra esta abordagem.<br />

Figura 12: Tratamento <strong>de</strong> Colisão com Listas Enca<strong>de</strong>adas<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


A Tabela é formada <strong>de</strong> um vetor <strong>de</strong> ponteiros para N listas<br />

enca<strong>de</strong>adas. Agora, na inserção, o en<strong>de</strong>reço end é calculado e o<br />

elemento é inserido na lista enca<strong>de</strong>ada apontada pelo ponteiro da<br />

posição end.<br />

A Figura 13 apresenta a implementação da interface do TAD<br />

tabelaHash com com tratamento <strong>de</strong> colisão por meio <strong>de</strong> lista<br />

enca<strong>de</strong>ada. Este TAD utiliza como cliente o tadLista implementado no<br />

Capítulo 3. A única modificação neste Tad foi na operação pesquisa<br />

que agora utiliza o campo nome do registro Elemento como chave e<br />

recebe a string chave como parâmetro para realizar a pesquisa. O<br />

TadLista é reapresentado na Figura 14.<br />

Figura: 13: Interface do TAD Tabela Hash<br />

Figura 14: Interface do TAD Lista Enca<strong>de</strong>ada<br />

A Figura 15 apresenta a implementação das operações do TAD<br />

TabelaHash.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 115


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 116<br />

Figura 15: operações inicializaTabela, insereElemento e pesquisaElemento<br />

4.3.3 Tratamento <strong>de</strong> Colisão usando en<strong>de</strong>reçamento Aberto<br />

Outra abordagem para tratar as colisões é o uso <strong>de</strong> en<strong>de</strong>reçamento<br />

aberto (open addressing). Nessa abordagem, a estrutura <strong>de</strong> dados da<br />

tabela é a mesma originalmente estabelecida (um vetor <strong>de</strong> registros).<br />

A técnica <strong>de</strong> en<strong>de</strong>reçamento aberto consiste nos seguintes<br />

procedimentos:<br />

Inserção:<br />

(1) Calcular o en<strong>de</strong>reço usando a função Hash para o valor <strong>de</strong><br />

chave do registro;<br />

(2) Se o en<strong>de</strong>reço estiver ocupado, procurar nas posições vizinhas<br />

ao en<strong>de</strong>reço calculado uma posição vazia.<br />

Pesquisa:<br />

(1) Calcular o en<strong>de</strong>reço usando a função Hash e a chave <strong>de</strong><br />

pesquisa fornecida;<br />

(2) Procurar, a partir do en<strong>de</strong>reço até:<br />

a. Encontrar o elemento procurado (pesquisa com<br />

sucesso);<br />

b. Encontrar uma posição vazia ou o fim da tabela<br />

(pesquisa sem sucesso).<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Ativida<strong>de</strong>s<br />

8. Modificar a implementação inicial <strong>de</strong> Hash (sem<br />

tratamento <strong>de</strong> colisão) para suportar colisões com a técnica<br />

<strong>de</strong> en<strong>de</strong>reçamento aberto.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 117


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 118<br />

5. ORDENAÇÃO EM MEMÓRIA PRIMÁRIA.<br />

Olá. Neste capítulo chegaremos ao fim <strong>de</strong> nosso<br />

estudo na disciplina <strong>de</strong> TPA. Reservei para a<br />

última etapa o tema Or<strong>de</strong>nação. A or<strong>de</strong>nação em<br />

computação reúne um conjunto <strong>de</strong> algoritmos que<br />

permite or<strong>de</strong>nar um conjunto <strong>de</strong> dados segundo<br />

uma <strong>de</strong>terminada chave <strong>de</strong> or<strong>de</strong>nação. Neste<br />

capítulo iremos estudar alguns <strong>de</strong>sses principais<br />

algoritmos. Se você vasculhar a Internet vai<br />

encontrar a maioria <strong>de</strong>sses algoritmos<br />

implementados. Assim você po<strong>de</strong>rá se perguntar:<br />

para que estudamos esses mecanismos se as suas<br />

implementações já estão prontas? E mais, porque<br />

estudar vários métodos <strong>de</strong> or<strong>de</strong>nação se existe um<br />

particularmente que é mais rápido que os outros?<br />

A resposta é simples: Porque estes algoritmos e<br />

suas variações reúnem técnicas <strong>de</strong> programação<br />

que são <strong>de</strong> muita valia na resolução <strong>de</strong> outros<br />

problemas, alem <strong>de</strong> tocarem em aspectos<br />

complexos que, ao serem absorvidos pelo<br />

estudante são capazes <strong>de</strong> aprofundar sua<br />

capacida<strong>de</strong> <strong>de</strong> raciocínio e habilida<strong>de</strong>s em<br />

<strong>de</strong>senvolver programas. Vamos a ele então.<br />

5.1 CONCEITOS BÁSICOS DE ORDENAÇÃO<br />

A or<strong>de</strong>nação consiste em rearranjar um conjunto <strong>de</strong> objetos em or<strong>de</strong>m<br />

crescente ou <strong>de</strong>crescente, levando-se em conta uma chave or<strong>de</strong>nação.<br />

A or<strong>de</strong>nação possui motivações óbvias. Em geral, colocamos dados <strong>de</strong><br />

forma or<strong>de</strong>nada para:<br />

Facilitar a obtenção <strong>de</strong> uma informação ao se observar um<br />

conjunto <strong>de</strong> dados. Por exemplo, a visualização da<br />

classificação <strong>de</strong> candidatos em um concurso fica mais fácil se<br />

a lista <strong>de</strong> candidatos estiver or<strong>de</strong>nada pelo número <strong>de</strong> pontos<br />

que fizeram.<br />

Facilitar a recuperação <strong>de</strong> uma informação. Por exemplo: obter<br />

os pontos que um candidato em um concurso fez, sabendo o<br />

seu nome, fica mais fácil se a lista <strong>de</strong> candidatos estiver<br />

or<strong>de</strong>nada por nome.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


A or<strong>de</strong>nação po<strong>de</strong> ser feita consi<strong>de</strong>rando diferentes tipos <strong>de</strong> dados<br />

para a chave, inteiros, reais, strings. O que importa é estabelecer as<br />

comparações nos algoritmos <strong>de</strong> acordo com o tipo <strong>de</strong> dado da chave<br />

<strong>de</strong> or<strong>de</strong>nação. Na or<strong>de</strong>nação <strong>de</strong> um arranjo <strong>de</strong> registros, com a tabela<br />

<strong>de</strong> elementos usada no capítulo anterior, apenas a chave <strong>de</strong> or<strong>de</strong>nação<br />

é usada. Os <strong>de</strong>mais campos do registro são irrelevantes.<br />

5.1.1 Operações Típicas <strong>de</strong> processos <strong>de</strong> Or<strong>de</strong>nação<br />

Nos diferentes métodos <strong>de</strong> or<strong>de</strong>nação, algumas operações ocorrem<br />

com mais freqüência e o <strong>de</strong>sempenho dos métodos está geralmente<br />

associado com a realização <strong>de</strong>ssas operações. As duas operações que<br />

mais afetam o <strong>de</strong>sempenho dos algoritmos <strong>de</strong> or<strong>de</strong>nação são as<br />

operações <strong>de</strong> troca e <strong>de</strong> comparação:<br />

Troca: Consiste na troca <strong>de</strong> valores entre duas posições do<br />

arranjo que está sendo or<strong>de</strong>nado. Esta operação tem custo<br />

elevado e é a que mais afeta o <strong>de</strong>sempenho dos algoritmos. A<br />

operação <strong>de</strong> troca geralmente é suportada por uma função. A<br />

Figura 1 apresenta uma implementação da função troca;<br />

Comparação: consiste nas comparações entre dois valores<br />

contidos em posições do arranjo com o objetivo <strong>de</strong> saber qual<br />

<strong>de</strong>ntre as duas é menor. Embora <strong>de</strong> menor custo, esta operação<br />

também afeta bastante o <strong>de</strong>sempenho dos algoritmos <strong>de</strong>vido ao<br />

fato <strong>de</strong> serem feitas muitas comparações nos processos <strong>de</strong><br />

or<strong>de</strong>nação.<br />

Figura 1: Implementação da função troca<br />

5.2 MÉTODOS DE ORDENAÇÃO<br />

Iremos estudar 5 métodos <strong>de</strong> or<strong>de</strong>nação diferentes neste capítulo.<br />

A saber:<br />

1. Seleção<br />

2. Inserção<br />

3. Bolha<br />

4. Shell<br />

5. Quicksort<br />

Depen<strong>de</strong>ndo da aplicação e situação, o uso <strong>de</strong> um método po<strong>de</strong> ser<br />

mais conveniente que o outro. Em geral, o quicksort apresenta-se com<br />

melhor <strong>de</strong>sempenho na maioria das situações. O Método da bolha é o<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 119


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 120<br />

menos eficiente. Porem a sua simplicida<strong>de</strong> o torna conveniente para<br />

or<strong>de</strong>nar pequenos vetores (com tamanho menor que 20). A seguir<br />

discutimos cada um <strong>de</strong>stes algoritmos individualmente.<br />

5.2.1 Or<strong>de</strong>nação por Seleção<br />

Este método tem como estratégia selecionar o menor elemento<br />

remanescente do conjunto não or<strong>de</strong>nado e mover este elemento para<br />

sua posição correta. Por exemplo, suponha um arranjo com 4<br />

elementos.<br />

O processo <strong>de</strong> or<strong>de</strong>nação por seleção <strong>de</strong>termina quem é o menor<br />

elemento e o insere na primeira posição do vetor. Para tanto, o<br />

elemento que estava na posição inicial precisa ser colocado na posição<br />

on<strong>de</strong> estava o menor. Em seguida o segundo menor é encontrado e<br />

inserido na segunda posição do arranjo. O Algoritmo segue assim<br />

sucessivamente até encontrar o último elemento e inseri-lo na última<br />

posição.<br />

A Figura 2 apresenta a implementação do algoritmo <strong>de</strong> seleção.<br />

Implementação:<br />

Figura 2: Implementação da Or<strong>de</strong>nação por Seleção<br />

Tomando como exemplo o arranjo<br />

int B[] = {10,3,7,20,1,2,11,0,5,4};<br />

a execução da função seleção irá imprimir a seguinte seqüência <strong>de</strong><br />

valores intermediários para o arranjo B até a sua or<strong>de</strong>nação completa:<br />

10 3 7 20 1 2 11 0 5 4<br />

0 3 7 20 1 2 11 10 5 4 (fim da interação i=0)<br />

0 1 7 20 3 2 11 10 5 4 (fim da interação i=1)<br />

0 1 2 20 3 7 11 10 5 4 (fim da interação i=2)<br />

0 1 2 3 20 7 11 10 5 4 (fim da interação i=3)<br />

0 1 2 3 4 7 11 10 5 20 (fim da interação i=4)<br />

0 1 2 3 4 5 11 10 7 20 (fim da interação i=5)<br />

0 1 2 3 4 5 7 10 11 20 (fim da interação i=6)<br />

0 1 2 3 4 5 7 10 11 20 (fim da interação i=7)<br />

0 1 2 3 4 5 7 10 11 20 (fim da interação i=8)<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


O algoritmo <strong>de</strong> seleção utiliza o índice i como limite inferior da parte<br />

do arranjo que ainda não está or<strong>de</strong>nado (<strong>de</strong> i até n-1). À medida que o<br />

arranjo vai sendo or<strong>de</strong>nado este limite vai avançando (laço mais<br />

externo). O laço mais interno tem como objetivo encontrar o menor<br />

elemento <strong>de</strong>ntre os elementos que ainda não foram or<strong>de</strong>nados. min é<br />

iniciado sempre com o valor <strong>de</strong> i. Em seguida o laço mais interno (do<br />

j) se encarrega <strong>de</strong> verificar se no restante do vetor (posições <strong>de</strong> i+1 até<br />

n-1) existe um valor menor que a[min]. Toda vez que isso ocorre, o<br />

valor <strong>de</strong> min é atualizado. Ao final <strong>de</strong> um laço mais interno, a<br />

variável min vai conter o índice do menor valor no arranjo <strong>de</strong>ntre os<br />

elementos que não foram ainda or<strong>de</strong>nados.<br />

Conforme po<strong>de</strong> ser visto na saída do programa, na primeira interação<br />

<strong>de</strong> i, i=0, o menor elemento contido no vetor é localizado e inserido<br />

na posição 0. Esta inserção é feita pela operação <strong>de</strong> troca que ocorre<br />

ao final <strong>de</strong> cada iteração do laço mais externo.<br />

Note que as trocas ocorrem in<strong>de</strong>pen<strong>de</strong>ntemente <strong>de</strong> haver sido<br />

encontrado um valor menor que a[i]. Neste caso haverá uma troca<br />

entre duas posições idênticas, pois o valor <strong>de</strong> min vai ser igual a i.<br />

Isso ocorreu nas iterações 6,7 e 8. Caso seja importante melhorar o<br />

<strong>de</strong>sempenho do algoritmo esta troca entre posições iguais po<strong>de</strong> ser<br />

evitada comparando-se o valor <strong>de</strong> min com i. Se estes forem iguais a<br />

troca não precisa ser feita.<br />

5.2.2 Método da Inserção<br />

Este método é similar ao que o jogador <strong>de</strong> cartas utiliza: Cada<br />

elemento a ser or<strong>de</strong>nado é reposicionado entre os or<strong>de</strong>nados<br />

movendo-se os elementos maiores que ele uma posição para a direita e<br />

posteriormente inserindo-o na posição vaga.<br />

A Figura 3 apresenta a implementação do algoritmo <strong>de</strong> Inserção.<br />

Figura 3: Implementação do método <strong>de</strong> or<strong>de</strong>nação por Inserção<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 121


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 122<br />

A função imprimevetor é chamada a cada iteração do laço while<br />

(mais interno) e ao fim <strong>de</strong> cada laço mais externo também. A<br />

execução <strong>de</strong>sta função sobre o arranjo<br />

int B[] = {10,3,7,20,1,2,11,0,5,4};<br />

produz a seguinte saída:<br />

10 3 7 20 1 2 11 0 5 4<br />

10 10 7 20 1 2 11 0 5 4<br />

3 10 7 20 1 2 11 0 5 4 (fim da iteração i=1)<br />

3 10 10 20 1 2 11 0 5 4<br />

3 7 10 20 1 2 11 0 5 4 (fim da iteração i=2)<br />

3 7 10 20 1 2 11 0 5 4 (fim da iteração i=3)<br />

3 7 10 20 20 2 11 0 5 4<br />

3 7 10 10 20 2 11 0 5 4<br />

3 7 7 10 20 2 11 0 5 4<br />

3 3 7 10 20 2 11 0 5 4<br />

1 3 7 10 20 2 11 0 5 4 (fim da iteração i=4)<br />

1 3 7 10 20 20 11 0 5 4<br />

1 3 7 10 10 20 11 0 5 4<br />

1 3 7 7 10 20 11 0 5 4<br />

1 3 3 7 10 20 11 0 5 4<br />

1 2 3 7 10 20 11 0 5 4 (fim da iteração i=5)<br />

1 2 3 7 10 20 20 0 5 4<br />

1 2 3 7 10 11 20 0 5 4 (fim da iteração i=6)<br />

1 2 3 7 10 11 20 20 5 4<br />

1 2 3 7 10 11 11 20 5 4<br />

1 2 3 7 10 10 11 20 5 4<br />

1 2 3 7 7 10 11 20 5 4<br />

1 2 3 3 7 10 11 20 5 4<br />

1 2 2 3 7 10 11 20 5 4<br />

1 1 2 3 7 10 11 20 5 4<br />

0 1 2 3 7 10 11 20 5 4 (fim da iteração i=7)<br />

0 1 2 3 7 10 11 20 20 4<br />

0 1 2 3 7 10 11 11 20 4<br />

0 1 2 3 7 10 10 11 20 4<br />

0 1 2 3 7 7 10 11 20 4<br />

0 1 2 3 5 7 10 11 20 4 (fim da iteração i=8)<br />

0 1 2 3 5 7 10 11 20 20<br />

0 1 2 3 5 7 10 11 11 20<br />

0 1 2 3 5 7 10 10 11 20<br />

0 1 2 3 5 7 7 10 11 20<br />

0 1 2 3 5 5 7 10 11 20<br />

0 1 2 3 4 5 7 10 11 20 (fim da iteração i=9)<br />

Na execução da função o laço mais externo é usado para <strong>de</strong>limitar os<br />

elementos do arranjo ainda não inspecionados. A variável x é usada<br />

para guardar o valor do elemento a ser inserido (a[i]). Na primeira<br />

iteração este elemento é o 3. Na segunda é o 7. Na terceira é o 10 e<br />

assim por diante. Nem sempre o elemento em x será substituído. Por<br />

exemplo, na iteração i=3, o elemento x é igual a 10 e não terá a sua<br />

posição alterada. O laço while tem a finalida<strong>de</strong> <strong>de</strong> percorrer o<br />

arranjo nas posições entre (i-1 e 0) procurando valores menores que<br />

x. À medida que estes valores são encontrados eles vão sendo<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


<strong>de</strong>slocados para a direita. O laço while termina quando é<br />

encontrado um elemento menor que x ou o limite do vetor é atingido.<br />

5.2.3 Método da Bolha<br />

O Método da Bolha consiste em percorrer o vetor trocando os<br />

elementos adjacentes caso necessário. Por este motivo, este método<br />

realiza muitas trocas.<br />

A implementação <strong>de</strong>sse método é apresentada na Figura 4.<br />

Figura 4: Método da Bolha<br />

A execução do método da bolha sobre o vetor<br />

int B[] = {10,3,7,20,1,2,11,0,5,4};<br />

produz a seguinte saída:<br />

10 3 7 20 1 2 11 0 5 4<br />

3 10 7 20 1 2 11 0 5 4 (troca 10 e 3)<br />

3 7 10 20 1 2 11 0 5 4 (troca 10 e 7)<br />

3 7 10 1 20 2 11 0 5 4 (troca 20 e 1)<br />

3 7 10 1 2 20 11 0 5 4 (troca 20 e 2)<br />

3 7 10 1 2 11 20 0 5 4 (troca 20 e 11)<br />

3 7 10 1 2 11 0 20 5 4 (troca 20 e 0)<br />

3 7 10 1 2 11 0 5 20 4<br />

3 7 10 1 2 11 0 5 4 20<br />

3 7 1 10 2 11 0 5 4 20<br />

3 7 1 2 10 11 0 5 4 20<br />

3 7 1 2 10 0 11 5 4 20<br />

3 7 1 2 10 0 5 11 4 20<br />

3 7 1 2 10 0 5 4 11 20<br />

3 1 7 2 10 0 5 4 11 20<br />

3 1 2 7 10 0 5 4 11 20<br />

3 1 2 7 0 10 5 4 11 20<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 123


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 124<br />

3 1 2 7 0 5 10 4 11 20<br />

3 1 2 7 0 5 4 10 11 20<br />

1 3 2 7 0 5 4 10 11 20<br />

1 2 3 7 0 5 4 10 11 20<br />

1 2 3 0 7 5 4 10 11 20<br />

1 2 3 0 5 7 4 10 11 20<br />

1 2 3 0 5 4 7 10 11 20<br />

1 2 0 3 5 4 7 10 11 20<br />

1 2 0 3 4 5 7 10 11 20<br />

1 0 2 3 4 5 7 10 11 20<br />

0 1 2 3 4 5 7 10 11 20<br />

5.2.4 Desempenho dos métodos <strong>de</strong> Seleção, Inserção e<br />

Bolha<br />

Para a análise <strong>de</strong> <strong>de</strong>sempenho <strong>de</strong> métodos <strong>de</strong> or<strong>de</strong>nação <strong>de</strong>ve levar em<br />

contar o número <strong>de</strong> comparações e o número <strong>de</strong> trocas realizadas.<br />

O Método da Seleção realiza aproximadamente n 2 /2 comparações e n<br />

trocas, pois para cada i <strong>de</strong> 1 até n-1 ocorre uma troca e n-1<br />

comparações. Assim temos que no final ocorrem n-1 trocas e (n-1 +<br />

(n-2) + ... 2 +1 = n(n-1)/2 comparações.<br />

O método da Inserção realiza aproximadamente n 2 /4 comparações e<br />

n 2 /8 trocas no caso médio. No entanto, esse método é linear para<br />

arranjos parcialmente or<strong>de</strong>nados, o que o torna um método<br />

conveniente para estes casos.<br />

O método da Bolha realiza n 2 /2 comparações e n 2 /2 trocas no caso<br />

médio e no pior caso.<br />

Realize testes <strong>de</strong> mesa para os métodos <strong>de</strong> seleção,<br />

inserção e bolha para os seguintes vetores:<br />

A = {10,0,3,2,5}, B={5,4,3,2,1}, C={1,1,2,2,0,0}.<br />

5.2.5 Método <strong>de</strong> Shell<br />

O método que estudaremos agora foi inventado por Criado por Donald<br />

Shell em 1959. Conforme comentado na Seção 5.2.4, o método da<br />

Inserção é bastante eficiente ao or<strong>de</strong>nar arranjos que já estão<br />

parcialmente or<strong>de</strong>nados. Shell observou esta característica e criou um<br />

método on<strong>de</strong> o método <strong>de</strong> inserção é aplicado sucessivamente.<br />

Em seu método, o algoritmo <strong>de</strong> inserção é aplicado para or<strong>de</strong>nar<br />

subconjuntos do arranjo completo. Estes subconjuntos são pegos<br />

consi<strong>de</strong>rando uma distância entre os elementos. Para cada execução<br />

do método <strong>de</strong> Inserção é consi<strong>de</strong>rada um distância d. d é iniciado com<br />

um valor que vai sendo <strong>de</strong>crementado até chegar a 1. Para cada valor<br />

<strong>de</strong> d, os elementos do arranjo localizados a uma distância d entre si<br />

são or<strong>de</strong>nados.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Por exemplo, em um arranjo,<br />

6. a = {a0,a1,a2,a3,a4,a5,a6,a7,a8,a9}<br />

E consi<strong>de</strong>rando uma série <strong>de</strong> distâncias d = {5,3,1}. Nas iterações com<br />

distância d = 5, o método da inserção será aplicado aos elementos do<br />

arranjo localizados a uma distância 5 entre si. Os elementos são pegos<br />

do arranjo a partir da posição a0, na primeira iteração, a1 na segunda,<br />

a2 na terceira e assim sucessivamente.<br />

Assim para a distância 5 teremos a or<strong>de</strong>nação dos elementos:<br />

{a0,a5} (primeira iteração)<br />

{a1,a6} (segunda iteração)<br />

{a2,a7} (terceira iteração)<br />

{a3,a8} (quarta iteração)<br />

{a4,a9} (quinta iteração)<br />

Para a distância 3 teremos a or<strong>de</strong>nação dos elementos:<br />

{a0,a3,a6,a9} (primeira iteração)<br />

{a1,a4,a7} (segunda iteração)<br />

{a2,a5,a8} (terceira iteração)<br />

{a3,a6,a9} (quarta iteração)<br />

{a4,a7} (quinta iteração)<br />

{a5,a8} (sexta iteração)<br />

{a6,a9} (sétima iteração)<br />

Para a distância 1, teremos a or<strong>de</strong>nação do arranjo completo<br />

7. {a0,a1,a2,a3,a4,a5,a6,a7,a8,a9}<br />

iteração)<br />

8.<br />

(primeira<br />

A cada iteração à distância d é reduzida <strong>de</strong> acordo com alguma<br />

seqüência. Na utiliza iteração esta distância é reduzida a 1. Este passo<br />

correspon<strong>de</strong> ao método <strong>de</strong> inserção original.<br />

Vejamos um exemplo concreto, consi<strong>de</strong>rando o arranjo a = {35,<br />

28, 16, 07, 12, 08,04} e utilizando a seqüência <strong>de</strong><br />

distâncias {3,1}:<br />

Para d = 3 o método or<strong>de</strong>na os elementos que estão a uma distância 3<br />

entre si. Na tabela abaixo estes elementos estão em negrito:<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 125


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 126<br />

35 28 16 07 12 08 04 35 está<br />

or<strong>de</strong>nado, 7<br />

é comparado<br />

07 28 16 35 12 08 04 {07,35} está<br />

or<strong>de</strong>nado, 4<br />

é comparado<br />

04 28 16 07 12 08 35 {04,07,35}<br />

está<br />

or<strong>de</strong>nado<br />

04 28 16 07 12 08 35 28 está<br />

or<strong>de</strong>nado,<br />

12 é<br />

comparado<br />

04 12 16 07 28 08 35 {12,28} está<br />

or<strong>de</strong>nado<br />

04 12 16 07 28 08 35 16 está<br />

or<strong>de</strong>nado,<br />

35 é<br />

comparado<br />

04 12 08 07 28 16 35 {08,16} está<br />

or<strong>de</strong>nado<br />

Após a or<strong>de</strong>nação parcial feita com d=3, a distância é reduzida a 1 e o<br />

vetor é completamente or<strong>de</strong>nado.<br />

Para implementação do método <strong>de</strong> Shell, implementa-se uma função<br />

(Shell) que correspon<strong>de</strong> ao método <strong>de</strong> inserção parametrizado pela<br />

distância. Esta implementação é apresentada na Figura 5.<br />

Figura 5: Implementação da função Shell<br />

Note que a função shell correspon<strong>de</strong> exatamente à função<br />

insercao, se substituirmos o d pelo valor 1.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Para concluirmos a implementação do método <strong>de</strong> Shell, basta agora<br />

implementarmos uma função que gere a seqüência <strong>de</strong> distâncias e a<br />

chave a função Shell para cada distância da série. A Figura 6 apresenta<br />

uma implementação da função shellSort, que executa o método com a<br />

série {5,2,1}.<br />

Figura 6: Implementação do método <strong>de</strong> Shell com distâncias 5,2 e 1<br />

A execução <strong>de</strong> shellsort1 sobre o vetor<br />

int B[] = {10,3,7,20,1,2,11,0,5,4}; produz a<br />

seguinte saída:<br />

10 3 7 20 1 2 11 0 5 4<br />

Distancia d= 5<br />

10 3 7 20 1 10 11 0 5 4<br />

2 3 7 20 1 10 11 0 5 4<br />

2 3 7 20 1 10 11 0 5 4<br />

2 3 7 20 1 10 11 7 5 4<br />

2 3 0 20 1 10 11 7 5 4<br />

2 3 0 20 1 10 11 7 20 4<br />

2 3 0 5 1 10 11 7 20 4<br />

2 3 0 5 1 10 11 7 20 4<br />

Distancia d= 2<br />

2 3 2 5 1 10 11 7 20 4<br />

0 3 2 5 1 10 11 7 20 4<br />

0 3 2 5 1 10 11 7 20 4<br />

0 3 2 5 2 10 11 7 20 4<br />

0 3 1 5 2 10 11 7 20 4<br />

0 3 1 5 2 10 11 7 20 4<br />

0 3 1 5 2 10 11 7 20 4<br />

0 3 1 5 2 10 11 10 20 4<br />

0 3 1 5 2 7 11 10 20 4<br />

0 3 1 5 2 7 11 10 20 4<br />

0 3 1 5 2 7 11 10 20 10<br />

0 3 1 5 2 7 11 7 20 10<br />

0 3 1 5 2 5 11 7 20 10<br />

0 3 1 4 2 5 11 7 20 10<br />

Distancia d= 1<br />

0 3 1 4 2 5 11 7 20 10<br />

0 3 3 4 2 5 11 7 20 10<br />

0 1 3 4 2 5 11 7 20 10<br />

0 1 3 4 2 5 11 7 20 10<br />

0 1 3 4 4 5 11 7 20 10<br />

0 1 3 3 4 5 11 7 20 10<br />

0 1 2 3 4 5 11 7 20 10<br />

0 1 2 3 4 5 11 7 20 10<br />

0 1 2 3 4 5 11 7 20 10<br />

0 1 2 3 4 5 11 11 20 10<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 127


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 128<br />

0 1 2 3 4 5 7 11 20 10<br />

0 1 2 3 4 5 7 11 20 10<br />

0 1 2 3 4 5 7 11 20 20<br />

0 1 2 3 4 5 7 11 11 20<br />

0 1 2 3 4 5 7 10 11 20<br />

Conforme po<strong>de</strong> ser observado, a seqüência <strong>de</strong> distâncias po<strong>de</strong> variar.<br />

A única restrição é que termine com 1. Não existe uma seqüência<br />

i<strong>de</strong>al. Donald Knuth (vale a pena ver<br />

http://pt.wikipedia.org/wiki/Donald_Knuth), mostrou empiricamente<br />

que a seqüência ( 1,4, 13,40,121,364,1093..) apresenta eficiência 20 %<br />

maior que outras seqüências.<br />

A Figura 7 apresenta a função shellsort2 que executa a função Shell<br />

usando esta seqüência.<br />

Figura 7: Implementação do método <strong>de</strong> Shell usando a seqüência <strong>de</strong> Knuth<br />

5.2.6 O Método Quicksort<br />

Até o momento, foram estudados métodos <strong>de</strong> or<strong>de</strong>nação cuja função<br />

<strong>de</strong> complexida<strong>de</strong> é próxima <strong>de</strong> n 2 . Ou seja, complexida<strong>de</strong> quadrática.<br />

Nos melhores casos, o método da inserção e Shell po<strong>de</strong>m chegar<br />

próximo <strong>de</strong> um comportamento linear. O método quicksort baseia-se<br />

em uma estratégia <strong>de</strong> dividir para conquistar (vi<strong>de</strong> pesquisa binária),<br />

tornando-o assim um método que apresenta complexida<strong>de</strong> logarítmica<br />

(sublinear), no caso médio. Este comportamento do quicksort o torna<br />

um método <strong>de</strong> or<strong>de</strong>nação extremamente rápido.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


O algoritmo Quicksort foi inventado por C.A.R. Hoare em 1960,<br />

durante visita a Universida<strong>de</strong> <strong>de</strong> Moscou (Russia), ainda como<br />

estudante.<br />

A idéia fundamental do quicksort consiste em:<br />

Primeiro dividir: a seqüência a ser or<strong>de</strong>nada a é particionada em duas<br />

partes b e c, <strong>de</strong> tal modo que todos os elementos da primeira parte b<br />

são menores ou iguais a todos os elementos da segunda parte c. Em<br />

seguida conquistar: as duas partes são or<strong>de</strong>nadas separadamente por<br />

meio da aplicação recursiva do mesmo procedimento. Por último<br />

recombinar. A recombinação das duas partes or<strong>de</strong>nadas em separado<br />

produz uma seqüência também or<strong>de</strong>nada, dado que os elementos da<br />

primeira parte b são menores ou iguais a todos os elementos da<br />

segunda parte c.<br />

O processo <strong>de</strong> particionamento da seqüência é feito por meio da<br />

escolha <strong>de</strong> um elemento pivô. Por exemplo, Seja o arranjo<br />

{f,e,d,h,a,c,g,b} e o valor d como o elemento escolhido para<br />

fazer a primeira partição. O elemento escolhido é chamado <strong>de</strong> pivô.<br />

Após a primeira iteração com o valor d como pivô, teremos a seguinte<br />

configuração para o vetor: {b,c,a,d,h,e,g,f}<br />

Neste passo o processo é repetido para cada sub vetor [b,c,a] e<br />

[h,e,g,f]<br />

A Figura 8 apresenta uma implementação da operação <strong>de</strong><br />

particionamento. Nessa implementação o pivô é sempre o elemento do<br />

meio do vetor.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 129


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 130<br />

Figura 8: função particiona<br />

A Figura 9 apresenta a função or<strong>de</strong>naQS que aplica o método sobre<br />

um arranjo com limites esq e dir.<br />

Figura 9: Implementação da função or<strong>de</strong>naQS<br />

A implementação da função quicksort, apresentada na Figura 10<br />

consiste apenas no encapsulamento da função or<strong>de</strong>naQS em uma<br />

função cujos parâmetros são apenas o arranjo e seu tamanho.<br />

Figura 10: implementação da função quicksort<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo


Ativida<strong>de</strong>s<br />

1. Realizar os experimentos com os 5 métodos <strong>de</strong> or<strong>de</strong>nação<br />

estudados. No caso do shellsort utilizar a seqüência <strong>de</strong> Knuth.<br />

a) Realizar testes com todos os métodos para vetores <strong>de</strong> tamanho<br />

20000, 40000, 60000, 80000, 100000, 150000, 200000, 250000,<br />

300000, 350000, 400000 e 500000.<br />

b) Utilizar um software que trace gráficos (Excel, por exemplo) e<br />

traçar um gráfico <strong>de</strong> <strong>de</strong>sempenho dos métodos utilizados. O<br />

gráfico <strong>de</strong>ve ter os seguintes eixos: eixo x: tamanho do vetor. Eixo<br />

y: tempo <strong>de</strong> execução. Colocar as curvas <strong>de</strong> todos os métodos no<br />

mesmo gráfico para facilitar a comparação.<br />

c) Fazer uma análise dos resultados obtidos (<strong>de</strong>scritiva).<br />

2. Sabemos que a eficiência do Shellsort <strong>de</strong>pen<strong>de</strong> da seqüência <strong>de</strong><br />

distâncias utilizada. Assim, neste exercício, primeiramente você<br />

<strong>de</strong>ve inventar uma seqüência <strong>de</strong> distâncias sua para ser utilizada<br />

no Shellsort. Posteriormente, escreva uma função myShellsort que<br />

execute o Shellsort com uma seqüência produzida por você.<br />

Invente uma seqüência sua! Você po<strong>de</strong> utilizar a mesma estrutura<br />

que foi utilizada para as funções shelsort1 e shellsort2. Compare<br />

os resultados <strong>de</strong> tempo obtidos com a sua seqüência com os<br />

obtidos com a seqüência <strong>de</strong> Knuth.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo<br />

<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 131


<strong>Técnicas</strong> <strong>de</strong> <strong>Programação</strong> <strong>Avançada</strong><br />

Página 132<br />

Referências utilizadas na elaboração <strong>de</strong>ste material<br />

1. LISKOV B. Data Abstraction and Hiararchy. In<br />

OOPSLA’87: Conference on Object Oriented Programming<br />

Systems Languages and Applications. Ad<strong>de</strong>ndum to the<br />

proceedings on Object-oriented programming systems,<br />

languages and applications, 1987.<br />

2. TENENBAUM A. M. Data Structs using C. Prentice Hall Int.<br />

Editions, 1990.<br />

3. ZIVIANI N. Projeto <strong>de</strong> Algoritmos. Segunda Edição.<br />

Pioneira, 2003.<br />

Centro Fe<strong>de</strong>ral <strong>de</strong> Educação Tecnológica do Espírito Santo

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!