Buscar

Algoritmo e Estrutura de Dados - UFRP

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 181 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 181 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 181 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Recife,2009
Algoritmos e 
Estrutura de Dados
Rodrigo de Souza
Hugo Rodrigues
Universidade Federal Rural de Pernambuco
Reitor: Prof. Valmar Corrêa de Andrade
Vice-Reitor: Prof. Reginaldo Barros
Pró-Reitor de Administração: Prof. Francisco Fernando Ramos Carvalho
Pró-Reitor de Extensão: Prof. Paulo Donizeti Siepierski
Pró-Reitor de Pesquisa e Pós-Graduação: Prof. Fernando José Freire
Pró-Reitor de Planejamento: Prof. Rinaldo Luiz Caraciolo Ferreira
Pró-Reitora de Ensino de Graduação: Profª. Maria José de Sena
Coordenação Geral de Ensino a Distância: Profª Marizete Silva Santos
Produção Gráfica e Editorial
Capa e Editoração: Allyson Vila Nova, Rafael Lira e Italo Amorim
Revisão Ortográfica: Marcelo Melo
Ilustrações: Roberto Costa e Diego Almeida
Coordenação de Produção: Marizete Silva Santos
Sumário
Plano da Disciplina ...............................................................................4
Apresentação ........................................................................................7
Conhecendo o Volume 1 ......................................................................8
Capítulo 1 – História do conceito de algoritmo .................................9
1. O que é um algoritmo? ...................................................................9
2. De onde vem a ideia de algoritmo? ..............................................11
3. Algoritmos versus programas .......................................................13
Capítulo 2 – Exemplos de algoritmos ...............................................19
1. O algoritmo de Euclides ................................................................20
2. A linguagem Portucal ....................................................................23
Capítulo 3 – Algoritmos recursivos ...................................................42
1. O conceito de recursão .................................................................42
2. Deve-se sempre usar recursão? ..................................................46
Capítulo 4 – Análise de um algoritmo ...............................................50
1. Uma experiência: eficiência de soma-subconjunto ......................51
2. Análise assintótica ........................................................................58
Considerações Finais ........................................................................66
Conheça os Autores ..............................................................................68
Plano da Disciplina
Disciplina: Algoritmos e Estruturas de Dados
Carga horária: 60h
» Ementa da Disciplina: História do conceito de algoritmo. Notação para 
apresentação de algoritmos. Algoritmos recursivos. Noções de análise de 
algoritmos, notação assintótica. Operações básicas em vetores e matrizes. Busca 
binária. Algoritmos de ordenação: inserção, seleção, flutuação, intercalação, 
quicksort. Listas ligadas, pilhas e filas. Árvores binárias, árvores balanceadas. 
Tabelas de espalhamento. Busca de palavras em um texto. Noções de grafos, 
algoritmo de Dijkstra.
Objetivos
Objetivo Geral
• Fixar o conceito de algoritmo e introduzir algoritmos eficientes para manipulação 
de dados
• Apresentar estruturas de dados fundamentais
Objetivos Específicos
• Fornecer ao aluno uma noção da história e da importância da noção de algoritmo, 
bem como rudimentos de análise de complexidade e correção de algoritmos, 
facultando-o a discernir entre possibilidades diversas para a solução de um 
problema computacional e habilitando-o a desenvolver algoritmos eficientes. 
Introduzir e analisar algoritmos clássicos de busca e ordenação.
• Apresentar estruturas de dados fundamentais, bem como suas operações básicas 
e algoritmos associados.
Conteúdo Programático
Módulo 1 – Noções básicas de algoritmos
Carga horária: 15h
• História do conceito de algoritmo
• Notação para apresentação de algoritmos
• Algoritmos recursivos
• Noções de análise de algoritmos, notação assintótica
Módulo 2 – Busca e ordenação em vetores
Carga horária: 15h
• Primeiras noções de estruturas de dados
• Busca, inserção e remoção em vetores
• Algoritmos elementares de ordenação
• Ordenação mais eficiente: intercalação
Módulo 3 – Estruturas de dados elementares
Carga horária: 15h
• Listas ligadas
• Pilhas
• Filas
• Árvores
Módulo 4 – Tópicos adicionais
Carga horária: 15h
• Tabelas de espelhamento
• Busca de palavras em um texto
• Noções de grafos, algoritmo de Dijkstra
Avaliação
 60% atividades presenciais realizadas nos pólos (provas e exercícios de 
programação simples)
 40% atividades nos ambientes virtuais de aprendizagem (participação, discussão 
em tempo real de questões propostas pelo professor)
Metodologia
 Uso de recursos visuais para animação e ilustração dos algoritmos e estruturas 
de dados estudados, uso de fórum de discussão para tratamento de dúvidas e 
discussão de problemas propostos na disciplina, realização de exercícios propostos 
pelo professor (listas ou exercícios de programação). Havendo possibilidade, uso 
de recursos interativos (applets Java, por exemplo), facultando ao aluno realizar 
com mouse/teclado operações básicas em estruturas de dados.
Referências
 CORMEN, T; LEISERSON, C; RIVEST, R; STEIN, C. Algoritmos - Teoria e 
Prática. São Paulo: Ed. Campus, 2002.
 TERADA, R.; SETzER, V. W. Introdução à Computação e à Construção de 
Algoritmos. São Paulo: Makron Books, 1992.
 KNUTH, D. The Art of Computer Programming. Massachusetts: Addison-
Wesley, 1997
 WIRTH, N. Algoritmos e Estruturas de Dados. Rio de Janeiro: Ed. Prentice Hall 
do Brasil, 2002.
Apresentação
Caro(a) cursista
Seja bem-vindo(a) ao primeiro módulo do curso Algoritmos e Estruturas de Dados. 
Nele, teremos nosso primeiro contato com o conceito de algoritmo, que é fundamental 
em Computação, pois algoritmos são a essência de qualquer programa em execução 
num computador real.
Começaremos esse volume com um pouco de História. Veremos que algoritmos já 
eram utilizados por civilizações da Antiguidade e que a formalização desse conceito foi 
uma das descobertas mais importantes do século vinte para a Computação.
Vamos, logo em seguida, estudar nossos primeiros algoritmos. Veremos que 
podemos descrever algoritmos com uma linguagem bastante simples e natural, e não 
muito distante das linguagens de programação populares. Também descobriremos 
que um algoritmo pode, em sua execução, iniciar uma nova execução de si mesmo, 
e que esse recurso é bastante poderoso. Tais algoritmos são chamados de algoritmos 
recursivos.
Finalmente, consideraremos o fato de que, normalmente, podemos resolver um 
problema de diversas maneiras, ou seja, usando uma variedade de algoritmos. Contudo, 
perceberemos que certos algoritmos podem ser mais eficientes do que outros. Para 
isso, vamos introduzir uma ferramenta para medir a eficiência de algoritmos, a análise 
de complexidade.
Bons estudos!
Professores Rodrigo de Souza e Hugo Rodrigues 
8
Algoritmos e Estrutura de Dados
Conhecendo o Volume 1
Neste primeiro volume, você irá encontrar o módulo 01 da disciplina 
Algoritmos e Estruturas de Dados. Para facilitar seus estudos, veja 
a organização deste primeiro módulo.
Módulo 1 – Noções básicas de algoritmos
Carga horária do Módulo 1: 15h/aula
Objetivo do Módulo 1: Introduzir os conceitos de algoritmo e 
algoritmo recursivo. Fixar uma notação para apresentação de 
algoritmos. Introduzir elementos de análise de algoritmos.
Conteúdo Programático do Módulo 1
• História do conceito de algoritmo
• Exemplos de algoritmos
• Algoritmos recursivos
• Noções de análise de algoritmos, notação assintótica
9
Algoritmos e Estrutura de Dados
Capítulo 1 – História do 
conceito de algoritmo
Vamos conversar sobre o assunto?
Você provavelmente já usou um computador para alguma tarefa 
cotidiana – escrever uma carta, comunicar-se por e-mail, calcular 
suas despesas, orientar-se através de um GPS de bordo... Talvez 
já tenha precisado buscar uma palavra num texto, executar uma 
operação repetidanuma planilha, ou determinar o caminho mínimo 
entre dois pontos em um mapa para percorrer com seu veículo. Já 
parou para pensar como o computador encontra a solução para esses 
problemas? Ou então já se perguntou se os computadores podem 
resolver qualquer problema?
Do ponto de vista do usuário, o computador é uma caixa preta 
que lhe fornece certas funcionalidades – como um eletrodoméstico 
comum. Para o estudioso de Computação, o computador, como 
objeto não-pensante, é meramente um mecanismo executor de 
sequências precisas de instruções. Tais sequências de instruções são 
o que chamamos de algoritmos. Algoritmos são, assim, o princípio do 
funcionamento de qualquer computador, e mais: desde a Antiguidade, 
o homem executa algoritmos, e é possível que cada um de nós tenha 
executado, ao longo de nossas vidas, algoritmos diversos. Quem 
nunca precisou ordenar as cartas de um baralho ou de um fichário? 
A noção de algoritmo, sendo natural, pode ser compreendida 
mesmo por quem não utiliza computadores. É com esse espírito que 
vamos estudá-los nesse volume. Por outro lado, o domínio da noção 
de algoritmos vai nos fornecer uma compreensão mais profunda dos 
limites dos computadores e uma visão diferente da do usuário comum. 
Bom estudo!
1. O que é um algoritmo?
De forma geral, um algoritmo é uma sequência finita de instruções. 
Uma receita de cozinha, se bem definida (especificando-se as 
quantidades de cada ingrediente e como eles serão misturados, 
por exemplo), é um algoritmo legítimo! Toda receita bem definida 
especifica com precisão os passos necessários para a elaboração 
10
Algoritmos e Estrutura de Dados
de um produto final. A execução de um algoritmo, nesse exemplo 
culinário, é simplesmente seguir a receita criteriosamente, passo a 
passo. Portanto, um algoritmo não tem necessariamente relação com 
um computador digital.
Mas essa definição parece muito geral e, para o estudioso de 
Computação, uma definição formal é necessária. De fato, esse 
estudioso desejará demonstrar fatos sobre algoritmos – o quão 
eficiente um algoritmo pode ser, se o mesmo resolve corretamente o 
problema a que se propõe solucionar. Não é à toa que muitas vezes 
a Computação confunde-se com a Matemática. Dessa forma, qual a 
resposta definitiva à nossa questão, “O que é um algoritmo?”
A resposta parece misteriosa, mas é simples. Vamos respondê-
la com alguma precisão no Capítulo 2, onde entraremos em acordo 
sobre uma notação para apresentação de algoritmos. Por enquanto, 
podemos manter em mente a idéia intuitiva de sequência de instruções, 
com algumas restrições importantes:
• essa sequência é finita;
• cada instrução pode ser executada de forma finita e mecânica 
(por um ser-humano usando apenas lápis e papel, por 
exemplo);
• o conjunto de instruções possíveis é finito.
Essas restrições são importantes: se tudo é permitido, então a 
noção de algoritmo perde o interesse do ponto de vista prático. Quem 
quer esperar toda a eternidade para saber o saldo de sua conta 
quando vai consultar um terminal eletrônico?
Também é preciso considerar que um algoritmo dialoga com o 
mundo externo. Ele recebe uma entrada, alguma informação a ser 
processada, e fornece uma saída, o resultado desse processamento. 
11
Algoritmos e Estrutura de Dados
No exemplo da receita, a entrada são os ingredientes, a saída é o 
produto que vamos cozinhar – um bolo, por exemplo.
Dissemos que a resposta é misteriosa, mas é simples. Também 
poderíamos dizer: a resposta é simples, mas por um bom tempo, 
foi misteriosa. A seguir, vamos sobrevoar o caminho trilhado pela 
civilização até a conquista da ideia precisa de algoritmos.
2. De onde vem a ideia de algoritmo?
A etimologia da palavra algoritmo revela um pouco de sua história. 
Historiadores da Matemática concluíram que ela vem do nome de um 
matemático persa do século nove, Abu Já’far Mohammed ibn Mûsâ 
Al-Khowârizmî. Literalmente: “Pai de Ja’far Mohammed, filho de 
Moisés, nativo de Khowârizm”. Al-Khowârizmî deu origem, durante 
a Idade Média, ao termo algoritmo (seu nome foi latinizado como 
Algorismus), e este foi usado com significados diversos. Por exemplo, 
alguns dicionários antigos definem essa palavra como todo processo 
de cálculo combinando às quatro operações aritméticas fundamentais. 
O que é compreensível, pois Al-Khowârizmî era um autor importante 
de livros matemáticos, e supostamente inventou algoritmos para a 
realização dessas operações.
Mas como dissemos, algoritmos são processos que ocorrem 
naturalmente em nosso dia-a-dia, sendo nós estudiosos da 
Computação ou não. Mencionamos o exemplo da cozinha. Poderíamos 
considerar também o processo de montagem de uma estante, ou da 
busca de um nome numa lista telefônica. Dessa forma, fica evidente 
que desde a Antiguidade o homem imagina e executa algoritmos em 
sua rotina. E com relação a algoritmos mais complexos, como os que 
são executados mecanicamente por computadores?
Como você ordena 
comumente um baralho? 
 
