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