Será que você utiliza algum 
algoritmo para isso?
12
Algoritmos e Estrutura de Dados
Novamente, voltamos à Antiguidade: desde há muito tempo, o 
homem executa algoritmo não tão simples para realizar operações 
com números. Considera-se como primeiro exemplo de algoritmo 
não-trivial um procedimento descrito entre 400 e 300 antes de Cristo 
pelo matemático grego Euclides para encontrar o maior divisor comum 
entre dois números. O algoritmo de Euclides é hoje universalmente 
conhecido. Vamos ainda nesse módulo entender como ele funciona.
Da Antiguidade até hoje, além da invenção de novos algoritmos, 
o homem também se preocupou com a invenção de máquinas para 
executá-los. O exemplo mais clássico é o ábaco, que permite a 
realização de operações aritméticas com rapidez (de fato, a realização 
de uma operação aritmética obedece a um algoritmo, e tais algoritmos 
são introduzidos às crianças nos primeiros anos escolares). No 
século XIX, alguns nomes se destacam por tentativas de construção 
de máquinas executoras de instruções: o francês Joseph Jacquard, 
o inglês Charles Babbage e o estadunidense Herman Hollerith. Em 
particular, a máquina de Hollerith, que usava cartões perfurados, 
foi usada com sucesso no censo dos Estados Unidos de 1890. Tais 
máquinas foram precursoras dos computadores modernos.
Figura 1 - Três máquinas projetadas para auxiliar na execução de algoritmos: um ábaco, 
uma máquina de cartão perfurado e um computador.
O passo definitivo para a compreensão do conceito de algoritmo, 
tal como o conhecemos atualmente, foi dado no início do século 
XX. O termo “procedimento efetivo”, que podemos entender como 
“método possível de realizar por meios mecânicos”, pairava no ar 
entre os matemáticos dessa época. Mas uma definição formal dessa 
idéia estava em falta. Questões como “encontre um procedimento 
efetivo para testar se uma fórmula é logicamente válida“ estavam 
13
Algoritmos e Estrutura de Dados
na moda, mas ficavam sem resposta. Mesmo antes do surgimento 
dos computadores, o matemático britânico Alan Turing imaginou um 
dispositivo abstrato que supriu essa lacuna. Esse dispositivo, hoje 
conhecido como máquina de Turing, apesar de bastante simples, é 
equivalente a qualquer computador real. E a pesquisa ao longo do 
século XX mostrou que todos os algoritmos conhecidos até então, 
desde o algoritmo de Euclides, podem, após uma certa transformação, 
ser vistos como uma máquina de Turing.
Essa descoberta de Turing foi uma das mais importantes para a 
Computação. Com ela, a noção de algoritmo ganhou uma definição 
formal razoável e cresceu em importância. O surgimento dos 
computadores digitais, poucos anos em seguida, veio em boa hora.
A tese de Church
As máquinas de Turing são atualmente aceitas como o modelo descritivo do 
conceito de algoritmo. No entanto, a discussão a respeito desse conceito não 
está concluída. Quem garantirá que a idéia vaga de procedimento efetivo 
corresponde de fato às máquina de Turing? Existirá na Natureza algum 
procedimentomecânico que não possa ser realizado por uma máquina 
de Turing? Essa questão, que beira a filosofia, é conhecida como tese de 
Church, em homenagem ao lógico Alonzo Church cujas pesquisas foram 
bastante importantes para a nascente Ciência da Computação.
3. Algoritmos versus programas
Às vezes os termos algoritmo e programa são usados como 
sinônimos. De fato, algoritmos hoje estão tão associados a 
computadores que costumamos pensar neles como um programa, 
que escrevemos numa linguagem como o C ou o Java. Mas há uma 
diferença aqui. Como acabamos de estudar, a ideia de algoritmo 
independe da existência de computadores digitais ou de linguagens 
de programação. Euclides certamente não pensava em computadores 
quando imaginou seu método para determinar o maior divisor comum 
entre dois números. Podemos executá-lo com lápis e papel.
Um programa é uma sequência de instruções em determinada 
linguagem de programação. Assim, programas em linguagens 
diferentes normalmente são diferentes, pois obedecem a regras de 
sintaxe distintas. Mas programas diferentes podem funcionar de 
maneira análoga. De fato, tipicamente um programa codifica algum 
14
Algoritmos e Estrutura de Dados
algoritmo. Dizemos que o algoritmo foi implementado. Um algoritmo 
pode ser implementado em linguagens diversas, de maneiras diversas. 
O algoritmo é o mesmo. A linguagem é preferência nossa.
Um algoritmo pode ser implementado em diversas linguagens.
Uma profissão muito em voga entre os egressos de um curso de 
Computação é a de programador. Um programador é um especialista 
em escrever programas de computador e geralmente conhece 
diversas linguagens de programação e tecnologias. Um programador, 
no entanto, não é necessariamente um projetista de algoritmos. Uma 
boa analogia aparece ao se pensar em compositores e pianistas. O 
pianista não é necessariamente um compositor, mas é especialista 
em realizar aquilo que o compositor imagina. E o compositor não é 
necessariamente um pianista exímio – apesar de existirem exemplos 
de grandes compositores que também eram bons pianistas, e de bons 
projetistas de algoritmos que programam muito bem. Um não pode 
prescindir do outro: um algoritmo complicado, projetado para realizar 
maravilhas em um computador, permanecerá no mundo das ideias 
sem um programador que o faça existir no mundo real.
Atualmente, a busca de algoritmos eficientes tornou-se uma área de 
pesquisa bastante ativa, especialmente na academia, mas também na 
indústria. Na prática, todas as aplicações de computadores podem se 
beneficiar com o resultado da pesquisa de novos algoritmos. Para citar 
alguns exemplos característicos: criptografia, sistemas operacionais, 
computação gráfica e bancos de dados são subáreas da Computação 
que dependem amplamente de algoritmos. Ademais, essa pesquisa 
esconde alguns dos problemas mais desafiadores em Computação. 
Existem problemas para os quais nenhum algoritmo eficiente é 
15
Algoritmos e Estrutura de Dados
conhecido. Para alguns deles, a execução dos melhores algoritmos 
conhecidos, no melhor computador atual, consumiria séculos para 
terminar. E há pior: para determinados problemas, nenhum algoritmo 
é possível (tais problemas são chamados de indecidíveis) Esses 
estudos ilustram bem os limites dos computadores.
Em suma, sempre que fizermos, com um computador, uma busca 
na Internet, ou o desenho de um gráfico, podemos imaginar que por 
trás da interface gráfica com o usuário, do aplicativo em execução, do 
processador, encontramos a execução de muitos algoritmos. Projetar 
bons algoritmos é, portanto, essencial para garantir a realização de 
tarefas com eficácia num computador. É pelo menos tão importante 
quanto o aprendizado de linguagens de programação.
Conheça Mais
Computadores nem são tão antigos, mas alguns deles já estão 
virando objeto de museu. Você pode consultar muitos fatos sobre a 
história dessas máquinas em
 http://www.computerhistory.org/
Para uma fonte em Língua Portuguesa, consulte
 http://www.ime.usp.br/~macmulti/historico/
Um dos mais ilustres cientistas da Computação é o estadunidense 
Donald Knuth. Além de ser responsável por muitas contribuições 
importantes ao estudo de algoritmos e programação de computadores, 
ele vem trabalhando desde a década de 1970 numa coleção 
enciclopédica chamada “The Art of Computer Programming”. Nos 
volumes já publicados – três no total – muitos dos principais algoritmos 
e técnicas de programação conhecidos são tratados com detalhes, e 
notas históricas bastante interessantes são discutidas. Mais detalhes 
na página do autor:
 http://www-cs-faculty.stanford.edu/~uno/taocp.html
Uma referência recente e bastante completa sobre algoritmos é o 
livro
 Cormen, T; Leiserson, C; Rivest, R; Stein, C. Algoritmos - 
Teoria e Prática. São Paulo: Ed. Campus, 2002.
16
Algoritmos e Estrutura de Dados
Você Sabia?
Você sabia que o termo holerite, usado informalmente como 
sinônimo de demonstrativo de vencimentos, vem do nome do 
engenheiro estadunidense Herman Hollerith?
Hollerith foi o inventor de uma máquina de cartões perfurados que 
ajudou na execução do censo dos Estados Unidos em 1980. Essa 
máquina foi uma das precursoras dos computadores programáveis e 
a fusão de sua empresa com outras companhias deu origem à IBM.
Webquest 1: História da Computação
Vamos aprofundar nosso conhecimento sobre a História da 
Computação através de uma Webquest. Nela, você pesquisará na 
Internet contribuições importantes para a evolução dessa ciência.
Introdução
Conforme estudamos, o homem elabora algoritmos há muito 
mais tempo do que o advento dos computadores digitais. Nessa 
tarefa, faremos um panorama da evolução desse conceito de forma 
esquemática.
A Ciência da Computação é considerada uma ciência recente. 
No entanto, busque notar durante a elaboração da sua pesquisa 
que algumas ideias fundamentais para essa ciência já existiam nos 
séculos passados.
A Tarefa
Agora, você será o historiador.
Sua missão é elaborar uma linha do tempo, especificando datas, 
personagens e descobertas importantes para a Computação. 
Você pode, por exemplo, iniciar com o algoritmo de Euclides, 
especificando brevemente quem foi Euclides, e em que obra esse 
algoritmo foi descrito. Em seguida, fazer o mesmo para outras 
contribuições, incluindo aquelas consideradas no texto desse módulo. 
Personagens como Jacquard, Babbage e Turing devem figurar nessa 
linha do tempo – mas você pode incluir outros não mencionados que 
17
Algoritmos e Estrutura de Dados
encontrar.
Por exemplo, tente descobrir quem foi Ada Lovelace (ela é 
considerada a primeira programadora da História). Também tente 
descobrir alguns nomes importantes que fizeram contribuições 
recentes ao estudo de algoritmos, e quais foram essas contribuições. 
Você pode, por exemplo, fazer sua pesquisa entre os vencedores do 
prêmio Turing, um dos mais prestigiosos em Computação. Certamente 
você se deparará com alguns dos autores dos livros mencionados 
nesse curso.
O Processo
Para elaborar sua linha do tempo, consulte os endereços 
sugeridos na seção “Conheça mais”. Você pode também acessar 
outros endereços: certamente uma busca na Internet vai revelar 
muitos locais interessantes para pesquisa. Busque por palavras-
chave de natureza diversa: nomes de personagens (Babbage, 
Turing), de conceitos (algoritmo), de algoritmos particulares (algoritmo 
de Euclides, algoritmos de ordenação), etc. Organize sua linha do 
tempo especificando, para cada evento interessante, personagens 
envolvidos (se algum), contribuição e influência. Se encontrar relações 
entre eventos, não deixe de mencioná-las.
Compare sua linha do tempo com a de seus colegas, e preencha 
lacunas, de maneira a torná-la bastante completa.
Avaliação
Na avaliação da atividade, serão observados os seguintes 
critérios:
a) A riqueza de informações da linha do tempo (que deve incluir 
pelo menos os eventos mencionados neste capítulo).
b) A compreensão do significado e importância de cada eventoespecificado.
c) A competência do educando nos processos de discussão on-
line sobre as etapas de pesquisa e de produção da atividade. 
Será avaliada a participação significativa do aluno nos fóruns 
de discussão orientados pelos tutores.
18
Algoritmos e Estrutura de Dados
Conclusão
Caro(a) aluno(a),
A elaboração dessa linha do tempo não apenas o levou a adquirir 
uma visão panorâmica da História da Computação, mas também 
o ajudou a compreender e fixar a noção de algoritmo, que é muito 
anterior ao advento dos computadores.
Procure conversar mais a respeito com amigos que trabalham 
com Informática. Descubra se essa noção também é clara para eles. 
Observe ao seu redor, atente para suas tarefas, e busque identificar 
quais escondem uma sequência de passos semelhante a um algoritmo. 
Essa preparação é importante para o próximo capítulo.
Referências
As referências mencionadas na seção “Conheça mais” trazem 
muitas informações a respeito da História da Computação. Para outra 
fonte consulte também
 http://www.cs.uwaterloo.ca/~shallit/Courses/134/history.html
A página do professor Waldemar Setzer da Universidade de São 
Paulo contém muitos artigos e apresentações sobre a história dos 
computadores e assuntos relacionados. Consulte:
 http://www.ime.usp.br/~vwsetzer/apresentacoes/
Vamos revisar?
Neste capítulo, descobrimos que a noção de algoritmo, que é 
subjacente ao processamento realizado por computadores, tem 
suas origens na Antiguidade. Vimos que ao longo da História a 
humanidade preocupou-se em projetar algoritmos e máquinas para 
executá-los, culminando com a invenção dos computadores no século 
XX. Também compreendemos que a noção de algoritmo é bastante 
natural, e pode ser identificada com a de receita para a realização de 
uma tarefa. No entanto, essa noção intuitiva só recebeu uma definição 
formal aceitável no início do século XX. Finalmente, consideramos 
que algoritmos e programas são conceitos distintos, mesmo que na 
prática sejam tratados como sinônimos.
19
Algoritmos e Estrutura de Dados
Capítulo 2 – Exemplos de 
algoritmos
Vamos conversar sobre o assunto?
Algoritmos, conforme vimos no capítulo anterior, são sequências de 
instruções, ou receitas bem definidas, projetadas para a manipulação 
ou transformação de um certo conjunto de dados. Com essa noção 
intuitiva em mente, vimos que ao longo da História o homem se 
preocupou em inventar algoritmos e máquinas para executá-los.
Recorde nosso objetivo principal nessa disciplina: adquirir a 
capacidade de imaginar nossos próprios algoritmos para problemas 
práticos, descobrir ferramentas poderosas para o projeto de algoritmos 
eficientes e implementá-los em um computador. Vamos, nesse 
capítulo, dar um passo adicional em direção a esse objetivo.
Começaremos analisando o algoritmo de Euclides, que calcula o 
máximo divisor comum entre dois números inteiros. Vamos descrever 
esse algoritmo em Português, de maneira muito próxima a uma receita 
de bolo. Também vamos simulá-lo, ou seja, executar essa receita. 
Em seguida, vamos estudar uma linguagem para a apresentação 
de algoritmos, mais próxima das linguagens de programação 
usadas para implementações em computadores. Veremos que essa 
linguagem não é tão diferente da receita de bolo, mas é, ao mesmo 
tempo, suficientemente próxima das linguagens de programação para 
uma tradução quase imediata. Essa linguagem, que chamamos de 
Portucal, permite a descrição de um algoritmo de maneira precisa, 
usando algumas poucas palavras em Língua Portuguesa.
Antes de prosseguirmos nosso estudo nos capítulos seguintes, 
vamos exercitar bastante nossa experiência de criação de algoritmos 
usando a linguagem Portucal. Veremos posteriormente que essa 
prática é a essência do desenvolvimento de algoritmos e estruturas 
de dados: não pensar em linguagens de programação ou tecnologias 
específicas, mas pensar no problema e em métodos eficientes para 
resolvê-lo. Dessa forma, estudaremos exemplos, desenvolveremos 
algoritmos para alguns problemas interessantes, e aprenderemos a 
demonstrar que nosso algoritmo é correto. Bom estudo!
20
Algoritmos e Estrutura de Dados
1. O algoritmo de Euclides
Dentre as contribuições dos gregos da Antiguidade Clássica à 
nossa civilização, a Matemática estudada por eles é certamente uma 
das mais importantes. Muitas propriedades dos números e dos objetos 
geométricos que conhecemos atualmente vieram de indagações 
filosóficas dos gregos, e foram, felizmente, preservadas em obras 
escritas que sobreviveram à passagem do tempo.
Uma dessas obras é a coleção Elementos, do matemático Euclides. 
O ponto de partida do presente capítulo é a análise de dois assuntos 
expostos nos livros 7 e 10 dessa coleção. 
O primeiro é o conceito de divisão inteira, que é largamente 
difundido no ensino de Matemática. Dados dois números inteiros, 
que podemos chamar de a e b, e onde b é diferente de 0, sempre é 
possível encontrar dois números inteiros q (chamado quociente) e r 
(chamado resto) que satisfazem
0 ≤ r < b, ou seja: r é menor ou igual a zero e, ao mesmo tempo, 
r é menor que b
a = bq + r, ou seja: o valor de a será igual ao valor de b multiplicado 
pelo quociente (da divisão de a por b) somado com o resto (também 
da divisão de a por b)
Por exemplo, para
a = 16, b = 6
o quociente e o resto são
q = 2, r = 4
Além disso, o quociente e o resto são únicos: cada par de números 
tem seu quociente e seu resto; não existem dois outros números que 
satisfaçam essas propriedades na divisão de a por b. Se o resto r é 
igual a zero, então temos uma situação de divisão exata. Neste caso, 
dizemos que b divide a, ou que a é divisível por b.
O segundo conceito é o máximo divisor comum. Dados dois inteiros 
m e n, existe um inteiro d com as seguintes propriedades:
1. d divide m e d divide n;
2. não existe inteiro maior do que d e que divide m e n.
Em outras palavras: d é o maior inteiro que divide tanto m quanto 
21
Algoritmos e Estrutura de Dados
n. Daí o nome “máximo divisor comum”. O máximo divisor comum 
dos números 16 e 6 é 2 porque qualquer outro número inteiro maior 
que 2 não divide ao mesmo tempo 16 e 6. Por exemplo, o número 4 
divide 16 (quociente = 4 e resto = 0), mas não divide 6 (quociente=1 
e resto=2). Já o número 3 divide 6 (quociente=2 e resto=0), mas não 
divide 16 (quociente=5 e resto=1).
Nos Elementos, Euclides descreveu um método para calcular o 
máximo divisor comum entre dois números (aparentemente, esse 
método já era conhecido antes dessa época). Trata-se de uma receita 
simples, composta por uma sequência de instruções precisas que 
podemos executar mecanicamente com lápis e papel – um algoritmo, 
portanto. Vejamos uma descrição desse algoritmo em Português. Em 
seguida, vamos analisá-la com cuidado.
Algoritmo de EUCLIDES
Encontra o máximo divisor comum entre dois inteiros m e n
Passo 1: Adote como valores de x e y os valores de m e n, respectivamente:
Passo 2: Adote como valor de r o resto da divisão do valor de x pelo valor de y;
Passo 3: Adote como novo valor de x o valor de y, e como novo valor de y o valor 
de r;
Passo 4: Se o valor de r é nulo, então o valor de x é o máximo divisor comum 
procurado, e o cálculo termina; senão, volte para o Passo 2.
Essa receita de quatro passos, a despeito de sua simplicidade, já 
introduz alguns conceitos que nos acompanharão ao longo de toda a 
disciplina. 
Primeiro, devemos estar atentos ao fato de que a receita lida com 
valores desconhecidos de antemão. De fato, não desejamos saber 
o máximo divisor comum entre 16 e 6, ou outro par de números em 
particular. Desejamos que nosso método seja capaz de encontrar uma 
resposta para qualquer par de números inteiros.
Esses valores são fornecidos pelo usuário do algoritmo. Como 
se costuma dizer, eles são a entrada do algoritmo, ou ainda, uma 
instância do problema de encontrar o máximo divisor comum. No 
caso, esses valores são representados pelas letras m e n.
Além de m e n, o algoritmo usa as letras x, y e r, e os valoresrepresentados por elas são modificados constantemente ao longo de 
22
Algoritmos e Estrutura de Dados
uma execução do algoritmo. Tais letras que representam valores, que 
podem ser usados, intercambiados, modificados, são chamadas de 
variáveis. Na execução de um algoritmo em um computador digital, 
as variáveis representam posições de memória contendo os valores 
manipulados.
Notamos também que o algoritmo usa no Passo 2 a operação de 
divisão inteira sem descrever como ela é executada. Embora a divisão 
inteira também seja um algoritmo, podemos assumir que possuímos 
uma calculadora que a realize. Ou seja, o algoritmo conta com um 
recurso externo, outro algoritmo, capaz de executar a divisão inteira.
Nós dissemos o que o algoritmo de Euclides faz – calcula o 
máximo divisor comum entre dois números – mas não provamos que 
ele realmente funciona. É importante ter em mente que a prática do 
desenvolvimento de algoritmos exige um certo rigor matemático para 
ser bem-sucedida: só vamos nos convencer a usar um algoritmo 
mediante uma prova de sua corretude, ou seja, uma prova de que ele 
sempre dá a resposta correta.
Não vamos aqui entrar nesses detalhes para o algoritmo de 
Euclides, embora seja um bom exercício tentar demonstrar que ele 
funciona. No momento, nossa preocupação será o entendimento 
de cada um dos passos desse algoritmo, e para isso faremos uma 
simulação.
Simular significa assumir o papel do computador, ou de qualquer 
máquina executora de algoritmos. De posse de lápis e papel, 
vamos acompanhar a execução do algoritmo de Euclides para uma 
determinada instância do problema. Como já conhecemos o resultado 
para o máximo divisor comum dos números 16 e 6, este é um bom 
exemplo inicial. Faremos isso anotando os valores das variáveis em 
uma tabela: cada coluna corresponde a uma variável, e as linhas 
indicam os valores assumidos sucessivamente por essas variáveis.
x y r
16 6 4
6 4 2
4 2 0
2 0
23
Algoritmos e Estrutura de Dados
Ao final da execução, quando a variável r assume o valor nulo 
(conforme é mostrado no quarto passo do algoritmo de Euclides), 
a variável x assume o valor 2 e, portanto, este é o máximo divisor 
comum entre 16 e 6. Dizemos esse é o valor devolvido ou retornado 
pelo algoritmo. 
Para ilustração de que o algoritmo funciona para qualquer outro 
par de inteiros, vamos simulá-lo novamente, agora com uma instância 
do problema um pouco mais complexa, os números 348 e 156. 
x y r
348 156 36
156 36 12
36 12 0
12 0
Ao final da execução, a variável x vale 12, que é o máximo divisor 
comum entre 348 e 156. A fim de se familiarizar com o algoritmo, uma 
boa ideia é simular o algoritmo para outros valores de x e y.
2. A linguagem Portucal
Mesmo que a linguagem natural seja muitas vezes um veículo 
eficiente para intercâmbio de informação, ela não é adequada para 
a programação de computadores, devido às suas ambiguidades 
e imprecisões. Por exemplo, embora a descrição feita na seção 
anterior do algoritmo de Euclides seja suficientemente precisa para 
que possamos compreendê-lo e simulá-lo, não temos meios para 
instruir um computador, de forma direta, a executar qualquer um de 
seus passos. De fato, as linguagens adequadas para a programação 
de computadores possuem um número finito (e em geral bastante 
restrito) de comandos e construções, além de regras precisas de 
sintaxe (as regras estruturais que todo programa deve respeitar) que 
não admitem exceções, ao contrário das possibilidades expressivas 
praticamente infinitas da linguagem natural.
Ainda que nosso objetivo não seja o aprendizado de uma linguagem 
de programação específica, precisamos nos adequar a essa realidade 
se quisermos ter alguma esperança de implementar futuramente nossos 
24
Algoritmos e Estrutura de Dados
algoritmos. Por essas razões, não vamos usar nem o Português, nem 
uma linguagem de programação corrente para descrever algoritmos. 
Nosso compromisso será o uso de uma linguagem que chamaremos 
de Portucal. O Portucal não é muito diferente das linguagens de 
programação comuns, de forma que saberemos identificar suas 
estruturas independentemente da linguagem em que já tivermos 
programado. Ele é, na verdade, projetado para ser mais simples do 
que a maioria das linguagens de programação, pois desejamos nos 
concentrar na estrutura dos algoritmos que estudaremos, e não em 
detalhes técnicos de implementação.
Por exemplo, as construções do Portucal são muito parecidas 
com as da linguagem de programação Pascal, e a tradução de 
um algoritmo descrito em Portucal para um programa em Pascal é 
quase imediata porque precisamos, na maioria das vezes, apenas 
traduzir as instruções e fazer pequenas adaptações estruturais. O 
Portucal é também bastante intuitivo: um algoritmo em Portucal pode 
ser explanado naturalmente em Português. Outra característica do 
Portucal é o uso de comandos em Português. Daí seu nome:
Portucal = Português + Pascal
A prática do projeto de algoritmos utilizando Portucal é o melhor 
método para a fixação das estruturas dessa linguagem. Como ponto 
de partida, apresentamos a seguir novamente o algoritmo de Euclides, 
agora em Portucal. Tente associá-lo com a descrição desse algoritmo 
na seção anterior:
25
Algoritmos e Estrutura de Dados
algoritmo EUCLIDES (m, n) 
x ← m
y ← n
repita
 r ← x mod y
 x ← y
 y ← r
até que r = 0
devolva x
► Calcula o máximo divisor comum entre dois 
inteiros positivos
Embora essa nova descrição possa parecer muito mais pobre, 
vemos que todos os elementos da receita de quatro passos estudada 
na seção anterior estão presentes. As flechas ← representam as 
instruções do tipo ”adote” ou “assuma o valor de”, que realizam a troca 
de valores de variáveis. O teste realizado no Passo 4 do algoritmo 
de Euclides, que induz à repetição de um trecho do algoritmo 
segundo uma condição sobre o valor da variável r, é transformado no 
comando
repita ... até que
Esse comando executa o bloco de comandos
 r ← x mod y
 x ← y
 y ← r
até que o valor da variável r seja igual a zero. Precisamos atentar 
para o fato de que o valor de r é alterado no interior desse bloco de 
comandos, e podemos provar que ele diminuirá a cada iteração – é 
por isso que o algoritmo sempre termina. O comando “mod” calcula 
o resto da divisão de x por y. Esse comando faz parte do Portucal, e 
pode, portanto, ser usado livremente.
A seguir, apresentamos uma descrição mais completa da linguagem 
Portucal. Essa descrição deve ser encarada como um manual de 
referência para consulta. Em seguida, vamos estudar outros exemplos 
de algoritmos escritos em Portucal e praticar com alguns exercícios. 
26
Algoritmos e Estrutura de Dados
Você pode passar diretamente a essa prática, e consultar a descrição 
da linguagem apenas quando julgar necessário ou quiser ter certeza 
da compreensão correta de algum conceito, instrução ou estrutura 
apresentada nos exemplos.
• Estrutura de um algoritmo
 Todo algoritmo descrito em Portucal começa com a palavra 
“algoritmo”, seguida por um nome com que o batizamos – no 
exemplo acima, “Euclides” – e uma lista de variáveis (rótulos 
para valores) que representam a instância do problema a 
ser processada pelo algoritmo – ou seja, a “entrada”, ou os 
“parâmetros” do algoritmo.
• Comentários
 É considerada uma boa prática de programação documentar 
o código com comentários que explicam seu funcionamento. 
Não fugiremos a essa regra. Todo algoritmo deve conter pelo 
menos um comentário: uma descrição do que ele faz. Mas é 
útil inserir outros comentários para explicar as passagens mais 
delicadas. Comentários não representam nenhuma modificação 
na execução do algoritmo, e podem ser inseridos em qualquer 
posição. Em Portucal, comentários começam com o símbolo ►
• Palavras reservadas
 Toda linguagem possui um determinado conjunto de comandos, 
que indicam uma ação precisa durante a execução do 
algoritmo, e não podem ser usados como nomes de variáveis. 
Tais comandos são o que chamamos de palavras reservadasda linguagem. Em Portucal, faremos a convenção de sublinhar 
as palavras reservadas (que descrevemos mais embaixo). 
• Variáveis e atribuição
 Conforme exemplificado pela descrição do algoritmo de Euclides 
na seção anterior, um algoritmo manipula dados através de 
variáveis, realizando operações e intercambiando os valores 
armazenados. Em Portucal, variáveis podem representar 
números inteiros, fracionários (com um número fixo de casas 
decimais), os valores booleanos V ou F, caracteres ou cadeias 
de caracteres, sendo que o tipo de dado poderá ser inferido do 
contexto. A atribuição do valor de uma variável x a uma variável 
y é designada por uma seta: x ← y. Atribuições podem ser mais 
27
Algoritmos e Estrutura de Dados
complexas: em geral, o lado direito da atribuição pode ser uma 
expressão qualquer. Neste caso, o resultado da expressão é 
armazenado na variável x.
• Vetores e matrizes
 Vetores e matrizes são as primeiras estruturas de dados que 
utilizaremos intensamente em nosso curso. Vamos recordar que 
um vetor é uma tabela indexada, contendo em cada posição 
um valor – um número inteiro, por exemplo. Uma matriz é uma 
tabela duplamente indexada, ou seja, de duas dimensões. Em 
Portucal, podemos representar vetores e matrizes em variáveis. 
Se A representa um vetor, e i é uma variável contendo um 
número inteiro, obtemos o valor de A na posição i através da 
construção A[i]. Se A é uma matriz, e i e j são variáveis inteiras, a 
posição (i, j) (ou seja, “linha i, coluna j”) de A pode ser acessada 
com a construção A[i, j]. Faremos a convenção de que um vetor 
começa na posição 1, e usamos a palavra reservada tamanho 
seguida do nome de um vetor para representar a dimensão 
(número de posições) desse vetor. Assim, a última posição de 
um vetor A é o número inteiro tamanho(A).
• Entrada e saída
 Em Portucal, dispomos das palavras reservadas leia e escreva 
para, respectivamente, solicitar dados ao usuário do algoritmo 
e escrever valores de variáveis em algum meio de saída. 
Ambos os comandos são seguidos por uma lista de variáveis, 
separadas por vírgula. Por exemplo, leia x, y solicita ao usuário 
a leitura de dois valores (dois números inteiros, por exemplo). 
Também podemos considerar o algoritmo como uma caixa 
preta que calcula um valor e o “envia” ao mundo externo, ao 
invés de escrevê-lo em algum meio de saída. Dito de outra 
forma, podemos considerar os algoritmos em Portucal como 
funções de linguagens de programação. Para devolver os 
dados construídos na execução do algoritmo, usamos a palavra 
reservada devolva.
• Operadores aritméticos
 Em Portucal, podemos realizar as quatro operações aritméticas 
entre números através dos símbolos + (soma), - (diferença), * 
(produto) e / (divisão). Também podemos realizar as operações 
de quociente e resto da divisão inteira com as palavras 
28
Algoritmos e Estrutura de Dados
reservadas div e mod, respectivamente. Os operandos são 
sempre valores numéricos, e o resultado de uma operação 
aritmética é um valor numérico. Parênteses podem ser usados 
livremente para facilitar a leitura de expressões aritméticas.
• Operadores relacionais
 São os operadores que permitem a comparação de valores 
numéricos. São eles: < (menor), <= (menor ou igual), > (maior), 
>= (maior ou igual), = (igual), <> (diferente). O resultado de 
uma expressão envolvendo operadores relacionais é um valor 
booleano (V ou F).
• Operadores lógicos
 Usamos os operadores E, OU, NãO para a construção de 
expressões lógicas envolvendo outras expressões lógicas ou 
variáveis booleanas. Por exemplo, (5 < 3) ou (2 * 3 = 6) é V, e 
não ((5 < 3) ou (2 * 3 = 6)) é F. Parênteses podem ser usados 
livremente para facilitar a leitura de expressões booleanas.
• Comando condicional
 Podemos desviar a execução de um algoritmo segundo 
o resultado de uma expressão lógica com o comando 
condicional
se (exp) então C1 senão C2
 Se o resultado da expressão lógica “exp” é verdadeiro, a 
execução continua no bloco de comandos C1; senão, ela 
continua no bloco de comandos C2.
• Comandos de repetição
 Vamos recordar que, no algoritmo de Euclides escrito em 
Portucal, usamos o comando de repetição
repita C até que exp
 Esse comando começa executando o bloco de comandos C, 
em seguida testa a expressão booleana exp, e continua a 
executar o mesmo bloco de comandos até que a condição se 
torne verdadeira. Em Portucal, também dispomos do comando 
de repetição
enquanto exp faça C
29
Algoritmos e Estrutura de Dados
 A primeira ação desse comando é testar a expressão booleana 
exp. Se a expressão for verdadeira, o bloco de comandos 
C é executado. Em seguida, um novo teste da expressão é 
realizado, e o bloco C é executando repetidamente enquanto o 
resultado for verdadeiro.
 Em diversas situações, é útil controlar o número de vezes em 
que um bloco de comandos deve ser executado. Essa tarefa é 
realizada pelo comando
para i ← k até n faça C
 que repete o bloco de comandos C para cada valor da variável i 
entre k e n, com incremento de uma unidade a cada iteração. Por 
outro lado, se k é maior do que n, a variável i é decrementada a 
cada execução.
• Procedimentos
 Como ocorre com procedimentos e funções em linguagens de 
programação, um algoritmo em Portucal pode ser executado no 
interior de outro algoritmo. Invoca-se um algoritmo escrevendo 
o seu nome seguido por uma lista de parâmetros entre 
parênteses. Variáveis simples são passadas por valor, ou seja, 
toda alteração dos parâmetros no interior de um algoritmo é 
invisível externamente. Por outro lado, a passagem de vetores 
e matrizes é por referência, ou seja, alterações nesses objetos 
no interior de um algoritmo são visíveis externamente.
Apresentação de algoritmos
Quem programa com frequência sabe que a leitura de um programa 
desorganizado pode ser penosa. Por isso, é importante caprichar na 
apresentação. Não fugiremos à regra. O uso de identação para a evidência 
de blocos de comando subordinados a estruturas de controle deve ser 
sistemático. Também é importante documentar nossos algoritmos, ou 
seja, escrever comentários explicando o funcionamento das partes mais 
delicadas. Finalmente, é importante descrever, também com comentários, 
o que o algoritmo faz. Observe como essas práticas são seguidas nos 
exemplos estudados nesse capítulo. Ao escrever seus algoritmos e 
programas, procure se habituar a seguí-las também.
30
Algoritmos e Estrutura de Dados
Aprenda Praticando
Ao longo dessa disciplina, escreveremos muitos algoritmos em 
Portucal. Vamos, então, nos familiarizar com essa linguagem através 
de alguns exemplos. Ao mesmo tempo, vamos discutir um pouco mais 
sobre práticas importantes no desenvolvimento de algoritmos.
Em cada exemplo, deparamo-nos com o enunciado de um 
problema. O objetivo é escrever um algoritmo que resolve o problema, 
da mesma forma que desenvolvíamos programas nas disciplinas 
introdutórias de programação.
Às vezes, é necessário ir além do projeto de um algoritmo para 
convencer que ele funciona. Em dois exemplos, vamos discutir 
rapidamente a corretude do algoritmo proposto. Dizemos que um 
algoritmo é correto se ele dá a resposta esperada para todo valor 
possível da entrada. A corretude de um algoritmo é um fato que deve 
ser demonstrado com uma prova matemática. A estratégia fundamental 
nesse tipo de questão é a utilização de certas propriedades que se 
mantêm válidas ao longo da execução, os invariantes.
Antes de ler a solução, tente resolver o problema, e compare 
seu algoritmo com o proposto. Comumente, um problema pode ser 
resolvido de diversas maneiras. Como você poderia argumentar que 
sua solução é melhor? Ou como você poderia melhorá-la?
Problema 1
Escreva um algoritmo que calcula o fatorial de um número.
Solução: Vamos recordar que o fatorial de um número n é definido 
por
n! = n x (n – 1) x ... x 2 x 1
Nosso algoritmo deve realizar uma multiplicação de n fatores. 
Para isso, uma possibilidade é utilizar o laço enquanto. Os resultadosparciais do produto são armazenados em uma variável, que após a 
última iteração é igual ao fatorial de n.
31
Algoritmos e Estrutura de Dados
algoritmo FATORIAL (n)
fat ← n
enquanto n > 1 faça
 n ← n - 1
 fat ← fat * n
devolva fat
► Calcula o fatorial de um inteiro positivo
Problema 2
Escreva um algoritmo que calcula a soma de um vetor.
A soma de um vetor A de tamanho n é definida por
A[1] + A[2] + ... + A[n]
Solução: O seguinte algoritmo resolve o problema.
algoritmo SOMA (A)
s ← 0
para i = 1 até tamanho (A) faça
 s ← s + A[i]
devolva s
► Calcula a soma de um vetor A
Como esse algoritmo é simples, podemos nos convencer 
rapidamente de que ele é correto, ou seja, sempre devolve a soma do 
vetor A. Em geral, não é tão fácil enxergar que um algoritmo é correto, 
e nesses casos precisamos de uma demonstração matemática 
baseada nos invariantes do algoritmo. É claro que o valor da variável 
s muda ao longo da execução. No entanto, a propriedade seguinte é 
sempre verdadeira ao final de cada iteração (ou seja, após a execução 
do corpo do laço para ... até). É por isso que dizemos que ela é um 
invariante (uma propriedade que nunca muda ao longo da execução):
s = A[1] + ... + A[i]
Em particular, o invariante mostra que, ao final da última iteração, 
s é igual à soma de A.
32
Algoritmos e Estrutura de Dados
Problema 3
Escreva um algoritmo que dados um vetor de n números em ordem crescente e 
um número x, determina a posição do número x, e retorna 0 caso x não apareça 
no vetor.
Solução: Temos aqui um exemplo de problema de busca. Busca é 
uma questão fundamental em Computação, e, em muitos contextos, 
é importante que possa ser realizada com eficiência – considere por 
exemplo Bancos de Dados, onde buscas são solicitadas com muita 
frequencia. Nessa disciplina, vamos estudar em breve estruturas de 
dados projetadas para que inserções, remoções e buscas em certos 
conjuntos de dados possam ser feitas com eficiência.
Quando atacamos um problema, é comum uma solução imediata 
saltar aos olhos, mas nem sempre essa solução é a melhor (em 
termos de eficiência). A solução imediata para o problema de busca em 
questão consiste em verificar todas as posições do vetor comparando 
o conteúdo de cada uma com o valor procurado x. No entanto, essa 
solução não aproveita a informação adicional de que o vetor está 
ordenado.
Com base na ordenação, o problema pode ser resolvido de forma 
muito mais eficiente. Veremos no próximo capítulo noções sobre 
eficiência de algoritmos. Mas uma experiência que realizaremos ao 
final desse capítulo dará indícios de que as execuções do algoritmo 
que vamos descrever são menos “trabalhosas” do que as do algoritmo 
exaustivo.
Esse algoritmo se chama busca binária, e segue uma estratégia 
conhecida em Ciência da Computação como divisão e conquista. A 
idéia é dividir o problema em partes menores, ou subproblemas, e 
atacá-las em separado. Esses subproblemas, por sua vez, são casos 
menores do mesmo problema, e podem então ser resolvidos com a 
mesma estratégia, ou melhor, com o mesmo algoritmo: dividindo-os 
em problemas ainda menores.
A busca binária começa determinando o índice que aponta para 
a metade do vetor. Evidentemente, o valor procurado está ou nessa 
posição, ou na metade à esquerda do vetor, ou na metade à direita. 
Agora, chegamos à observação crucial para o projeto desse algoritmo: 
a comparação do valor procurado com o valor nessa posição mostra 
em qual metade do vetor a busca deve prosseguir. Assim, metade do 
vetor pode ser desprezada, e a busca continua na outra metade.É 
Atenção
Dizemos que 
um vetor A está 
ordenado, ou em 
ordem crescente, 
se, para todo 
índice i entre 1 e 
o tamanho de A 
menos 1, temos 
A[i] ≤ A[i + 1]. 
Como se 
define ordem 
decrescente?
Atenção
Origem do 
termo “divisão e 
conquista”
O termo “divisão 
e conquista” era 
usado no exército 
de Napoleão 
para designar 
a estratégia 
de dividir o 
exército inimigo 
em exércitos 
menores e mais 
fáceis de derrotar.
33
Algoritmos e Estrutura de Dados
aqui que a ordem crescente é utilizada: em um vetor que não está 
ordenado, não é possível concluir em qual das metades o número 
poderia estar.
De modo geral, a cada iteração do algoritmo uma região do vetor 
é analisada. Essa região é determinada por duas variáveis, esq (o 
limite inferior) e dir (o limite superior). Apenas uma dessas variáveis 
é atualizada, de acordo com a comparação do valor procurado com o 
valor na posição intermediária dessa região.
Vamos notar que na documentação do algoritmo especificamos 
que o vetor A fornecido na entrada deve estar em ordem crescente. 
Como mencionamos anteriormente, é importante especificar o que o 
algoritmo faz, e em particular, o que ele recebe, e o que ele devolve. 
O algoritmo não promete que encontra x para qualquer vetor possível. 
Mas promete que o encontra para qualquer vetor ordenado.
algoritmo BUSCA-BINÁRIA (A, x) ► Recebe um vetor A de inteiros em ordem
► crescente e um inteiro x, devolve um índice
► i tal que A[i] = x ou 0 se um tal índice
► não existe.
esq ← 1
dir ← tamanho (A) ► Supomos que A tem pelo menos
► um elemento, ou seja, dir >= 1
repita
 meio ← (esq + dir) div 2
 se x > A[meio] então
 esq ← meio + 1
 senão
 dir ← meio – 1
até que (A[meio] = x) OU (esq > dir)
se A[meio] = x então
 devolva meio
senão
 devolva 0
34
Algoritmos e Estrutura de Dados
A compreensão do busca-binária fica muito mais fácil se realizamos 
algumas simulações. Consideremos por exemplo o seguinte vetor, 
indexado de 1 a 11:
Índice 1 2 3 4 5 6 7 8 9 10 11
Valor 8 23 37 44 50 57 57 86 91 102 104
Busquemos o valor 86. O índice que aponta para o meio do vetor 
é
(esq + dir) div 2 = (1 + 11) div 2 = 6
O valor nessa posição é 57, que é menor que 86. Dessa forma, 
a parte esquerda do vetor pode ser desprezada (área mostrada em 
cinza claro):
Índice 1 2 3 4 5 6 7 8 9 10 11
Valor 8 23 37 44 50 57 57 86 91 102 104
A variável esq é então atualizada para apontar o início da segunda 
metade. Na próxima iteração, o algoritmo considera a região do vetor 
determinada por
esq = 7, dir = 11
cujo meio é o índice 9. O valor na posição 9 é 91, maior do que 86. 
Então, a metade direita da região entre 7 e 11 é desprezada (área 
mostrada em preto), e o algoritmo atualiza dir para 8:
Índice 1 2 3 4 5 6 7 8 9 10 11
Valor 8 23 37 44 50 57 57 86 91 102 104
O meio dessa região é o índice 7 (o quociente da divisão de 7 + 8 
por dois), que indica o valor 57. Esse valor é menor do que 86, então 
esq passa a valer 8 e a região à esquerda (área mostrado em cinza 
escuro) é descartada. Neste caso, a região descartada se resume ao 
índice 7 do vetor. Finalmente, ambas variáveis valem 8, e o trecho a 
pesquisar contém apenas um elemento:
Índice 1 2 3 4 5 6 7 8 9 10 11
Valor 8 23 37 44 50 57 57 86 91 102 104
Aqui o algoritmo encontra o valor e devolve o índice 8.
Atenção
Vamos recordar 
que na linguagem 
Portucal os 
vetores são 
indexados a 
partir do índice 
1. A definição da 
linguagem, na 
seção anterior, 
tem mais 
informações 
sobre vetores.
35
Algoritmos e Estrutura de Dados
Como provar que o algoritmo é correto? Ou seja, como provar 
que o algoritmo sempre encontra um índice que aponta para o 
valor procurado, se esse valor aparece no vetor, e devolve 0 caso 
contrário?
Como fizemos no exemplo anterior, a demonstração baseia-se em 
invariantes. O invariante seguinte permanece verdadeiro no início de 
cada iteração do laço repita:
se x ocorre em A, então A[esq] ≤ x ≤ A[dir]
Em outras palavras, se o valor procurado (x) existir no vetor A, o 
índice referente a sua posição estará sempre entre a primeira posição 
da região considerada e a última. Contudo, não devemos acreditar 
nessa propriedade gratuitamente: é preciso justificar, a partir das 
operações realizadas pelo algoritmo, que ela continua verdadeira 
sempre que os valores de esq e dir são alterados.
Para completar essa análise de corretude, precisamos de outra 
propriedade:
a cada iteração,esq – dir diminui.
Essencialmente, ela mostra que o algoritmo termina. A combinação 
de ambas permite concluir que o algoritmo sempre dá a resposta 
esperada.
Problema 4
Escreva um algoritmo que recebe um vetor de números inteiros, positivos ou 
negativos, e devolve um subconjunto de índices desse vetor tal que a soma 
dos números nesses índices seja igual a zero. Se um tal conjunto não existe, o 
algoritmo deve devolver o conjunto vazio.
Por exemplo, para o vetor
A = -4, -1, 6, -1, 2
o subconjunto de índices {2, 4, 5} produz uma soma igual a zero:
A[2] + A[4] + A[5] = (-1) + (-1) + 2 = 0
Neste caso, o algoritmo pode devolver o subconjunto {2, 4, 5}, ou 
qualquer outro que produza soma zero (outra possibilidade é o 
subconjunto {1, 2, 3, 4}). Para o vetor
B = 1, -2, 3, 4
nenhum subconjunto de índices resulta em zero. Neste caso, o 
algoritmo responde {}.
Atenção
Falaremos um 
pouco da Teoria 
da Complexidade 
no próximo 
capítulo.
36
Algoritmos e Estrutura de Dados
Solução: Esse problema é famoso, porque tem uma importância 
particular numa área da Ciência da Computação chamada “Teoria da 
Complexidade”. Ele é conhecido como “Soma de Subconjuntos”, do 
inglês Subset Sum.
Nossa solução para o Soma de Subconjuntos será uma busca 
exaustiva: vamos analisar todos os subconjuntos de índices do vetor, 
e para cada subconjunto, testar se a soma dos números indicados 
pelos índices é igual a 0. Por exemplo, se nosso vetor tem tamanho 
3 (e portanto é indexado de 1 a 3), nosso algoritmo verifica os 
subconjuntos
{1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}
Talvez você fique com a impressão de que essa solução não é 
muito eficiente, e isso é verdade – se o vetor for grande, há muitos 
subconjuntos para testar. Por outro lado, todas as soluções conhecidas 
para esse problema têm o mesmo defeito, ou seja, nenhum algoritmo 
eficiente é conhecido. Vamos discutir melhor esse fato no último 
capítulo desse volume.
Nosso algoritmo tem duas tarefas básicas, enumerar os 
subconjuntos de índices do vetor, e testar a soma para cada 
subconjunto. Para realizar a primeira tarefa, nossa estratégia será 
gerar todos os números binários de n dígitos, onde n é o tamanho 
do vetor. Cada número binário representa um subconjunto de índices: 
exatamente aqueles correspondentes às posições do número binário 
onde o dígito é 1. Por exemplo, se nosso vetor tem tamanho 5, o 
número binário
01101
(lido da esquerda para a direita) representa o subconjunto de índices
{2, 3, 5}.
A geração dos números binários, por sua vez, pode ser feita 
naturalmente em ordem crescente do valor representado pelo número 
– no presente exemplo, de 00000 até 11111. Ou seja, a geração do 
próximo número binário consiste em somar uma unidade (um dígito 
binário) ao número atual.
Para simplificar o entendimento do algoritmo, vamos utilizar um 
algoritmo auxiliar, chamado SOMA-UM, que realiza a soma de uma 
unidade a um número binário. Também vamos escrever esse número 
37
Algoritmos e Estrutura de Dados
em um vetor, B, armazenando cada dígito em uma posição. Por 
exemplo, o vetor
B = 1, 0, 0, 1
armazena o numero binário 1001, cuja valor é 9. Após a execução de 
SOMA-UM, esse vetor representa o número 1010, cujo valor é 10.
Para entender melhor como funciona o algoritmo auxiliar SOMA-
UM, observe a Tabela 1, que também exemplifica a representação de 
números binários em um vetor. Em seguida, apresentamos o algoritmo 
SOMA-SUBCONJUNTOS:
Valor Binário Vetor B Subconjunto 
representado
Cada subconjunto de 
índices que o algoritmo 
SOMA-SUBCONJUNTOS 
considera é representado 
por um número binário. 
O algoritmo SOMA-
UM é responsável por 
incrementar (somar um) a 
esses números. 
Cada número binário 
é representado no 
vetor B, um dígito por 
posição. O algoritmo 
SOMA-UM recebe 
um vetor com esse, e 
modifica-o, de forma 
a somar um ao valor 
representado.
O algoritmo SOMA-
SUBCONJUNTOS 
usa o vetor B para 
representar um 
subconjunto de índices: 
exatamente os índices 
cujos valores em B são 
iguais a 1
00000 B[1]=0 B[2]=0 B[3]=0 
B[4]=0 B[5]=0
00001 B[1]=0 B[2]=0 B[3]=0 
B[4]=0 B[5]=1
5
00010 B[1]=0 B[2]=0 B[3]=0 
B[4]=1 B[5]=0
4
00011 B[1]=0 B[2]=0 B[3]=0 
B[4]=1 B[5]=1
4, 5
00100 B[1]=0 B[2]=0 B[3]=1 
B[4]=0 B[5]=0
3
00101 B[1]=0 B[2]=0 B[3]=1 
B[4]=0 B[5]=1
3, 5
00110 B[1]=0 B[2]=0 B[3]=1 
B[4]=1 B[5]=0
3, 4
00111 B[1]=0 B[2]=0 B[3]=1 
B[4]=1 B[5]=1
3, 4, 5
Tabela 1: Representação de subconjuntos no algoritmo SOMA-SUBCONJUNTOS
38
Algoritmos e Estrutura de Dados
algoritmo SOMA-SUBCONJUNTOS(A) ► Decide se um vetor de inteiros A tem
► um subconjunto de índices tal que
► a soma dos números indicados é 0.
n ← tamanho(A)
para i ← 1 até n faça
 B[i] ← 0
 V[i] ← 0
► Inicialização de um vetor, B, que
► representa números binários e de um
► vetor V que representa o conjunto vazio 
repita
 soma ← 0
 m ← 0 ► Ao final da iteração, m contém o
► número de dígitos iguais a 1 no vetor B 
 SOMA-UM (B) ► Armazena em B o próximo número
► binário, em ordem de valor
para i ← 1 até n faça 
 se B[i] = 1 então
 soma ← soma + A[i]
 m ← m + 1
até que (soma = 0) OU (m = n) ► O laço termina quando um subconjunto 
► é encontrado, ou quando todos os
► subconjuntos já foram analisados
se soma = 0 então
 devolva B ► Devolve o subconjunto de índices
► encontrado
senão
 devolva V ► Devolve o conjunto vazio
Conheça Mais
A descrição do algoritmo de Euclides na Seção 1 e a definição 
da linguagem Portucal foram inspiradas no livro Aspectos Teóricos 
da Computação, dos autores Cláudio L. Lucchesi, Imre Simon, 
Istvan Simon, Janos Simon e Towasz Kowaltowski. Trata-se de 
39
Algoritmos e Estrutura de Dados
uma excelente referência em Língua Portuguesa sobre Teoria da 
Computação, e a primeira parte, sobre programação de computadores, 
contem muitos fatos sobre algoritmos que não vamos tratar em nosso 
curso. Por ser antigo, o livro encontra-se esgotado, mas os autores 
disponibilizam gratuitamente na Internet uma versão digitalizada:
 http://www.ime.usp.br/~is/atc/index.html
Nessa altura da disciplina, é uma excelente ideia resolver exercícios 
de desenvolvimento de algoritmos usando a linguagem Portucal. Por 
isso, ao final do capítulo, propomos uma atividade prática sobre o 
assunto. No entanto, você pode buscar outros problemas, e discutir 
suas soluções com seus colegas. Um bom repositório de problemas 
é o site
 http://www.ime.usp.br/~macmulti/
Você sabia?
Desenvolvimento de algoritmos e programação de computadores 
são atividades delicadas, que exigem muita atenção a todos os 
detalhes. É muito comum cometermos erros - os famosos bugs, que 
às vezes são muito sutis e de difícil identificação. Infelizmente, mesmo 
erros mínimos podem comprometer drasticamente a execução e 
produzir resultados absurdos.
A busca-binária é um exemplo de algoritmo simples cuja 
programação exige atenção redobrada. A experiência mostra que 
muitos programadores iniciantes, e às vezes até programadores 
avançados, cometem erros ao implementá-lo.
Por exemplo, uma versão da busca-binária semelhante à proposta 
neste capítulo foi implementada na biblioteca padrão da linguagem 
java e utilizada com sucesso por milhares de programadores por 
alguns anos. Essa versão teve de ser modificada após um relatório de 
erro, onde se verificou que a operação
meio ← (esq + dir) div 2
pode “estourar” (ocorrência de um “overflow”, no jargão em inglês) se 
a soma esq + dir for maior do que o limite superior de representação 
de números inteiros da linguagem.
40
Algoritmos e Estrutura de Dados
Essa operação foi corrigida para
meio ← esq + ((dir - esq) div 2),
que resolve o problema.
Do nosso ponto de vista, o algoritmo é correto, pois não estamos 
considerando detalhes de implementação. Cabe notar, no entanto, 
que a “tradução” de Portucal para uma linguagem de programação 
real pode exigir certos cuidados além da simples substituição de 
comandos.
Atividades e Orientações de Estudo
Nessa atividade prática,vamos elaborar três algoritmos em 
Portucal, e em seguida realizar uma implementação numa linguagem 
de programação.
As implementações devem ser compiladas e testadas 
intensivamente, conforme descrevemos no enunciado do Exercício 4. 
Entre em acordo com o seu professor a respeito da linguagem a ser 
utilizada.
Exercício 1
Escreva um algoritmo que testa se um vetor está em ordem crescente.
Exercício 2
Escreva um algoritmo que recebe um vetor de números inteiros e um número 
inteiro x, determina uma posição contendo x, e retorna essa posição ou 0 caso x 
não apareça no vetor.
Nesse novo exercício de busca, você não pode supor que o vetor 
está ordenado. Assim, não resta alternativa senão a realização de 
uma busca sequencial.
Exercício 3
Escreva o algoritmo soma-um usado por busca-binária. O soma-um recebe 
um vetor B contendo zeros e uns, interpretado como um número binário que 
representa um número natural x (sendo a posição menos significativa a mais à 
esquerda), e modifica o vetor de forma que o valor representado seja x + 1.
Para resolver esse exercício, precisamos recordar que a passagem 
41
Algoritmos e Estrutura de Dados
de parâmetros em Portucal é por referência quando o parâmetro é um 
vetor. Ou seja, o seu algoritmo recebe um vetor B, mas não devolve 
nada (veja mais detalhes na descrição da linguagem). A alteração 
do vetor B será visível externamente. Além disso, você deve antes 
de projetar seu algoritmo fazer uma decisão de projeto: se todas as 
posições do vetor B são iguais a 1, ou seja, o maior número possível é 
representado, como o algoritmo deve se comportar? Documente (com 
um comentário) essa sua decisão, de forma a tornar o funcionamento 
do seu algoritmo bastante claro para um usuário.
Exercício 4
Implemente o algoritmo busca-binária e o algoritmo que você projetou para 
resolver o Exercício 2 em alguma linguagem de programação. Execute ambos os 
algoritmos para diversos vetores ordenados, variando o tamanho desses vetores 
e a posição do número procurado. O que você observa?
Vamos em seguida realizar uma atividade de interação.
Primeiro, busque encontrar os invariantes importantes para a 
demonstração da corretude dos algoritmos que você projetou para os 
dois primeiros exercícios. Discuta-os com seus colegas e tutores no 
ambiente.
Em seguida, discuta no ambiente o resultado do teste de sua 
implementação. Tente chegar a uma conclusão sobre a execução 
de ambos os algoritmos, especialmente para vetores muito grandes. 
Algum deles é sempre mais rápido? Caso positivo, tente descobrir a 
razão juntamente com seus colegas.
Vamos revisar?
Neste capítulo, introduzimos uma linguagem para representação de 
algoritmos, que chamamos de Portucal. Além do aprendizado dessa 
linguagem, discutimos alguns exemplos de algoritmos, consideramos 
a importância da prática da documentação, e aprendemos que 
a corretude de um algoritmo pode ser demonstrada através da 
identificação de invariantes do mesmo.
42
Algoritmos e Estrutura de Dados
Capítulo 3 – Algoritmos 
recursivos
Vamos conversar sobre o assunto?
Talvez você já tenha ouvido falar de definições recursivas em 
Matemática, ou então já estudou alguma demonstração baseada 
no princípio da indução matemática. Em ambos os casos, a ideia 
subjacente é a possibilidade de definir um objeto através de uma 
versão “menor” do mesmo objeto.
Veremos, neste capítulo, que também é possível definir algoritmos 
que fazem uso de si mesmos durante a execução. Essa técnica, 
chamada recursão, é fundamental em programação de computadores, 
além de ser uma ferramenta poderosa para o desenvolvimento de 
algoritmos. De fato, o uso de recursão pode conduzir a algoritmos 
notavelmente simples e elegantes, especialmente se o problema 
em questão possuir uma estrutura recursiva. Vamos estudar alguns 
exemplos desses algoritmos, e compará-los com outros algoritmos 
que não utilizam recursão – os algoritmos iterativos.
Por outro lado, veremos também que a recursão não pode ser 
utilizada de maneira indiscriminada: em certas situações, um algoritmo 
iterativo é a melhor solução. 
1. O conceito de recursão
Quem nunca viu uma imagem que contém uma cópia de si própria? 
Tais imagens são chamadas de recursivas.
43
Algoritmos e Estrutura de Dados
Muitos conceitos em Matemática têm uma estrutura recursiva. 
Por exemplo, os fractais são curvas que contêm cópias menores 
semelhantes a si mesmas. Abaixo, vemos as primeiras etapas da 
construção de um fractal conhecido como “curva de Koch”.
De forma geral, nas definições recursivas de objetos matemáticos 
desejamos definir um objeto de tamanho n, onde n é um número 
natural. Se n é pequeno, definimo-lo diretamente, de forma explícita. 
Se n é grande, definimo-lo utilizando a mesma definição, mas para 
valores menores de n. 
Um exemplo é a definição de fatorial:
 n! = 0 para n = 0 ou n = 1
 n x (n – 1)! para n > 1
Essa definição pode ser imediatamente transformada num 
algoritmo que chama a si próprio para o cálculo do fatorial. Trata-se 
de um algoritmo recursivo.
algoritmo FATORIAL-REC(n) ► Calcula o fatorial de um inteiro positivo
se n <= 1 então
 devolva 1
senão
 devolva n * FATORIAL-REC(n – 1)
Como se convencer de que o algoritmo funciona? Discutiremos 
essa questão ao longo desse capítulo. Em linhas gerais, é preciso 
raciocinar de maneira indutiva. Ou ainda, é preciso confiar que o 
algoritmo resolve corretamente o problema para n – 1.
Vimos no capítulo anterior um algoritmo para o cálculo do fatorial 
que não utiliza recursão. Dizemos que esse algoritmo é iterativo, 
devido ao uso de uma estrutura repetitiva que realiza uma sequência 
de iterações. O algoritmo FATORIAL-REC também realiza todas as 
multiplicações necessárias para o cálculo de n!, mas essas operações 
estão “espalhadas” em chamadas distintas do algoritmo.
Lembrete
verbete recursão
Para definição, 
veja recursão
Lembrete
“Para fazer um 
procedimento 
recursivo é 
preciso ter fé.”
Siang Wun Song
44
Algoritmos e Estrutura de Dados
Os números de Fibonacci são outro bom exemplo de definição 
recursiva. Esses números formam uma sequência infinita de números 
naturais,
f0, f1, f2, ...
que são definidos como segue:
 f0 = 0
 f1 = 1
 fn = fn-1 + fn-2 para n > 1
Ou seja, para índices pequenos (0 ou 1), o número de Fibonacci é 
definido diretamente. Para todo índice n grande, o número de Fibonacci 
fn é a soma de dois números de Fibonacci de índices menores. Os 
primeiros números de Fibonacci são
0, 1, 1, 2, 3, 5, 8, 13, 21, 34
Como no exemplo anterior, a definição recursiva dos números de 
Fibonacci se transforma naturalmente num algoritmo recursivo que 
recebe um natural n e devolve fn.
algoritmo FIBONACCI(n) ► Calcula o n-ésimo número de Fibonacci
se n = 0 então
 devolva 0
senão
 se n = 1 então
 devolva 1
 senão
 devolva FIBONACCI(n – 1) + FIBONACCI(n - 2)
É instrutivo simular as chamadas recursivas desse algoritmo. Por 
exemplo, para n = 5, o algoritmo executa fibonacci(4) e fibonacci(3), 
esperando o resultado dessas execucções para calcular o falor de 
f5. Por sua vez,fibonacci(4) executa fibonacci(3) e fibonacci(2), 
enquanto que fibonacci(3) executa fibonacci(2) e fibonacci(1), 
e assim por diante. Tente desenhar essas chamadas recursivas de 
forma hierárquica, usando uma estrutura de árvore, e anote os valores 
calculados em cada chamada.
Atenção
Fibonacci
Os números de 
Fibonacci são 
assim batizados 
em homenagem 
ao matemático 
italiano Leonardo 
de Pisa (1170 - 
1250), também 
chamado de 
Fibonacci devido 
à contração filius 
Bonaccio, “filho 
de Bonaccio”. 
Fibonacci 
introduziu essa 
sequencia na 
Europa ocidental 
em sua obra Liber 
Abaci. Mas eles já 
eram conhecidos 
na Matemática 
hindu.
45
Algoritmos e Estrutura de Dados
À primeira vista, pode parecer estranho que seja possível executar 
algoritmos recursivos em computadores. Uma reflexão sobre 
organização de computadores espanta todo mistério.
Sempre que uma função ou procedimento é executado,o 
computador armazena na memória o estado atual do processamento, 
ou seja, os valores de todas as variáveis. Ao término da execução do 
procedimento, esses valores são recuperados, e a execução continua 
na instrução seguinte.
Um procedimento recursivo é um procedimento legítimo, como 
outro qualquer. Sua particularidade é que ele chama a si próprio, 
potencialmente muitas vezes. Para cada chamada, o estado da 
execução é armazenado na memória, compondo uma estrutura de 
pilha de estados de memória.
Quando o caso básico da recursão é atingido, essas execuções 
encaixadas terminam sucessivamente, e os estados de memória 
correspondentes são desempilhados. O resultado a ser calculado na 
primeira chamada do algoritmo depende dos resultados calculados no 
momento em que esses estados de memória são desempilhados.
Algoritmos recursivos são intimamente relacionados com indução 
matemática. Vamos recordar que indução é uma técnica de prova que 
permite estabelecer a validade de uma propriedade para todo número 
natural. Por exemplo, vamos usar indução para demonstrar que para 
todo número natural n é verdade que
1 + 2 + ... + n = n(n + 1) / 2
A estratégia de uma prova por indução é semelhante à de um 
algoritmo recursivo: se n é pequeno (base da indução), resolva o 
problema explicitamente, e se n é grande, use a hipótese de que a 
propriedade é verdadeira para valores menores de n (hipótese de 
indução):
• n = 1: a propriedade é verdadeira, pois neste caso n(n + 1) / 2 = 
1.
• n > 1: vamos dividir a soma em duas partes, 1 + 2 + ... + (n – 1) 
e n. Pela hipótese de indução, a primeira parte é igual a (n - 1)n / 
2. A soma total é igual a (n - 1)n / 2 + n, que é igual a n(n + 1) / 2. 
A propriedade está assim demonstrada.
Provas indutivas também são úteis para demonstrar a corretude 
46
Algoritmos e Estrutura de Dados
de algoritmos recursivos. A base da indução corresponde aos valores 
calculados explicitamente pelo algoritmo, e o passo de indução usa o 
fato de que o algoritmo dá a resposta correta para valores menores 
do considerado.
Vamos concluir essa seção analisando uma questão. Vimos no 
exemplo do fatorial dois algoritmos para resolver o mesmo problema, 
um recursivo e um iterativo. É verdade que para todo algoritmo 
recursivo podemos construir um algoritmo iterativo que resolve o 
mesmo problema? A resposta é sim. Uma estratégia que sempre 
funciona é simular a execução da pilha de recursão explicitamente, 
com o uso de variáveis auxiliares. No entanto, essa simulação produz 
em alguns casos algoritmos iterativos muito complicados.
2. Deve-se sempre usar recursão?
Recursão é uma ferramenta poderosa, que se utilizada corretamente 
leva a algoritmos simples e elegantes. No entanto, deve-se utilizar 
recursão sistematicamente sempre que o problema considerado tem 
uma estrutura recursiva?
A resposta é não, e a razão principal é que o número de chamadas 
recursivas do algoritmo pode ser proibitivamente grande. Veremos, no 
próximo capítulo, noções de análise de algoritmo. Mas podemos desde 
já guardar a lição de que se o algoritmo executa uma quantidade de 
operações proporcional a uma função de crescimento muito lento, 
como a função exponencial, então esse algoritmo não será útil na 
prática.
 Esse é o caso do algoritmo recursivo que apresentamos para o 
cálculo dos números de Fibonacci. Ao simular a execução do algoritmo, 
percebemos que o número de chamadas recursivas é exponencial. 
Neste caso, o algoritmo iterativo a seguir é muito mais eficiente:
47
Algoritmos e Estrutura de Dados
algoritmo FIBONACCI-ITERATIVO(n) ► Calcula o n-ésimo número de 
Fibonacci
a ← 0
b ← 1
para i ← 2 até n faça
 f ← a + b
 a ← b
 b ← f
devolva b
Outra razão para cautela antes do uso de algoritmos recursivos 
é o fato de que as chamadas do algoritmo podem implicar no 
empilhamento de grandes volumes de informação na memória 
(recorde a discussão sobre a pilha de execução de um algoritmo 
recursivo feita anteriormente).
No entanto, também não é verdade que algoritmos recursivos são 
inerentemente ineficientes. A escolha entre um algoritmo recursivo e 
um iterativo é assim uma decisão importante de projeto, que depende 
de uma análise cuidadosa do problema, e exige, naturalmente, alguma 
experiência que só adquirimos através da prática.
Conheça Mais
Se você não está familiarizado com demonstrações por indução 
matemática, esse é um bom momento para se aprofundar. Voltaremos 
a usar indução nessa disciplina para demonstrar fatos sobre outros 
algoritmos. Procure realizar a demonstração de algumas propriedades 
sobre números inteiros por indução. Um bom repositório de problemas 
é o Capítulo 2 do livro Problems on Algorithms de Ian Parberry e 
William Gasarch que está disponível on-line para uso livre:
 http://www.eng.unt.edu/ian/books/free/poa.pdf
Para se aprofundar no estudo de algoritmos recursivos, uma boa 
referência é o livro
48
Algoritmos e Estrutura de Dados
 WIRTH, N. Algoritmos e Estruturas de Dados. Rio de Janeiro: 
Ed. Prentice Hall do Brasil, 2002.
Atividades e Orientações de Estudo
Vamos realizar duas atividades práticas.
Na primeira, vamos desenvolver uma versão recursiva do algoritmo 
que busca um valor em um vetor (não necessariamente ordenado). 
Na versão iterativa do algoritmo que você desenvolveu no capítulo 
precedente, o algoritmo recebe duas entradas, o vetor e o número a 
ser localizado. Neste exercício, vamos observar que a versão recursiva 
necessita de um parâmetro adicional, que indica o início da região do 
vetor onde a busca será efetuada. Observe que esse algoritmo é mais 
geral do que o outro, e responda: como se enuncia o problema mais 
geral que esse algoritmo resolve?
Na segunda atividade, o objetivo é buscar uma explicação para a 
ineficiência da versão recursiva do algoritmo que calcula o n-ésimo 
número de Fibonacci.
Implemente ambos os algoritmos numa linguagem de programação 
que será determinada pelo professor. Também inclua, na versão 
recursiva, a impressão do valor do parâmetro recebido pelo vetor. Ou 
seja, cada vez que uma chamada recursiva é executada, o algoritmo 
imprime o valor de n. Execute ambos os algoritmos para diversos 
valores de n, e compare o tempo de execução.
Em seguida, verifique as mensagens impressas pela versão 
recursiva. O que você conclui? Para ajudar, é útil refletir também sobre 
a árvore de execução do algoritmo cujo desenho sugerimos. Discuta 
suas conclusões com seus colegas no ambiente.
Vamos revisar?
Neste capítulo, estudamos o conceito de recursão e analisamos 
alguns algoritmos recursivos. Vimos que recursão é um recurso 
poderoso e particularmente útil para o desenvolvimento de algoritmos 
49
Algoritmos e Estrutura de Dados
para problemas de estrutura recursiva. Também aprendemos que 
recursão deve ser utilizada com cautela, pois em determinadas 
situações o número de vezes que o algoritmo chama a si mesmo pode 
ser explosivo.
50
Algoritmos e Estrutura de Dados
Capítulo 4 – Análise de um 
algoritmo
Vamos conversar sobre o assunto?
Neste último capítulo, vamos discutir o conceito de eficiência de 
um algoritmo.
Às vezes, esperamos muito tempo diante do computador o término 
da execução de alguma operação. Nessas situações, tendemos a 
concluir que nosso equipamento não é tão bom. Temos a impressão 
de que o problema se resolveria com a substituição do processador 
ou um aumento de memória. Pode ser verdade. A evolução e 
aumento de complexidade dos sistemas operacionais, por exemplo, 
exigirá naturalmente tecnologias mais eficazes para um desempenho 
satisfatório. Por outro lado, o problema nem sempre está associado à 
tecnologia disponível.
De nosso estudo nos capítulos anteriores, podemos concluir que 
qualquer serviço disponível num computador depende de um ou 
mais algoritmos – alguns simples, outros muito delicados. Também 
consideramos que a execução de um algoritmo, para qualquer entrada, 
deve ocorrer em tempo finito – condição que faz parte da definição 
de algoritmo. Não

Continue navegando