Você está na página 1de 196

ALGORITMOS

em linguagem C
Paulo Feofiloff

ALGORITMOS
em linguagem C

5 Tiragem
2009, Elsevier Editora Ltda.

Todos os direitos reservados e protegidos pela Lei no 9.610, de 19/2/1998. Nenhuma parte deste livro
poder ser reproduzida ou transmitida sem autorizao prvia por escrito da editora, sejam quais forem
os meios empregados: eletrnicos, mecnicos, fotogrcos, gravaes ou quaisquer outros.

Copidesque: Caravelas Produes Editoriais

Editorao eletrnica: Paulo Feolo

Reviso grca: Marlia Pinto de Oliveira e Wilton Palha

Elsevier Editora Ltda. Conhecimento sem fronteiras


Rua Sete de Setembro, 111, 16o andar
20050-006 Rio de Janeiro, RJ Brasil
Rua Quintana, 753, 8o andar
04569-011 Brooklin So Paulo, SP Brasil

Servio de Atendimento ao Cliente


0800-265340
sac@elsevier.com.br

ISBN: 978-85-352-3249-3

Nota: Muito zelo e tcnica foram empregados na edio desta obra. Apesar disso, podem ter ocorrido
erros de digitao, de impresso ou de conceitos. Em qualquer das hipteses, solicitamos a comunicao
nossa Central de Atendimento, para que possamos esclarecer ou encaminhar a questo.
Nem a editora nem o autor assumem qualquer responsabilidade por eventuais danos ou perdas originados
do uso desta publicao.

CIP-BRASIL. CATALOGAO-NA-FONTE
SINDICATO NACIONAL DOS EDITORES DE LIVROS, RJ

F383a
Feolo, Paulo, 1946-
Algoritmos / Paulo Feolo. Rio de Janeiro : Elsevier, 2009. - 5a reimpresso.
il.
Inclui bibliograa e ndice
ISBN 978-85-352-3249-3
1. Algoritmos. I. Ttulo.

08-3451. CDD: 518.1


CDU: 519.165

14.08.08 18.08.08 008208

32a impresso, corrigida


Prefcio

Computer science is no more about computers


than astronomy is about telescopes.
E. W. Dijkstra

A good algorithm is like a sharp knife:


it does what it is supposed to do with a minimum amount of applied eort.
T. Cormen, C. Leiserson, R. Rivest, C. Stein, Introduction to Algorithms

A Computao anda sobre trs pernas: a correo, a ecincia e a elegncia.


I. Simon

Este livro contm um curso introdutrio de algoritmos e estruturas de dados. Ele


apropriado para estudantes de computao que j tenham alguma experincia
de programao em linguagem C.1 (Alguns conceitos e recursos da linguagem
esto resumidos nos apndices.)
O livro discute algoritmos clssicos para vrios problemas computacionais
bsicos. O estudo cuidadoso dessas solues clssicas deve ajudar o leitor a criar
algoritmos para os seus prprios problemas. O texto evita oferecer receitas que
possam ser aplicadas mecanicamente, e portanto est mais prximo do ensinar
a pescar que do dar o peixe.
A coleo de tpicos no difere muito da que se encontra em outras obras
sobre o assunto. Mas a abordagem tem as seguintes peculiaridades:
o destaque dado aos algoritmos recursivos;
o uso de invariantes na anlise de algoritmos iterativos;
a ateno dispensada documentao;
o cuidado com a elegncia do cdigo.
O texto evita longas explicaes informais sobre o funcionamento e a lgica dos
algoritmos, preferindo colocar o leitor em contato direto com o cdigo. Para
1
Veja o verbete C programming language na Wikipedia [21].
vi ALGORITMOS em linguagem C ELSEVIER

ajudar o leitor a entender o cdigo, oferece uma boa documentao e exibe os


invariantes dos processos iterativos.
O livro procura mostrar vrios algoritmos para um mesmo problema e vrias
maneiras de escrever o cdigo de um mesmo algoritmo. Alm disso, d exemplos
de erros comumente cometidos por programadores inexperientes. Assim, tenta
habituar o leitor a distinguir o bom cdigo do mau.

Algoritmos corretos, ecientes e elegantes. Todo bom algoritmo e


programa2 tem trs qualidades fundamentais: correo,3 ecincia4 e elegncia.5
O livro discute essas trs qualidades, de maneira muito informal, atravs de
exemplos.
Antes de aprender a construir algoritmos corretos, preciso aprender a
vericar se um algoritmo dado est correto. A vericao da correo de um
algoritmo uma atividade semelhante prova de um teorema. (Mas o leitor
no deve car assustado: o livro trata essa analogia de maneira muito informal.)
A vericao depende do enunciado preciso do que o algoritmo deve fazer; esse
enunciado constitui a documentao do algoritmo. No caso de um algoritmo
iterativo, a prova da correo se apoia sobre o conceito de invariante.
Para provar a correo de um algoritmo, o livro no recorre a abstraes
vagas mas estuda a relao entre os valores das variveis em pontos estratgicos
do cdigo. A propsito, convm ouvir E. W. Dijkstra [6]:
[We must] deal with all computations possible under control of a given pro-
gram by ignoring them and working with the program. We must learn to
work with program texts while (temporarily) ignoring that they admit the
interpretation of executable code. 6

2
Um programa uma implementao concreta de um algoritmo. Para simplicar a lin-
guagem, o livro tende a usar as duas palavras como sinnimas.
3
Um algoritmo correto se faz o que dele se espera, ou seja, se cumpre o que sua
documentao promete.
4
Um algoritmo eciente se no desperdia tempo. (Uma denio mais ampla diria
tambm que no desperdia espao de memria.) Dados dois algoritmos para um mesmo
problema, o primeiro mais eciente que o segundo se a execuo do primeiro consome menos
tempo que a do segundo.
5
Um algoritmo elegante se for simples, limpo, bonito, sem enfeites. Um algoritmo
elegante no trata casos especiais do problema em separado. Um algoritmo elegante no tem
cdigo supruo, nem variveis desnecessrias, nem construes convoludas e espertas, nem
sutilezas evitveis.
6
Para lidar com todas as computaes possveis sob o controle de um dado programa
preciso ignorar essas computaes e trabalhar com o [texto do] programa. Precisamos aprender
a trabalhar com os textos dos programas e esquecer (temporariamente) que eles podem ser
interpretados como cdigo executvel.
ELSEVIER Prefcio vii

E ainda:
[. . . ] all by itself, a program is no more than half a conjecture. The other
half of the conjecture is the functional specication the program is supposed
to satisfy. The programmers task is to present such complete conjectures as
proven theorems. 7
(Espero que o leitor no me considere demasiado pretensioso por citar Dijkstra.
O livro procura incorporar um pouco do esprito das observaes, mas no
pretende implement-las de maneira sria e sistemtica.)
A ecincia dos algoritmos ou melhor, o consumo de tempo em funo
do tamanho das instncias analisada de maneira informal e intuitiva, como
convm a um livro introdutrio.
Alm de correto e eciente, um bom algoritmo deve ser elegante. O conceito
um tanto subjetivo, mas programadores experientes concordam entre si, em
geral, quando julgam a elegncia de um algoritmo. Para dar uma plida ideia
do conceito, o Captulo 3 exibe pequenos exemplos de algoritmos elegantes e
deselegantes para um mesmo problema.

A estrutura do livro. O livro tem quinze captulos e um grande nmero


de apndices. Os trs primeiros captulos constituem uma espcie de introduo.
Os doze captulos seguintes tratam do assunto central do livro. Os apndices
fazem um resumo dos principais conceitos e recursos da linguagem C.
Sugiro comear a leitura pelo Apndice A, que trata do leiaute de programas.
Esse assunto mais importante do que parece, porque programas precisam
ser lidos e compreendidos por seres humanos. Em seguida, sugiro ler os trs
captulos introdutrios:

Captulo 1 (Documentao e invariantes). Apresenta, de maneira muito in-


formal, a ideia da boa documentao e o conceito de invariante de um
processo iterativo.
Captulo 2 (Algoritmos recursivos). O conceito de recurso fundamental
em computao, embora nem sempre receba a ateno que merece.
O captulo introduz a noo de algoritmo recursivo por meio de um
exemplo muito simples.
Captulo 3 (Vetores). Os problemas de busca, remoo e insero em um
vetor so usados aqui como pretexto para ilustrar os conceitos de cor-
reo, ecincia e elegncia de algoritmos e de cdigo. Em particular,
7
Por si s, um programa apenas metade de uma conjectura. A outra metade da con-
jectura a especicao funcional que o programa deve satisfazer. tarefa do programador
apresentar as duas metades de tais conjecturas como teoremas demonstrados.
viii ALGORITMOS em linguagem C ELSEVIER

o captulo procura despertar a sensibilidade do leitor para o conceito


de elegncia. Os trs problemas busca, insero e remoo rea-
parecem, em outros contextos, nos captulos seguintes.

Depois desta introduo, o leitor pode passar ao assunto central do livro:

Captulos 4, 5 e 6 (Listas encadeadas, Filas e Pilhas). Estes captulos tra-


tam da manipulao de trs estruturas de dados muito teis, comuns
a um sem-nmero de aplicaes.
Captulo 7 (Busca em vetor ordenado). O captulo discute o clebre algo-
ritmo da busca binria. As ideias subjacentes ao algoritmo reaparecem
em vrios dos captulos seguintes.
Captulos 8, 9, 10 e 11 (Algoritmos de ordenao). Este grupo de captulos
trata do clssico problema de colocar um vetor de nmeros em ordem
crescente. O Captulo 8 discute dois algoritmos muito simples, mas
pouco ecientes. Os demais introduzem algoritmos mais sosticados e
bem mais ecientes. As estruturas de dados criadas por esses algorit-
mos so muito teis em outras aplicaes, diferentes da ordenao.
Captulo 12 (Algoritmos de enumerao). Consideramos aqui o problema
de gerar todos os subconjuntos de um conjunto. A soluo deste pro-
blema uma boa demonstrao do poder da recurso. Algoritmos do
tipo discutido aqui aparecem em aplicaes que envolvem backtracking.
Captulo 13 (Busca de palavras em um texto). O problema de encontrar
uma ocorrncia de uma dada palavra num texto um componente
bsico de muitas aplicaes prticas, como a construo de editores de
texto e a procura por um gene num genoma.
Captulos 14 e 15 (rvores binrias e rvores de busca). O primeiro des-
tes captulos introduz uma estrutura de dados fundamental muito til.
O segundo captulo usa a estrutura para generalizar a busca binria
discutida no Captulo 7.

Exerccios. Os exerccios so parte essencial do livro. Eles esclarecem


pontos obscuros e levam o leitor a pensar sobre os detalhes dos algoritmos
discutidos no texto. Alguns convidam o leitor a investigar, por conta prpria,
assuntos que o texto no aborda. A soluo de alguns exerccios dada no
Apndice L.
Um bom nmero de exerccios explora maneiras alternativas de codicar os
algoritmos discutidos no texto. Esses exerccios incentivam o leitor a analisar
cdigo e a encontrar defeitos.
ELSEVIER Prefcio ix

Recomendo que o leitor no se limite aos exerccios do livro e aventure-se


a resolver os problemas de competies de programao, como o Programming
Challenges [19] e o Problem Set Archive [14]. Os dois livros de Bentley [2, 1]
tambm so excelente fonte de exerccios, exemplos e inspirao.

Histrico. O livro evoluiu a partir das notas de aula que mantenho na


Internet (www.ime.usp.br/~pf/algoritmos/) h vrios anos. Aquelas notas, por
sua vez, foram escritas ao longo de vrias edies da disciplina Princpios de
Desenvolvimento de Algoritmos do curso de graduao em Cincia da Compu-
tao da USP (Universidade de So Paulo), administrado pelo IME (Instituto
de Matemtica e Estatstica). Esta disciplina oferecida no segundo semestre
do currculo, logo depois de uma disciplina de introduo programao.

Agradecimentos. Quero registrar minha gratido aos alunos, colegas e


professores que contriburam com ideias e material, e corrigiram muitos dos meus
erros (no raro fundamentais). Ainda que muitos deles tenham sido corrigidos,
quase certo que muitos outros escaparam.
Agradeo ao Departamento de Cincia da Computao do IME-USP pelo
uso de suas instalaes e equipamento durante a preparao do livro.

So Paulo, agosto de 2008


P.F.
Captulo 1

Documentao e invariantes

Let us change our traditional attitude to the construction of programs.


Instead of imagining that our main task is to instruct a computer what to do,
let us imagine that our main task is to explain to human beings
what we want a computer to do.
Programming is best regarded as the process of creating works of literature,
which are meant to be read.
D. E. Knuth, Literate Programming

. . . a program is no more than half a conjecture.


The other half of the conjecture is the functional specication
the program is supposed to satisfy.
E. W. Dijkstra, manuscrito EWD1036

H quem diga que documentar um programa o mesmo que escrever muitos


comentrios de mistura com o cdigo. Essa ideia est errada. Uma boa docu-
mentao evita sujar o cdigo com comentrios e limita-se a explicar

o que cada uma das funes que compem o programa faz.

A documentao de uma funo um minimanual que d instrues precisas e


completas sobre o uso correto da funo. O minimanual comea por especicar
o que entra que dados a funo recebe e o que sai que objetos a
funo devolve. Em seguida, descreve a relao entre o que entrou e o que sai
(bem como as eventuais transformaes executadas sobre o que entrou). Com
isso, uma boa documentao coloca nas mos do leitor as condies necessrias
para detectar os erros que o autor da funo tenha porventura cometido.
Em geral, uma boa documentao no perde tempo tentando explicar como
uma funo faz o que faz (o leitor interessado nesta questo deve ler o cdigo).
A distino entre o que uma funo faz e como ela faz o que faz essencial
2 ALGORITMOS em linguagem C ELSEVIER

para uma boa documentao. Esta distino a mesma que existe entre a
interface (arquivo .h) e a implementao (arquivo .c) de uma biblioteca em
linguagem C. A seguinte analogia pode tornar a distino mais clara: Uma
empresa de entregas promete apanhar o seu pacote em So Paulo e entreg-lo
no dia seguinte no Rio de Janeiro. Isto o que a empresa faz. Como o servio
feito se o transporte terrestre, areo ou martimo, por exemplo assunto
interno da empresa.

1.1 Exemplo de documentao


A funo abaixo calcula o valor de um elemento mximo de um vetor. Observe
como a documentao da funo simples e precisa:

/* A funo abaixo recebe um inteiro n >= 1 e um vetor v e


* devolve o valor de um elemento mximo de v[0..n-1]. */ 1
int Max (int v[], int n) {
int j, x = v[0];
for (j = 1; j < n; j++)
if (x < v[j]) x = v[j];
return x;
}

A documentao diz o que a funo faz mas no perde tempo tentando


explicar como a funo faz o que faz (se recursiva ou iterativa, se percorre o
vetor da esquerda para a direita ou vice-versa etc.). Para fazer contraste com
este bom exemplo, seguem algumas amostras de m documentao:
1. a funo devolve o valor de um elemento mximo de um vetor indecen-
temente vago;
2. a funo devolve o valor de um elemento mximo do vetor v ainda
muito vago, pois no explica o papel do parmetro n;
3. a funo devolve o valor de um elemento mximo de um vetor v que tem
n elementos melhor, mas ainda est vago: no se sabe se o vetor
v[0..n-1] ou v[1..n];
4. a funo devolve o valor de um elemento mximo de v[0..n-1] j est
quase bom, mas sonega a importante restrio n >= 1.
Observe que a documentao menciona todos os parmetros da funo (a
saber, v e n) e no faz meno de quaisquer outras variveis. Observe tambm a
1
A expresso v[i..m] representa um vetor v indexado por i, i+1, . . . , m.
ELSEVIER Captulo 1. Documentao e invariantes 3

ausncia de comentrios inteis (como, por exemplo, o ndice j vai percorrer


o vetor e x armazena o maior valor encontrado at agora) misturados ao
cdigo.

Exerccios
1.1.1 Escreva a documentao correta da funo abaixo.
int soma (int n, int v[]) {
int i, x = 0;
for (i = 0; i < n; i++) x += v[i];
return x; }
1.1.2 Escreva a documentao correta da funo abaixo.
int onde (int x, int v[], int n) {
int j = 0;
while (j < n && v[j] != x) j += 1;
return j; }
1.1.3 Critique a seguinte documentao de uma funo: Esta funo recebe nmeros
inteiros p, q, r, s e devolve a mdia aritmtica de p, q, r.
1.1.4 Critique a seguinte documentao de uma funo: Esta funo recebe nmeros
inteiros p, q, r tais que p <= q <= r e devolve a mdia aritmtica dos trs nmeros.
1.1.5 Leia o verbete Software documentation na Wikipedia [21].

1.2 Invariantes
O corpo de muitas funes contm um ou mais processos iterativos (tipicamente
controlados por um for ou um while). O programador pode enriquecer a
documentao da funo dizendo quais os invariantes dos processos iterativos.
Um invariante uma relao entre os valores das variveis que vale no incio de
cada iterao do processo iterativo. Os invariantes explicam o funcionamento do
processo iterativo e permitem provar, por induo, que ele tem o efeito desejado.
Considere, por exemplo, a funo Max da Seo 1.1. O processo iterativo
controlado pelo for tem o seguinte invariante: no incio de cada iterao (ime-
diatamente antes da comparao de j com n),

x um elemento mximo de v[0..j-1].

O invariante vale, em particular, no incio da ltima iterao, quando j vale n+1.


Isto mostra que a funo de fato devolve o valor de um elemento mximo de
v[0..n-1].
4 ALGORITMOS em linguagem C ELSEVIER

int Max (int v[], int n) {


int j, x;
x = v[0];
for (j = 1; j < n; j++)
/* x um elemento mximo de v[0..j-1] */
if (x < v[j]) x = v[j];
return x;
}

(O enunciado de um invariante , provavelmente, o nico tipo de comentrio


que vale a pena inserir no corpo de uma funo.)

Exerccios
1.2.1 Mostre que o invariante da funo Max vale no incio da primeira iterao. Supo-
nha que o invariante vale no incio de uma iterao qualquer e mostre que ele vale no
incio da iterao seguinte. Suponha que o invariante vale no incio da ltima iterao
e deduza da que a funo devolve um elemento mximo do vetor v[0..n-1].
1.2.2 Qual o invariante do processo iterativo da funo soma no Exerccio 1.1.1?
1.2.3 Qual o invariante do processo iterativo da funo onde no Exerccio 1.1.2?
1.2.4 Projeto de programao. O piso de um nmero x o nico inteiro i tal que
i x < i + 1. O piso de x denotado por x. Escreva uma funo lg que receba um
inteiro positivo n e calcule log2 n. Segue uma amostra de valores:

n 15 16 31 32 63 64 127 128 255 256 511 512


log2 n 3 4 4 5 5 6 6 7 7 8 8 9

Documente sua funo e enuncie os invariantes do processo iterativo.


Implemente um programa completo que use a funo lg para imprimir uma tabela
de valores de log2 n. Faa um bom leiaute do seu programa (veja Apndice A) e
documente-o corretamente.
Durante a fase de testes, o seu programa deve vericar a correo da funo lg
valendo-se da funo log que faz parte da bibilioteca math (veja Seo K.3).
Captulo 2

Recurso

Ao tentar resolver o problema, encontrei obstculos dentro de obstculos.


Por isso, adotei uma soluo recursiva.
um aluno

To understand recursion, we must rst understand recursion.


folclore

O conceito de recurso de fundamental importncia em computao. Este


captulo introduz o conceito por meio de um exemplo muito simples.

2.1 Algoritmos recursivos


Muitos problemas computacionais tm a seguinte propriedade: cada instncia1
do problema contm uma instncia menor do mesmo problema. Dizemos que es-
ses problemas tm estrutura recursiva. Para resolver um tal problema natural
aplicar o seguinte mtodo:
se a instncia em questo pequena,
resolva-a diretamente (use fora bruta se necessrio);
seno,
reduza-a a uma instncia menor do mesmo problema,
aplique o mtodo instncia menor
e volte instncia original.
A aplicao deste mtodo produz um algoritmo recursivo.
1
Uma instncia de um problema um exemplo do problema. Cada conjunto de dados
de um problema dene uma instncia. (A palavra instncia um neologismo importado do
ingls. Ela est sendo empregada aqui no sentido de exemplo, espcime, amostra.)
6 ALGORITMOS em linguagem C ELSEVIER

2.2 Um exemplo: o problema do mximo


Considere o problema de determinar o valor de um2 elemento mximo de um
vetor v[0 . . n1]. O tamanho de uma instncia do problema n. claro que
o problema s faz sentido se o vetor no for vazio, ou seja, se n 1. Se n = 1,3
ento v[0] o nico elemento do nosso vetor e portanto v[0] o mximo. Se
n > 1, o valor que procuramos o maior dentre o mximo do vetor v[0 . . n2]
e o nmero v[n1]. Assim, a instncia v[0 . . n1] do problema ca reduzida
instncia v[0 . . n2]. Estas observaes levam seguinte funo recursiva:

/* Ao receber v e n >= 1, esta funo devolve o valor de


* um elemento mximo do vetor v[0..n-1]. */
int MximoR (int v[], int n) { 4
if (n == 1)
return v[0];
else {
int x;
x = MximoR (v, n - 1);
if (x > v[n-1])
return x;
else
return v[n-1];
}
}

Para vericar que uma funo recursiva est correta, use o seguinte roteiro.
Passo 1: Escreva o que a funo deve fazer (veja Captulo 1). Passo 2: Verique
se a funo de fato faz o que deveria quando n pequeno (n = 1, no nosso
exemplo). Passo 3: Imagine que n grande (n > 1, no nosso exemplo) e suponha
que a funo far a coisa certa se no lugar de n tivermos algo menor que n. Sob
esta hiptese, verique que a funo faz o que dela se espera.
Como o computador executa uma funo recursiva? Embora relevante, esta
pergunta ser ignorada por enquanto. Veja o conceito de pilha de execuo na
Seo 6.5.
2
Eu no disse do elemento mximo porque o vetor pode ter vrios elementos mximos.
3
Embora sejam tipogracamente semelhantes, os sinais = e = tm signicados diferentes.
O primeiro o sinal de igualdade da matemtica: x = y signica x igual a y. O segundo
o operador de atribuio na linguagem C: x = y signica atribua varivel x o valor da
varivel y. O = da matemtica corresponde ao == da linguagem C.
4
Veja Seo A.4.
ELSEVIER Captulo 2. Recurso 7

Exerccios
2.2.1 Escreva uma verso iterativa da funo MximoR.
2.2.2 Critique a funo abaixo. Ela promete encontrar o valor de um elemento mximo
de v[0..n-1].
int mximoR1 (int v[], int n) {
int x;
if (n == 1) return v[0];
if (n == 2) {
if (v[0] < v[1]) return v[1];
else return v[0]; }
x = mximoR1 (v, n - 1);
if (x < v[n-1]) return v[n-1];
else return x; }
2.2.3 Critique a seguinte funo recursiva que promete encontrar o valor de um ele-
mento mximo do vetor v[0..n-1].
int mximoR2 (int v[], int n) {
if (n == 1) return v[0];
if (mximoR2 (v, n - 1) < v[n-1])
return v[n-1];
else
return mximoR2 (v, n - 1); }
2.2.4 Se X a funo recursiva abaixo, qual o valor de X(4)?
int X (int n) {
if (n == 1 || n == 2) return n;
else return X (n - 1) + n * X (n - 2); }
2.2.5 O que h de errado com a seguinte funo recursiva?
int XX (int n) {
if (n == 0) return 0;
else return XX (n/3 + 1) + n; }
2.2.6 Programa de teste. Escreva um pequeno programa para testar a funo
recursiva MximoR. O seu programa deve pedir ao usurio que digite uma sequncia de
nmeros ou gerar um vetor aleatrio (veja Apndice I).
Importante: Para efeito de testes, acrescente ao seu programa uma funo auxiliar
que conra a resposta produzida por MximoR.

2.3 Outra soluo recursiva do problema


A funo MximoR discutida acima aplica a recurso ao subvetor v[0 . . n2].
possvel escrever uma verso que aplique a recurso ao subvetor v[1 . . n1]:
8 ALGORITMOS em linguagem C ELSEVIER

/* Ao receber v e n >= 1, esta funo devolve o valor de


* um elemento mximo do vetor v[0..n-1]. */
int Mximo (int v[], int n) {
return MaxR (v, 0, n);
}

/* Esta funo recebe v, i e n tais que i < n e devolve


* o valor de um elemento mximo do vetor v[i..n-1]. */
int MaxR (int v[], int i, int n) {
if (i == n-1) return v[i];
else {
int x;
x = MaxR (v, i + 1, n);
if (x > v[i]) return x;
else return v[i];
}
}

A funo Mximo apenas uma embalagem; o servio pesado executado


pela funo recursiva MaxR, que resolve um problema mais geral, com mais
parmetros que o original.
A necessidade de generalizar o problema ocorre com frequncia na constru-
o de algoritmos recursivos. O papel dos novos parmetros (como i no exemplo
acima) deve ser devidamente explicado na documentao da funo,5 o que nem
sempre fcil (veja mais exemplos nas Sees 7.7 e 12.3).

Exerccios
2.3.1 Verique que a seguinte funo equivalente funo Mximo. Ela usa a aritm-
tica de endereos mencionada no Seo D.4.
int mximo2r (int v[], int n) {
int x;
if (n == 1) return v[0];
x = mximo2r (v + 1, n - 1);
if (x > v[0]) return x;
return v[0]; }
2.3.2 Max-Min. Escreva uma funo recursiva que calcule a diferena entre o valor
de um elemento mximo e o valor de um elemento mnimo do vetor v[0..n-1].
5
Explicaes do tipo a primeira chamada da funo deve ser feita com i = 0 no
explicam nada e devem ser evitadas a todo o custo.
ELSEVIER Captulo 2. Recurso 9

2.3.3 Soma. Escreva uma funo recursiva que calcule a soma dos elementos positivos
do vetor de inteiros v[0 . . n1]. O problema faz sentido quando n = 0? Quanto deve
valer a soma neste caso?
2.3.4 Soma de dgitos. Escreva uma funo recursiva que calcule a soma dos dgitos
decimais de um inteiro positivo. A soma dos dgitos de 132, por exemplo, 6.
2.3.5 Piso de logaritmo. Escreva uma funo recursiva que calcule log2 n, ou seja,
o piso do logaritmo de n na base 2. (Veja Exerccio 1.2.4.)
2.3.6 Fibonacci. A sequncia de Fibonacci denida assim: F0 = 0, F1 = 1 e Fn =
Fn1 + Fn2 para n > 1. Escreva uma funo recursiva que receba n e devolva Fn .
Escreva uma verso iterativa da funo. Sua funo recursiva to eciente quanto a
iterativa? Por qu?
2.3.7 Seja F a verso recursiva da funo de Fibonacci (veja Exerccio 2.3.6). O clculo
de F(3) provoca a sequncia de invocaes da funo dada abaixo (note a indentao).
D a sequncia de invocaes da funo provocada pelo clculo de F(5).
F(3)
F(2)
F(1)
F(0)
F(1)
2.3.8 Execute a funo ff abaixo com argumentos 7 e 0.
int ff (int n, int ind) {
int i;
for (i = 0; i < ind; i++)
printf (" ");
printf ("ff (%d,%d)\n", n, ind);
if (n = 1)
return 1;
if (n % 2 == 0)
return ff (n/2, ind + 1);
return ff ((n-1)/2, ind + 1) + ff ((n+1)/2, ind + 1); }
2.3.9 Euclides. A seguinte funo calcula o maior divisor comum dos inteiros positivos
m e n. Escreva uma funo recursiva equivalente.
int Euclides (int m, int n) {
int r;
do {
r = m % n;
m = n; n = r;
} while (r != 0);
return m; }
2.3.10 Exponenciao. Escreva uma funo recursiva eciente que receba inteiros
10 ALGORITMOS em linguagem C ELSEVIER

positivos k e n e calcule o valor de k n . Suponha que k n cabe em um int (veja Se-


o C.2). Quantas multiplicaes sua funo executa aproximadamente?
2.3.11 Leia o verbete Recursion na Wikipedia [21].
Captulo 3

Vetores

Um vetor uma estrutura de dados que armazena uma sequncia1 de objetos,


todos do mesmo tipo, em posies consecutivas da memria (veja Seo D.4).
Este captulo estuda os problemas de procurar um objeto em um vetor, de inserir
um novo objeto no vetor e de remover um elemento do vetor. Os problemas
servem de pretexto para ilustrar os conceitos de correo, ecincia e elegncia
de algoritmos (veja notas de rodap na pgina vi do prefcio), bem como para
exibir alguns exemplos de algoritmos recursivos.
Imagine que temos uma longa lista de nmeros armazenada num vetor v.
O espao reservado para o vetor pode ter sido criado pela declarao
int v[N];
sendo N uma constante (possivelmente denida por um #define, conforme Se-
o J.4). Se a lista de nmeros est armazenada nas posies 0, 1, . . . , n1 do
vetor, diremos que
v[0 . . n1]
um vetor de inteiros. claro que devemos ter 0 n N. Se n = 0, o vetor
v[0 . . n1] est vazio. Se n = N, o vetor est cheio.

3.1 Busca
Dado um inteiro x e um vetor de inteiros v[0 . . n1], considere o problema de
encontrar um ndice k tal que v[k] = x. O problema faz sentido com qualquer
n 0. Se n = 0, o vetor vazio e portanto essa instncia do problema no tem
soluo.
1
O aspecto mais importante de uma sequncia a ordem de seus elementos: h um
primeiro elemento, um segundo elemento etc., um ltimo elemento (todas as sequncias neste
livro so nitas). Cada elemento da sequncia, exceto o ltimo, tem um sucessor.
12 ALGORITMOS em linguagem C ELSEVIER

x 987
0 n1
v 222 555 111 333 444 666 555 888 777 987 654

Figura 3.1: Problema da busca: encontrar k tal que 0 k < n e v[k] = x.

preciso comear com uma deciso de projeto: que fazer se x no estiver


no vetor? Adotaremos a conveno de devolver 1 nesse caso. A conveno
razovel pois 1 no pertence ao conjunto 0 . . n1 de ndices vlidos. Para
implementar esta conveno, convm varrer o vetor do m para o comeo:

/* Esta funo recebe um nmero x e um vetor v[0..n-1]


* com n >= 0 e devolve k no intervalo 0..n-1 tal que
* v[k] == x. Se tal k no existe, devolve -1. */
int Busca (int x, int v[], int n) {
int k;
k = n - 1;
while (k >= 0 && v[k] != x)
k -= 1; 2
return k;
}

Observe como o algoritmo eciente e elegante. Ele funciona corretamente


mesmo quando o vetor est vazio, ou seja, quando n vale 0.

Maus exemplos. Para fazer contraste com o cdigo acima, seguem algumas
amostras de cdigo deselegante, ineciente e incorreto. A primeira, muito po-
pular, usa uma varivel booleana3 sem necessidade:
int k = n - 1, achou = 0;
while (k >= 0 && achou == 0) { /* deselegante */
if (v[k] == x) achou = 1; /* deselegante */
else k -= 1;
}
return k;
2
Em C, a expresso k -= 1 uma abreviatura de k = k-1.
3
Uma varivel booleana se admite apenas dois valores: 0 e 1.
ELSEVIER Captulo 3. Vetores 13

A segunda, popular mas deselegante, trata do vetor vazio em separado:


int k;
if (n == 0) return -1; /* deselegante */
k = n - 1;
while (k >= 0 && v[k] != x) k -= 1;
return k;

A terceira ineciente, pois continua calculando depois de ter encontrado uma


soluo, e deselegante, pois inicializa uma varivel desnecessariamente:

int k = 0; /* deselegante */
int sol = -1;
for (k = n-1; k >= 0; k--) /* ineficiente */
if (v[k] == x) sol = k;
return sol;

Na amostra seguinte, a ltima iterao comete o erro de examinar v[-1] por-


que a ordem dos termos na expresso que controla o while est errada (veja o
Seo J.2):

int k = n - 1;
while (v[k] != x && k >= 0) /* errado! */
k -= 1;
return k;

Exerccios
3.1.1 Qual o invariante (veja Seo 1.2) do processo iterativo na funo Busca?
3.1.2 Analise a seguinte variante4 do cdigo da funo Busca.
int k;
for (k = n-1; k >= 0; k--)
if (v[k] == x) return k;
return -1;
3.1.3 Critique a seguinte verso da funo Busca:
int k = 0;
while (k < n && v[k] != x) k += 1; 5
if (v[k] == x) return k;
else return -1;
3.1.4 Critique a seguinte verso da funo Busca:

4
No confunda variante com invariante. . .
5
A expresso k += 1 uma abreviatura de k = k+1.
14 ALGORITMOS em linguagem C ELSEVIER

int sol;
for (k = 0; k < n; k++) {
if (v[k] == x) sol = k;
else sol = -1; }
return sol;
3.1.5 Tome uma deciso de projeto diferente da adotada no texto: se x no estiver em
v[0 . . n1], a funo deve devolver n. Escreva a verso correspondente da funo Busca.
Para evitar o grande nmero de comparaes de k com n, coloque uma sentinela
em v[n].
3.1.6 Considere o problema de determinar o valor de um elemento mximo de um vetor
v[0..n-1]. A seguinte funo resolve o problema?
int mximo (int n, int v[]) {
int j, x;
x = v[0];
for (j = 1; j < n; j += 1)
if (x < v[j]) x = v[j];
return x; }
Faz sentido trocar x = v[0] por x = 0 ? Faz sentido trocar x = v[0] por x =
INT_MIN ? 6 Faz sentido trocar x < v[j] por x <= v[j] ?

3.2 Busca recursiva


A funo Busca da seo anterior pode ser reescrita em estilo recursivo.7 A
ideia do cdigo simples: se n = 0 ento o vetor vazio e portanto x no est
em v[0 . . n1]; se n > 0 ento x est em v[0 . . n1] se e somente se x = v[n1]
ou est x no vetor v[0 . . n2].

/* Recebe x, v e n >= 0 e devolve k tal que 0 <= k < n


* e v[k] == x. Se tal k no existe, devolve -1. */
int BuscaR (int x, int v[], int n) {
if (n == 0) return -1;
if (x == v[n-1]) return n - 1;
return BuscaR (x, v, n - 1);
}

Mau exemplo. A seguinte verso da funo BuscaR muito deselegante. Ela


coloca o caso n = 1 na base da recurso e portanto s funciona se n 1.
6
INT_MIN o valor do menor nmero do tipo int. Veja Apndice C e Seo K.5.
7
A verso recursiva desta funo pode no ser uma alternativa muito prtica para a verso
iterativa pois a pilha de recurso (veja Seo 6.5) consome memria.
ELSEVIER Captulo 3. Vetores 15

int feio (int x, int v[], int n) {


if (n == 1) { /* deselegante */
if (x == v[0]) return 0; /* deselegante */
else return -1;
}
if (x == v[n-1]) return n - 1;
return feio (x, v, n - 1);
}

Exerccios
3.2.1 Critique a seguinte funo. Ela promete decidir se x est em v[0..n-1], devol-
vendo 1 em caso armativo e 0 em caso negativo.
int muitofeio (int x, int v[], int n) {
if (n == 0) return 0;
else {
int achei;
achei = muitofeio (x, v, n - 1); /* ineficiente */
if (achei || x == v[n-1]) return 1;
else return 0; } }
3.2.2 O autor da funo abaixo arma que ela decide se x est no vetor v[0..n-1].
Critique o cdigo.
int busc (int x, int v[], int n) {
if (v[n-1] == x) return 1;
else return busc (x, v, n - 1); }
3.2.3 Escreva um programa para testar o funcionamento da funo BuscaR. O seu
programa deve gerar um vetor aleatrio (veja Apndice I) para fazer o teste. Acrescente
ao seu programa uma funo que conra a resposta dada por BuscaR.

3.3 Remoo
A operao de remoo consiste em retirar do vetor v[0 . . n1] o elemento que
tem ndice k e fazer com que o vetor resultante tenha ndices 0, 1, . . . , n2.
(Por exemplo, o resultado da remoo do elemento de ndice 3 no vetor
000, 111, 222, 333, 444, 555 o vetor 000, 111, 222, 444, 555.) claro que a ope-
rao s faz sentido se 0 k < n. A seguinte funo faz a remoo e devolve o
nmero de elementos do vetor resultante:

/* Remove o elemento de ndice k do vetor v[0..n-1] e


* devolve o novo valor de n. A funo supe 0 <= k < n. */
16 ALGORITMOS em linguagem C ELSEVIER

int Remove (int k, int v[], int n) {


int j;
for (j = k; j < n-1; j++)
v[j] = v[j+1];
return n - 1;
}

Note como tudo funciona bem mesmo quando k = n 1 ou k = 0. Para


remover o elemento de ndice 51 (supondo que 51 < n), basta dizer
n = Remove (51, v, n);

Verso recursiva. um bom exerccio escrever uma verso recursiva da fun-


o Remove. O tamanho de uma instncia do problema medido pela dife-
rena n k e o problema considerado pequeno se n k = 1:

int RemoveR (int k, int v[], int n) {


if (k == n-1) return n - 1;
else {
v[k] = v[k+1];
return RemoveR (k + 1, v, n);
}
}

Exerccios
3.3.1 Critique a seguinte variante da parte central do cdigo da funo Remove:
for (j = n-2; j >= k; j--) v[j] = v[j+1];
3.3.2 Critique a seguinte variante da parte central do cdigo da funo Remove:
for (j = k; j < n-1; j++) v[j] = v[j+1];
v[n-1] = 0;
3.3.3 Critique a seguinte variante da parte central do cdigo da funo Remove:
if (k < n-1)
for (j = k; j < n-1; j++) v[j] = v[j+1];
3.3.4 Discuta a seguinte verso da funo RemoveR:
int removeR2 (int k, int v[], int n) {
if (k < n - 1) {
removeR2 (k, v, n - 1);
v[n-2] = v[n-1]; }
return n - 1; }
ELSEVIER Captulo 3. Vetores 17

3.4 Insero
A operao de insero consiste em introduzir um novo elemento y entre a
posio de ndice k 1 e a posio de ndice k no vetor v[0 . . n1]. Isto faz
sentido no s quando 1 k n 1, mas tambm quando k igual a 0 (insere
no incio) e quando k igual a n (insere no m). Em suma, faz sentido quando
0 k n.

/* Insere y entre as posies k-1 e k do vetor v[0..n-1]


* e devolve o novo valor de n. Supe que 0 <= k <= n.
int Insere (int k, int y, int v[], int n) {
int j;
for (j = n; j > k; j--)
v[j] = v[j-1];
v[k] = y;
return n + 1;
}

A funo s deve ser usada se o vetor no estiver cheio, ou seja, se n < N


(veja pgina 11). Para inserir um novo elemento com valor 999 entre as posies
50 e 51 (supondo que 51 n), basta dizer
n = Insere (51, 999, v, n);

Verso recursiva. um bom exerccio escrever uma verso recursiva da fun-


o Insere:

int InsereR (int k, int y, int v[], int n) {


if (k == n) v[n] = y;
else {
v[n] = v[n-1];
InsereR (k, y, v, n - 1);
}
return n + 1;
}

Exerccios
3.4.1 Critique a seguinte verso da funo InsereR:
int insereR2 (int k, int y, int v[], int n) {
if (k == n) {
18 ALGORITMOS em linguagem C ELSEVIER

v[n] = y;
return n + 1;
} else {
v[n] = v[n-1];
return insereR2 (k, y, v, n - 1); } }
3.4.2 Discuta a seguinte variante da funo InsereR:
int insereR3 (int k, int y, int v[], int n) {
if (k == n) {
v[n] = y;
return n + 1;
} else {
int z = v[k];
v[k] = y;
return insereR3 (k + 1, z, v, n); } }

3.5 Busca seguida de remoo


Considere agora uma combinao das operaes de busca e remoo. Suponha
que queremos remover todos os elementos nulos do vetor v[0 . . n1]. Aplicada
ao vetor
111, 222, 0, 0, 333, 0, 444, 0 ,
por exemplo, a funo deve produzir 111, 222, 333, 444. claro que a funo
deve devolver o novo valor de n.
/* Esta funo remove todos os elementos nulos de v[0..n-1],
* deixa o resultado em v[0..i-1], e devolve i. */
int RemoveZeros (int v[], int n) {
int i = 0, j;
for (j = 0; j < n; j++)
if (v[j] != 0) {
v[i] = v[j];
i += 1;
}
return i;
}

Observe como o cdigo funciona bem nos casos extremos: quando n vale 0,
quando v[0 . . n1] no tem zeros, e quando v[0 . . n1] s tem zeros. No incio
de cada iterao, temos os seguintes invariantes: i j e v[0 . . i1] o resultado
da remoo dos zeros do vetor v[0 . . j1] original.
ELSEVIER Captulo 3. Vetores 19

0 i j n1
999 999 999 999 999 000 999 000 999 000 999 000
verso sem zeros do vetor lixo
v[0 . . j1] original

Figura 3.2: Estado do vetor v[0 . . n1] no incio de uma iterao da funo
RemoveZeros.

Maus exemplos. fcil escrever uma verso ineciente de RemoveZeros. O


cdigo abaixo simples mas desperdia tempo e espao (alm de depender da
restrio articial n 1000):

int w[1000], i = 0, j;
for (j = 0; j < n; j++) w[j] = v[j]; /* ineficiente */
for (j = 0; j < n; j++)
if (w[j] != 0)
v[i] = w[j], i += 1;
return i;

Eis outra soluo deselegante e ineciente. O comando v[j] = v[j+1] no copia


v[j+1] para o seu lugar denitivo e por isso precisa ser repetido muitas vezes:

int i = 0, j = 0; /* "j = 0" suprfluo */


while (i < n) {
if (v[i] != 0) i += 1;
else {
for (j = i; j+1 < n; j++) /* ineficiente */
v[j] = v[j+1]; /* ineficiente */
--n;
}
}
return n;

Verso recursiva. Eis uma soluo recursiva do problema:

int RemoveZerosR (int v[], int n) {


int m;
if (n == 0) return 0;
m = RemoveZerosR (v, n - 1);
if (v[n-1] == 0) return m;
v[m] = v[n-1];
return m + 1;
}
20 ALGORITMOS em linguagem C ELSEVIER

Exerccios
3.5.1 Critique a seguinte variante da funo RemoveZeros:
int i, j;
for (i = n-1; i >= 0; i--)
if (v[i] == 0) {
for (j = i; j < n-1; j++) v[j] = v[j+1];
--n; }
return n;
3.5.2 Critique a seguinte verso da funo RemoveZeros:
int i, j;
for (i = 0; i < n; i++)
if (v[i] == 0) {
for (j = i; j+1 < n; j++) v[j] = v[j+1];
n -= 1; }
return n;
3.5.3 Critique a seguinte verso da funo RemoveZeros. H alguma maneira simples
de corrigir o cdigo?
int i, z = 0;
for (i = 0; i < n; i++) {
if (v[i] == 0) z += 1;
v[i-z] = v[i]; }
return n - z;
Captulo 4

Listas encadeadas

Uma lista encadeada uma representao de uma sequncia de objetos na me-


mria do computador. Cada elemento da sequncia armazenado em uma c-
lula da lista. As clulas que armazenam elementos consecutivos da sequncia
no cam necessariamente em posies consecutivas da memria.

4.1 Denio
Uma lista encadeada uma sequncia de registros (veja Apndice E) que
chamaremos clulas. Cada clula contm um objeto de determinado tipo e
o endereo (veja Seo D.1) da clula seguinte (no caso da ltima clula, esse
endereo NULL).
Suporemos neste captulo que os objetos armazenados nas clulas so do
tipo int. A estrutura das clulas pode, ento, ser denida assim:

struct cel {
int contedo; 1 999 r -
struct cel *seg; contedo seg
};

conveniente tratar as clulas como um novo tipo de dados (veja Seo J.3),
que chamaremos clula:
typedef struct cel clula;
Uma clula c e um ponteiro p para uma clula podem agora ser declarados
1
Veja Seo A.4.
22 ALGORITMOS em linguagem C ELSEVIER

assim:
clula c;
clula *p;

Se c uma clula ento c.contedo o contedo da clula e c.seg o


endereo da clula seguinte. Se p o endereo de uma clula, ento p->contedo
o contedo da clula e p->seg o endereo da clula seguinte (veja Seo E.2).
Se p o endereo da ltima clula da lista ento p->seg vale NULL.

999 r - 999 r - 999 r - 999 r - 999 r - 999 r

Figura 4.1: Uma lista encadeada (veja Figura D.2 no Apndice D).

O endereo de uma lista encadeada o endereo de sua primeira clula.


Se p o endereo de uma lista, podemos dizer, simplesmente, p uma lista
e considere a lista p. Reciprocamente, a expresso p uma lista deve ser
interpretada como p o endereo da primeira clula de uma lista.
A seguinte observao coloca em evidncia a natureza recursiva das listas
encadeadas: para toda lista encadeada p,
1. p NULL ou
2. p->seg uma lista encadeada.
Muitos algoritmos que manipulam listas cam mais simples quando escritos em
estilo recursivo.

Exerccio
4.1.1 Dizemos que uma clula D sucessora de uma clula C se C.seg = &D. Nas
mesmas condies, dizemos que C antecessora de D. Um ciclo uma sequncia
(C1 , C2 , . . . , Ck ) de clulas tal que Ci+1 sucessora de Ci para i = 1, 2, . . . , k1 e
C1 sucessora de Ck . Mostre que uma coleo L de clulas uma lista encadeada
se e somente se (1) a sucessora de cada elemento de L est em L, (2) cada elemento
de L tem no mximo uma antecessora, (3) exatamente um elemento de L no tem
antecessora em L e (4) no h ciclos em L.
ELSEVIER Captulo 4. Listas encadeadas 23

4.2 Listas com cabea e sem cabea


Uma lista encadeada pode ser vista de duas maneiras diferentes, dependendo do
papel que sua primeira clula desempenha. Na lista com cabea, a primeira
clula serve apenas para marcar o incio da lista e portanto o seu contedo
irrelevante. A primeira clula a cabea da lista. Se lst o endereo da
cabea ento lst->seg vale NULL se e somente se a lista est vazia. Para criar
uma lista vazia deste tipo, basta dizer

clula c, *lst; clula *lst;


c.seg = NULL; ou lst = malloc (sizeof (clula)); 2
lst = &c; lst->seg = NULL;

Na lista sem cabea, o contedo da primeira clula to relevante quanto


o das demais. A lista est vazia se no tem clula alguma. Para criar uma lista
vazia lst basta dizer
clula *lst;
lst = NULL;

Embora as listas sem cabea sejam mais puras, trataremos preferencial-


mente de listas com cabea, pois elas so mais fceis de manipular. O caso das
listas sem cabea ser relegado aos exerccios.

void Imprima (clula *lst) {


clula *p;
for (p = lst; p != NULL; p = p->seg)
printf ("%d\n", p->contedo);
}

Figura 4.2: A funo imprime o contedo de uma lista encadeada lst sem cabea.
Para aplicar a funo a uma lista com cabea, diga Imprima (lst->seg). Outra pos-
sibilidade trocar o fragmento for (p = lst por for (p = lst->seg, obtendo assim
uma verso da funo que s se aplica a listas com cabea.

4.3 Busca em lista encadeada


fcil vericar se um objeto x pertence a uma lista encadeada, ou seja, se x
igual ao contedo de alguma clula da lista:
2
Veja Seo F.2.
24 ALGORITMOS em linguagem C ELSEVIER

/* Esta funo recebe um inteiro x e uma lista encadeada lst


* com cabea. Devolve o endereo de uma clula que contm x
* ou devolve NULL se tal clula no existe. */
clula *Busca (int x, clula *lst) {
clula *p;
p = lst->seg;
while (p != NULL && p->contedo != x)
p = p->seg;
return p;
}

(Observe como o cdigo simples. Observe como produz o resultado correto


mesmo quando a lista est vazia.) Eis uma verso recursiva da funo:

clula *BuscaR (int x, clula *lst) {


if (lst->seg == NULL)
return NULL;
if (lst->seg->contedo == x)
return lst->seg;
return BuscaR (x, lst->seg);
}

Exerccios
4.3.1 Que acontece se trocarmos while (p != NULL && p->contedo != x) por
while (p->contedo != x && p != NULL) na funo Busca?
4.3.2 Critique a seguinte variante da funo Busca:
int achou = 0;
clula *p;
p = lst->seg;
while (p != NULL && achou != 0) {
if (p->contedo == x) achou = 1;
p = p->seg; }
if (achou) return p;
else return NULL;
4.3.3 Lista sem cabea. Escreva uma verso da funo Busca para listas sem cabea.
4.3.4 Mnimo. Escreva uma funo que encontre uma clula de contedo mnimo.
Faa duas verses: uma iterativa e uma recursiva.
4.3.5 Lista crescente. Uma lista crescente se o contedo de cada clula no
maior que o contedo da clula seguinte. Escreva uma funo que faa uma busca
ELSEVIER Captulo 4. Listas encadeadas 25

em uma lista crescente. Faa verses para listas com e sem cabea. Faa uma verso
recursiva e outra iterativa.

4.4 Remoo de uma clula


Suponha que queremos remover uma clula de uma lista. Como devemos especi-
car a clula a ser removida? Parece natural apontar para a clula em questo,
mas fcil perceber o defeito da ideia. melhor apontar para a clula anterior
que queremos remover. ( bem verdade que esta conveno no permite re-
mover a primeira clula da lista, mas esta operao no necessria no caso de
listas com cabea.) A funo abaixo implementa a ideia:

/* Esta funo recebe o endereo p de uma clula em uma


* lista encadeada e remove da lista a clula p->seg.
* A funo supe que p != NULL e p->seg != NULL. */
void Remove (clula *p) {
clula *lixo;
lixo = p->seg;
p->seg = lixo->seg;
free (lixo); 3
}

A funo no faz mais que alterar o valor de um ponteiro. No preciso


copiar coisas de um lugar para outro, como zemos na Seo 3.3 ao remover um
elemento de um vetor. A funo consome sempre o mesmo tempo, quer a clula
a ser removida esteja perto do incio da lista, quer esteja perto do m.

Exerccios
4.4.1 Critique a seguinte variante da funo Remove:
void Remove (clula *p, clula *lst) {
clula *lixo;
lixo = p->seg;
if (lixo->seg == NULL) p->seg = NULL;
else p->seg = lixo->seg;
free (lixo); }
4.4.2 Lista sem cabea. Escreva uma funo que remova uma determinada clula
de uma lista encadeada sem cabea. (Ser preciso tomar algumas decises de projeto
antes de comear a programar.)
3
Veja Seo F.3.
26 ALGORITMOS em linguagem C ELSEVIER

4.5 Insero de nova clula


Suponha que queremos inserir uma nova clula com contedo y entre a clula
apontada por p e a seguinte. claro que isso s faz sentido se p for diferente
de NULL.
/* A funo insere uma nova clula em uma lista encadeada
* entre a clula p e a seguinte (supe-se que p != NULL).
* A nova clula ter contedo y. */
void Insere (int y, clula *p) {
clula *nova;
nova = malloc (sizeof (clula));
nova->contedo = y;
nova->seg = p->seg;
p->seg = nova;
}

A funo no faz mais que alterar os valores de alguns ponteiros. No h


movimentao de clulas para abrir espao para um nova clula, como zemos
na Seo 3.4 ao inserir um novo elemento em um vetor. Assim, o tempo que a
funo consome no depende do ponto de insero: tanto faz inserir uma nova
clula na parte inicial da lista quanto na parte nal.
A funo se comporta corretamente mesmo quando a insero se d no m
da lista, isto , quando p->seg vale NULL. Se a lista tem cabea, a funo pode
ser usada para inserir no incio da lista: basta que p aponte para a clula-cabea.
Mas a funo no capaz de inserir antes da primeira clula de uma lista sem
cabea.

Exerccios
4.5.1 Por que a seguinte verso de Insere no funciona?
clula nova;
nova.contedo = y;
nova.seg = p->seg;
p->seg = &nova;
4.5.2 Escreva uma funo que insira uma nova clula entre a clula cujo endereo p
e a anterior.
4.5.3 Lista sem cabea. Escreva uma funo que insira uma nova clula numa dada
posio de uma lista encadeada sem cabea. (Ser preciso tomar algumas decises de
projeto antes de comear a programar.)
ELSEVIER Captulo 4. Listas encadeadas 27

4.6 Busca seguida de remoo ou insero


Considere uma lista encadeada com cabea. Dado um inteiro x, queremos re-
mover da lista a primeira clula que contiver x; se tal clula no existe, no
preciso fazer nada.

/* Esta funo recebe uma lista encadeada lst com cabea


* e remove da lista a primeira clula que contiver x,
* se tal clula existir. */
void BuscaERemove (int x, clula *lst) {
clula *p, *q;
p = lst;
q = lst->seg;
while (q != NULL && q->contedo != x) {
p = q;
q = q->seg;
}
if (q != NULL) {
p->seg = q->seg;
free (q);
}
}

No incio de cada iterao, imediatamente antes da comparao de q com


NULL, vale a relao q = p->seg (ou seja, q est sempre um passo frente de p).
Suponha agora que queremos inserir na lista uma nova clula com contedo
y imediatamente antes da primeira clula que tiver contedo x; se tal clula no
existe, devemos inserir y no m da lista.

/* Recebe uma lista encadeada lst com cabea e insere uma


* nova clula na lista imediatamente antes da primeira que
* contiver x. Se nenhuma clula contiver x, a nova clula
* ser inserida no fim da lista. A nova clula ter
* contedo y. */
void BuscaEInsere (int y, int x, clula *lst) {
clula *p, *q, *nova;
nova = malloc (sizeof (clula));
nova->contedo = y;
p = lst;
q = lst->seg;
28 ALGORITMOS em linguagem C ELSEVIER

while (q != NULL && q->contedo != x) {


p = q;
q = q->seg;
}
nova->seg = q;
p->seg = nova;
}

Exerccios
4.6.1 Escreva uma verso da funo BuscaERemove para listas encadeadas sem cabea.
(Veja Exerccio 4.4.2.)
4.6.2 Escreva uma verso da funo BuscaEInsere para listas encadeadas sem cabea.
(Veja Exerccio 4.5.3.)
4.6.3 Escreva uma funo para remover de uma lista encadeada todos os elementos que
contm x. Faa uma verso iterativa e uma recursiva.
4.6.4 Escreva uma funo que remova de uma lista encadeada uma clula cujo contedo
tem valor mnimo. Faa uma verso iterativa e uma recursiva.

4.7 Exerccios: manipulao de listas


A maioria dos exerccios desta seo tem duas verses: uma para lista com cabea e
outra para lista sem cabea. Alm disso, interessante resolver cada exerccio de duas
maneiras: uma iterativa e uma recursiva.

4.7.1 Vetor para lista. Escreva uma funo que copie um vetor para uma lista
encadeada.
4.7.2 Lista para vetor. Escreva uma funo que copie uma lista encadeada para
um vetor.
4.7.3 Cpia. Escreva uma funo que faa uma cpia de uma lista dada.
4.7.4 Comparao. Escreva uma funo que decida se duas listas dadas tm o mesmo
contedo.
4.7.5 Concatenao. Escreva uma funo que concatene duas listas encadeadas
(isto , amarre a segunda no m da primeira).
4.7.6 Contagem. Escreva uma funo que conte o nmero de clulas de uma lista
encadeada.
4.7.7 Ponto mdio. Escreva uma funo que receba uma lista encadeada e devolva
o endereo de uma clula que esteja o mais prximo possvel do ponto mdio da lista.
Faa isso sem calcular explicitamente o nmero n de clulas da lista e o quociente n/2.
ELSEVIER Captulo 4. Listas encadeadas 29

4.7.8 Contagem e remoo. Escreva uma funo que remova a k-sima clula de
uma lista encadeada.
4.7.9 Contagem e insero. Escreva uma funo que insira uma nova clula com
contedo x entre a k-sima e a (k+1)-sima clulas de uma lista encadeada.
4.7.10 Liberao. Escreva uma funo que aplique a funo free a todas as clu-
las de uma lista encadeada. Estamos supondo, claro, que cada clula da lista foi
originalmente alocado por malloc.
4.7.11 Inverso. Escreva uma funo que inverta a ordem das clulas de uma lista
encadeada (a primeira passa a ser a ltima, a segunda passa a ser a penltima etc.).
Faa isso sem criar novas clulas; apenas altere os ponteiros.
4.7.12 Projeto de programao. Digamos que um documento um vetor de ca-
racteres contendo apenas letras, espaos e sinais de pontuao. Digamos que uma
palavra um segmento maximal que consiste apenas de letras. Escreva uma funo
que imprima uma relao de todas as palavras de um documento dado juntamente com
o nmero de ocorrncias de cada palavra.

4.8 Outros tipos de listas encadeadas


Poderamos denir vrios outros tipos de listas encadeadas alm do tipo bsico
discutido acima. Seguem dois exemplos importantes.
a. Numa lista encadeada circular, a ltima clula aponta para a primeira.
A lista pode ou no ter uma clula-cabea. (Se no tiver cabea, as
expresses primeira clula e ltima clula no fazem muito sentido.)
b. Numa lista duplamente encadeada, cada clula contm o endereo
da clula anterior e o da clula seguinte. A lista pode ou no ter uma
clula-cabea, conforme as convenincias do programador.
As seguintes questes so apropriadas para qualquer tipo de lista encadeada:
Em que condies a lista est vazia? Como remover a clula apontada por p?
Como remover a clula seguinte apontada por p? Como remover a clula
anterior apontada por p? Como inserir uma nova clula entre a apontada
por p e a anterior? Como inserir uma nova clula entre a apontada por p e a
seguinte?

Exerccios
4.8.1 Descreva, em C, a estrutura de uma clula de uma lista duplamente encadeada.
4.8.2 Escreva uma funo que remova de uma lista duplamente encadeada a clula cujo
endereo p. Que dados sua funo recebe? Que coisa devolve?
30 ALGORITMOS em linguagem C ELSEVIER

4.8.3 Suponha uma lista duplamente encadeada. Escreva uma funo que insira uma
nova clula com contedo y logo aps a clula cujo endereo p. Que dados sua funo
recebe? Que coisa devolve?
4.8.4 Problema de Josephus. Imagine n pessoas dispostas em crculo. Suponha que
as pessoas esto numeradas de 1 a n no sentido horrio. Comeando com a pessoa de
nmero 1, percorra o crculo no sentido horrio e elimine cada m-sima pessoa enquanto
o crculo tiver duas ou mais pessoas. (Veja Josephus problem na Wikipedia [21].) Qual
o nmero do sobrevivente? Escreva e teste uma funo que resolva o problema.
4.8.5 Leia o verbete Linked list na Wikipedia [21].
Captulo 5

Filas

Fila: Fileira de pessoas que se colocam umas atrs das outras,


pela ordem cronolgica de chegada a guichs
ou a quaisquer estabelecimentos onde haja grande auncia de interessados.
Novo Dicionrio Aurlio

Uma la uma sequncia dinmica, isto , uma sequncia da qual elementos


podem ser removidos e na qual novos elementos podem ser inseridos. Mais
especicamente, uma la uma sequncia de objetos, todos do mesmo tipo,
sujeita s seguintes regras de comportamento: (1) sempre que solicitamos a
remoo de um elemento, o elemento removido o primeiro da sequncia e
(2) sempre que solicitamos a insero de um novo objeto, o objeto inserido no
m da sequncia.
Podemos resumir o comportamento de uma la com a seguinte frase: o
elemento removido da la sempre o que est l h mais tempo. Outra maneira
de dizer isso: o primeiro objeto inserido na la tambm o primeiro a ser
removido. Esta poltica conhecida pela abreviatura FIFO da expresso First-
In-First-Out.

5.1 Implementao em vetor


Uma la pode ser armazenada em um segmento f[s..t-1] de um vetor
f[0..N-1]. claro que devemos ter 0 s t N. O primeiro elemento
da la est na posio s e o ltimo na posio t-1. A la est vazia se s igual
a t e cheia se t igual a N. Para remover um elemento da la basta dizer
x = f[s++];
32 ALGORITMOS em linguagem C ELSEVIER

o que equivale ao par de comandos x = f[s]; s += 1; (veja Seo J.1).


claro que o programador no deve fazer isso se a la estiver vazia. Para inserir
um objeto y na la basta dizer
f[t++] = y;
Se o programador zer isso quando a la j est cheia, dizemos que a la trans-
bordou. Em geral, a tentativa de inserir em uma la cheia um evento excep-
cional, que resulta de um mau planejamento lgico do seu programa.

0 s t N-1
111 222 333 444 555 666

Figura 5.1: O vetor f[s..t-1] armazena uma la.

Exerccio
5.1.1 Suponha que, diferentemente da conveno adotada no texto, a parte do vetor
ocupada pela la f[s..t]. Escreva o comando que remove um elemento da la.
Escreva o comando que insere um objeto y na la.

5.2 Aplicao: distncias em uma rede


Imagine n cidades numeradas de 0 a n 1 e interligadas por estradas de mo
nica. As ligaes entre as cidades so representadas por uma matriz A (veja
Seo F.5) denida da seguinte maneira: A[x][y] vale 1 se existe estrada da
cidade x para a cidade y e vale 0 em caso contrrio. (Veja Figura 5.2.)
A distncia1 de uma cidade o a uma cidade x o menor nmero de estradas
que preciso percorrer para ir de o a x. Nosso problema: determinar a distncia
de uma dada cidade o a cada uma das outras cidades.
As distncias sero armazenadas em um vetor d de tal modo que d[x] seja
a distncia de o a x. Se for impossvel chegar de o a x, podemos dizer que d[x]
vale . Usaremos 1 para representar (uma vez que nenhuma distncia
real pode ter valor 1).
O seguinte algoritmo usa o conceito de la para resolver nosso problema das
distncias. Uma cidade considerada ativa se j foi visitada mas as estradas
1
A palavra distncia j traz embutida a ideia de minimalidade. As expresses distncia
mnima e menor distncia so redundantes.
ELSEVIER Captulo 5. Filas 33

que nela comeam ainda no foram exploradas. O algoritmo mantm as cidades


ativas numa la. Em cada iterao, o algoritmo remove da la uma cidade x
e insere na la todas as vizinhas a x que ainda no foram visitadas. Eis uma
implementao do algoritmo:

/* A matriz A representa as interligaes entre cidades


* 0,1,..,n-1: h uma estrada (de mo nica) de x a y se
* e somente se A[x][y] == 1. A funo devolve um vetor d
* tal que d[x] a distncia da cidade o cidade x. */
int *Distncias (int **A, int n, int o) {
int *d, x, y;
int *f, s, t;
d = malloc (n * sizeof (int)); 2
for (x = 0; x < n; x++) d[x] = -1;
d[o] = 0;
f = malloc (n * sizeof (int));
s = 0; t = 1; f[s] = o;
while (s < t) {
/* f[s..t-1] uma fila de cidades */
x = f[s++];
for (y = 0; y < n; y++)
if (A[x][y] == 1 && d[y] == -1) {
d[y] = d[x] + 1;
f[t++] = y;
}
}
free (f);
return d;
}

Ao longo da execuo do algoritmo, o vetor f[s..t-1] armazena a la de


cidades, enquanto f[0..s-1] armazena as cidades que j saram da la. Para
compreender o algoritmo (e provar que ele est correto), basta observar que as
seguintes propriedades valem no incio de cada iterao, imediatamente antes
da comparao s < t:
1. para cada v no vetor f[0..t-1], existe um caminho de o a v, de com-
primento d[v], cujas cidades esto todas no vetor f[0..t-1];
2
Veja Seo F.2.
34 ALGORITMOS em linguagem C ELSEVIER

2. para cada v no vetor f[0..t-1], todo caminho de o a v tem comprimento


pelo menos d[v];
3. toda estrada que comea em f[0..s-1] termina em f[0..t-1].
Deduz-se imediatamente de 1 e 2 que, para cada v no vetor f[0..t-1], o nmero
d[v] a distncia de o a v. Para provar que as trs propriedades so invariantes,
preciso observar que duas outras propriedades valem no incio de cada iterao:
4. d[f[s]] d[f[s+1]] d[f[t-1]] e
5. d[f[t-1]] d[f[s]] + 1.
Em outras palavras, a sequncia de nmeros d[f[s]], . . . , d[f[t-1]] tem a
forma k, . . . , k ou a forma k, . . , k, k+1, . . , k+1.

0 1 2 3 4 5
0 0 1 0 0 0 0
1 0 0 1 0 0 0 5
r
0
r  r
4
0 1 2 3 4 5
2 0 0 0 0 1 0 @ @
R
@ ? @ d 2 3 1 0 1 6
3 0 0 1 0 1 0 @ 6 @ I
4 1 0 0 0 0 0 @r - r @r
1 2 3
5 0 1 0 0 0 0

Figura 5.2: A matriz representa cidades 0, . . . , 5 interligadas por estradas de


mo nica. O vetor d d as distncias da cidade 3 a cada uma das demais.

Exerccios
5.2.1 Transbordamento. Na funo Distncias, o espao alocado para o vetor f
suciente? O comando f[t++] = y pode provocar o transbordamento da la?
5.2.2 ltima iterao. Suponha que os invariantes 1 a 3 valem no incio da ltima
iterao da funo Distncias (quando s igual a t). Mostre que, para cada v no vetor
f[0..t-1], o nmero d[v] a distncia de o a v. Mostre tambm que impossvel ir
da cidade o a uma cidade que esteja fora do vetor f[0..t-1].
5.2.3 Primeira iterao. Verique que os invariantes 1 a 5 valem no incio da
primeira iterao da funo Distncias.
5.2.4 Invariantes. Suponha que os invariantes 1 a 5 da funo Distncias valem no
incio de uma iterao qualquer que no a ltima. Mostre que elas continuam valendo
no incio da prxima iterao. (A prova surpreendentemente longa e delicada.)
5.2.5 Labirinto. Imagine um tabuleiro quadrado 10por10. As casas livres so
ELSEVIER Captulo 5. Filas 35

marcadas com 0 e as casas bloqueadas com 1. As casas (1, 1) e (10, 10) esto livres.
Ajude uma formiga que est na casa (1, 1) a chegar casa (10, 10). Em cada passo, a
formiga s pode se deslocar para uma casa livre que esteja direita, esquerda, acima
ou abaixo da casa em que est.

5.3 Implementao circular


No problema discutido na seo anterior, o vetor que abriga a la no precisa ter
mais componentes que o nmero total de cidades, pois cada cidade entra na la
no mximo uma vez. Em geral, entretanto, difcil prever o espao necessrio
para abrigar a la. Nesses casos, mais seguro implementar a la de maneira
circular. Suponha que os elementos da la esto dispostos no vetor f[0..N-1]
de uma das seguintes maneiras:

f[s..t-1] ou f[s..N-1] f[0..t-1]

(veja Figura 5.3). Teremos sempre 0 s < N e 0 t < N, mas no podemos


supor que s t. A la est vazia se t = s e cheia se

t+1 = s ou t+1 = N e s = 0 ,

ou seja, se (t+1) % N = s.3 A posio t car sempre desocupada, para que


possamos distinguir uma la cheia de uma vazia. Para remover um elemento da
la basta fazer
x = f[s++];
if (s == N) s = 0;
(supondo que a la no est vazia). Para inserir um objeto y na la (supondo
que ela no est cheia), faa
f[t++] = y;
if (t == N) t = 0;

Exerccio
5.3.1 Considere a manipulao de uma la circular. Escreva uma funo que devolva
o tamanho da la. Escreva uma funo que remova um elemento da la e devolva esse
elemento; se a la estiver vazia, no faa nada. Escreva uma funo que verique se a
la est cheia e em caso negativo insira um objeto dado na la. (Lembre-se de que uma
la um pacote com trs objetos: um vetor e dois ndices. No use variveis globais.)

3
O valor da expresso a % b o resto da diviso de a por b, ou seja, a b a/b.
36 ALGORITMOS em linguagem C ELSEVIER

0 s t N-1
111 222 333 444 555 666

0 t s N-1
444 555 666 111 222 333

Figura 5.3: Fila circular. Na primeira parte da gura, a la est armazenada no vetor
f[s..t-1]. Na segunda parte, a la est armazenada no vetor f[s..N-1] concatenado
com f[0..t-1].

5.4 Implementao em lista encadeada


Considere agora a implementao de uma la em uma lista encadeada. Digamos
que as clulas da lista so do tipo clula:

typedef struct cel {


int valor;
struct cel *seg;
} clula;

preciso tomar algumas decises de projeto sobre a maneira de acomodar


a la na lista. Vamos supor que nossa lista encadeada no tem cabea, que o
primeiro elemento da la car na primeira clula e que o ltimo elemento da
la car na ltima clula.
Para manipular a la, precisamos de dois ponteiros: um ponteiro s apontar
o primeiro elemento da la e um ponteiro t apontar o ltimo. A la estar
vazia se s = t = NULL. Suporemos que s = NULL sempre que t = NULL e
vice-versa. Uma la vazia pode ser criada assim:

clula *s, *t;


s = t = NULL;

Para remover um elemento da la (supondo que ela no est vazia), ser preciso
passar funo de remoo os endereos das variveis s e t para que os valores
dessas variveis possam ser alterados:

int Remove (clula **es, clula **et) {


clula *p;
int x;
p = *es;
x = p->valor;
ELSEVIER Captulo 5. Filas 37

*es = p->seg;
free (p);
if (*es == NULL) *et = NULL;
return x;
}

A funo de insero precisa levar em conta a possibilidade de insero em la


vazia:
void Insere (int y, clula **es, clula **et) {
clula *nova;
nova = malloc (sizeof (clula));
nova->valor = y;
nova->seg = NULL;
if (*et == NULL) *et = *es = nova;
else {
(*et)->seg = nova;
*et = nova;
}
}

Exerccios
5.4.1 Implemente uma la em uma lista encadeada com clula-cabea.
5.4.2 Implemente uma la em uma lista encadeada circular com clula-cabea. O
primeiro elemento da la car na segunda clula e o ltimo elemento car na clula
anterior cabea. Para manipular a la basta conhecer o endereo ff da clula-cabea.
5.4.3 Implemente uma la em uma lista duplamente encadeada sem clula-cabea.
Mantenha um ponteiro para a primeira clula e um ponteiro para a ltima.
5.4.4 Leia o verbete Queue na Wikipedia [21].
Captulo 6

Pilhas

Pilha: Poro de objetos dispostos uns sobre os outros.


Dicionrio Houaiss

Uma pilha uma sequncia dinmica, isto , uma sequncia da qual elementos
podem ser removidos e na qual novos elementos podem ser inseridos. Mais espe-
cicamente, uma pilha uma sequncia de objetos, todos do mesmo tipo, sujeita
s seguintes regras de comportamento: (1) sempre que solicitamos a remoo
de um elemento, o elemento removido o ltimo da sequncia e (2) sempre
que solicitamos a insero de um novo objeto, o objeto inserido no m da
sequncia.
Podemos resumir o comportamento de uma pilha com a seguinte frase: o
elemento removido da pilha sempre o que est l h menos tempo. Outra
maneira de dizer isso: o primeiro objeto inserido na pilha o ltimo a ser
removido. Esta poltica conhecida pela abreviatura LIFO da expresso Last-
In-First-Out.

6.1 Implementao em vetor


Suponha que nossa pilha est armazenada em um vetor p[0..N-1]. A parte do
vetor efetivamente ocupada pela pilha p[0..t-1]. O ndice t-1 dene o topo
da pilha.
A pilha est vazia se t vale 0 e cheia se t vale N. Para remover um
elemento da pilha, ou seja, para desempilhar um elemento, faa
x = p[--t];
o que equivale ao par de comandos t -= 1; x = p[t]; (veja Seo J.1). claro
que o programador no deve fazer isto se a pilha estiver vazia. Para consultar
40 ALGORITMOS em linguagem C ELSEVIER

a pilha sem desempilhar basta fazer x = p[t-1]. Para empilhar um objeto y,


ou seja, para inserir y na pilha faa
p[t++] = y;
o que equivale ao par de comandos p[t] = y; t += 1;. Antes de empilhar,
preciso ter certeza de que a pilha no est cheia. Em geral, a tentativa de inserir
em uma pilha cheia um indcio de mau planejamento lgico do seu programa.

0 t N-1
111 222 333 444 555 666 777

Figura 6.1: O vetor p[0..t-1] armazena uma pilha.

Exerccios
6.1.1 Suponha que, diferentemente da conveno adotada no texto, a parte do vetor
ocupada pela pilha p[0..t]. Escreva o comando que remove um elemento da pilha.
Escreva o comando que insere um objeto na pilha.
6.1.2 Inverso de palavras. Escreva uma funo que inverta a ordem das letras
de cada palavra de uma sentena, preservando a ordem das palavras. Suponha que as
palavras da sentena so separadas por espaos. A aplicao da operao sentena
AMU MEGASNEM ATERCES, por exemplo, deve produzir UMA MENSAGEM SECRETA.
6.1.3 Permutaes produzidas pelo desempilhar [10, sec. 2.2.1]. Suponha que
os nmeros inteiros 1, 2, 3, 4 so colocados, nesta ordem, numa pilha inicialmente vazia.
Depois de cada operao de empilhar, voc pode retirar zero ou mais elementos da
pilha. Cada nmero retirado da pilha impresso numa folha de papel. Por exemplo,
a sequncia de operaes E, E, D, E, D, D, E, D, onde E signica empilhar o prximo
nmero da sequncia e D signica desempilhar, produz a impresso da sequncia
2, 3, 1, 4. Quais das 24 permutaes de 1, 2, 3, 4 podem ser obtidas desta maneira?

6.2 Aplicao: parnteses e chaves


Considere o problema de decidir se uma dada sequncia de parnteses e chaves
bem-formada. Por exemplo, a sequncia

( ( ) { ( ) } )

bem-formada, enquanto a sequncia ( { ) } malformada.


ELSEVIER Captulo 6. Pilhas 41

Suponha que a sequncia de parnteses e chaves est armazenada em uma


string s (veja Apndice G). De acordo com as convenes da linguagem C, o
ltimo elemento da string o caractere nulo \0 (veja Apndice B).

/* Esta funo devolve 1 se a string s contm uma sequncia


* bem-formada de parnteses e chaves e devolve 0 se
* a sequncia est malformada. */
int BemFormada (char s[]) {
char *p; int t;
int n, i;
n = strlen (s);
p = malloc (n * sizeof (char));
t = 0;
for (i = 0; s[i] != \0; i++) {
/* p[0..t-1] uma pilha */
switch (s[i]) {
case ): if (t != 0 && p[t-1] == () --t;
else return 0;
break;
case }: if (t != 0 && p[t-1] == {) --t;
else return 0;
break;
default: p[t++] = s[i];
}
}
return t == 0; 1
}

(Eu deveria ter invocado free (p) antes de cada return; s no z isso para
no obscurecer a lgica da funo.) A pilha p jamais transborda porque nunca
ter mais elementos do que o nmero, n, de caracteres de s.

Exerccios
6.2.1 A funo BemFormada funciona corretamente se s tem apenas dois elementos?
apenas um? nenhum?
6.2.2 Mostre que o processo iterativo na funo BemFormada tem o seguinte invariante:
1
Veja Seo J.2.
42 ALGORITMOS em linguagem C ELSEVIER

no incio de cada iterao, a string s est bem-formada se e somente se a sequncia


p[0..t-1] s[i...], formada pela concatenao de p[0..t-1] com s[i...], estiver
bem-formada.

6.3 Aplicao: notao posxa


Expresses aritmticas so usualmente escritas em notao inxa: os operadores
cam entre os operandos. Na notao posxa (ou polonesa) os operadores cam
depois dos operandos. Os exemplos da Figura 6.2 esclarecem o conceito. (A
propsito, veja o Exerccio 14.2.7.)

notao inxa notao posxa


(A+B*C) ABC*+
(A*(B+C)/D-E) ABC+*D/E-
(A+B*(C-D*(E-F)-G*H)-I*3) ABCDEF-*-GH*-*+I3*-
(A+B*C/D*E-F) ABC*D/E*+F-
(A*(B+(C*(D+(E*(F+G)))))) ABCDEFG+*+*+*

Figura 6.2: Expresses aritmticas em notao inxa e notao posxa. A


notao posxa dispensa parnteses. Os operandos (A, B etc.) aparecem na
mesma ordem nas duas notaes.

Nosso problema: traduzir para notao posxa uma expresso inxa dada.
Para simplicar, suporemos que a expresso inxa est correta e contm apenas
letras, parnteses e os smbolos +, -, * e /. Suporemos tambm que cada nome
de varivel tem uma letra apenas. Finalmente, suporemos que a expresso toda
est embrulhada em um par de parnteses. Se a expresso est armazenada
na string infix, o primeiro caractere da string ( e os dois ltimos so )
e \0.
Usaremos uma pilha para resolver o problema de traduo. Como a expres-
so inxa est embrulhada em parnteses, no ser preciso preocupar-se com
pilha vazia.

/* A funo abaixo recebe uma expresso infixa infix e


* devolve a correspondente expresso posfixa. */
char *InfixaParaPosfixa (char infix[]) {
char *posfix, x;
ELSEVIER Captulo 6. Pilhas 43

char *p; int t;


int n, i, j;
n = strlen (infix);
posfix = malloc (n * sizeof (char));
p = malloc (n * sizeof (char));
t = 0; p[t++] = infix[0]; /* empilha ( */
for (j = 0, i = 1; /*X*/ infix[i] != \0; i++) {
/* p[0..t-1] uma pilha de caracteres */
switch (infix[i]) {
case (: p[t++] = infix[i]; /* empilha */
break;
case ): while (1) { /* desempilha */
x = p[--t];
if (x == () break;
posfix[j++] = x; }
break;
case +:
case -: while (1) {
x = p[t-1];
if (x == () break;
--t; /* desempilha */
posfix[j++] = x; }
p[t++] = infix[i]; /* empilha */
break;
case *:
case /: while (1) {
x = p[t-1];
if (x == ( || x == + || x == -)
break;
--t;
posfix[j++] = x; }
p[t++] = infix[i];
break;
default: posfix[j++] = infix[i]; }
}
free (p);
posfix[j] = \0;
return posfix;
}
44 ALGORITMOS em linguagem C ELSEVIER

infix[0..i-1] p[0..t-1] posfix[0..j-1]


( (
(A ( A
(A* (* A
(A*( (*( A
(A*(B (*( AB
(A*(B* (*(* AB
(A*(B*C (*(* ABC
(A*(B*C+ (*(+ ABC*
(A*(B*C+D (*(+ ABC*D
(A*(B*C+D) (* ABC*D+
(A*(B*C+D)) ABC*D+*

Figura 6.3: Resultado da aplicao da funo InfixaParaPosfixa expresso inxa


(A*(B*C+D)). A gura registra os valores das variveis no incio de cada iterao (ou
seja, a cada passagem pelo ponto X do cdigo). Constantes e variveis vo diretamente
de infix para posfix. Todo parntese esquerdo vai para a pilha. Ao encontrar um
parntese direito, a funo remove tudo da pilha at o primeiro parntese esquerdo que
encontrar. Ao encontrar + ou - , a funo desempilha tudo at encontrar um parntese
esquerdo. Ao encontrar * ou / , desempilha tudo at um parntese esquerdo ou um +
ou um -.

Exerccios
6.3.1 Aplique expresso inxa (A+B)*D+E/(F+A*D)+C o algoritmo de converso
para notao posxa.
6.3.2 Na funo InfixaParaPosfixa, suponha que a string infix tem n caracteres
(sem contar o caractere nulo nal). Que altura a pilha pode atingir, no pior caso?
Em outras palavras, qual o valor mximo da varivel t? Que acontece se o nmero de
parnteses for limitado (menor que 10, por exemplo)?
6.3.3 Reescreva o cdigo da funo InfixaParaPosfixa de maneira um pouco mais
compacta, sem os while (1). Tire proveito dos recursos sintticos da linguagem C.
6.3.4 Reescreva a funo InfixaParaPosfixa sem supor que a expresso inxa est
embrulhada em um par de parnteses.
6.3.5 Reescreva a funo InfixaParaPosfixa supondo que a expresso inxa pode
estar incorreta.
6.3.6 Reescreva a funo InfixaParaPosfixa supondo que a expresso pode ter pa-
rnteses e chaves.
6.3.7 Valor de expresso posfixa. Suponha dada uma expresso aritmtica em
notao posxa sujeita s seguintes restries: cada varivel consiste em uma nica
letra do conjunto A..Z; no h constantes; os nicos operadores so +, -, *, / (todos
exigem dois operandos). Suponha dado tambm um vetor inteiro val, indexado por
ELSEVIER Captulo 6. Pilhas 45

A..Z, que d os valores das variveis. Escreva uma funo que calcule o valor da
expresso. Cuidado com divises por zero.

6.4 Implementao em lista encadeada


Uma pilha pode ser implementada em uma lista encadeada. Digamos que as
clulas da lista so do tipo clula:

typedef struct cel {


int valor;
struct cel *seg;
} clula;

Suporemos que nossa lista tem uma clula-cabea e que o topo da pilha est na
segunda clula (e no na ltima). Uma pilha (vazia) pode ser criada assim:

clula cabea;
clula *p;
p = &cabea;
p->seg = NULL;

Para manipular a pilha, basta dispor do ponteiro p, cujo valor ser sempre
&cabea. A pilha estar vazia se p->seg for NULL. Eis a funo que insere um
nmero y na pilha:

void Empilha (int y, clula *p) {


clula *nova;
nova = malloc (sizeof (clula));
nova->valor = y;
nova->seg = p->seg;
p->seg = nova;
}

Eis uma funo que remove um elemento de uma pilha no vazia:

int Desempilha (clula *p) {


int x; clula *q;
q = p->seg;
x = q->valor;
p->seg = q->seg;
free (q);
return x;
}
46 ALGORITMOS em linguagem C ELSEVIER

Exerccios
6.4.1 Implemente uma pilha em uma lista encadeada sem clula-cabea. A pilha ser
especicada pelo endereo da primeira clula da lista.
6.4.2 Reescreva as funes BemFormada e InfixaParaPosfixa (Sees 6.2 e 6.3 respec-
tivamente) armazenando a pilha em uma lista encadeada.
6.4.3 Leia o verbete Stack (data structure) na Wikipedia [21].

6.5 A pilha de execuo de um programa


Todo programa C composto por uma ou mais funes, sendo main a primeira
funo a ser executada. Para executar um programa, o computador usa uma
pilha de execuo. A operao pode ser descrita conceitualmente da seguinte
maneira. Ao encontrar a invocao de uma funo, o computador cria um novo
espao de trabalho, que contm todos os parmetros e todas as variveis locais
da funo. Esse espao de trabalho colocado na pilha de execuo (sobre
o espao de trabalho que invocou a funo) e a execuo da funo comea
(connada ao seu espao de trabalho). Quando a execuo da funo termina,
o seu espao de trabalho retirado da pilha e descartado. O espao de trabalho
que estiver agora no topo da pilha reativado e a execuo retomada do ponto
em que havia sido interrompida.
Considere o seguinte exemplo:

int G (int a, int b) {


return a + b;
}
int F (int i, int j, int k) {
int x;
x = /*2*/ G (i, j) /*3*/;
return x + k;
}
int main (void) {
int i, j, k, y;
i = 111; j = 222; k = 444;
y = /*1*/ F (i, j, k) /*4*/;
printf ("%d\n", y);
return EXIT_SUCCESS; 2
}
2
Veja Seo K.1.
ELSEVIER Captulo 6. Pilhas 47

O programa executado da seguinte maneira:


1. Um espao de trabalho criado para a funo main e colocado na pilha
de execuo. O espao contm as variveis locais i, j, k e y. A execuo
de main comea.
2. No ponto 1, a execuo de main suspensa e um espao de trabalho para
a funo F colocado na pilha. Esse espao contm os parmetros i, j,
k da funo (com valores 111, 222 e 444 respectivamente) e a varivel
local x. Comea ento a execuo de F.
3. No ponto 2, a execuo de F suspensa e um espao de trabalho para a
funo G colocado na pilha. Esse espao contm os parmetros a e b da
funo (com valores 111 e 222 respectivamente). Em seguida, comea a
execuo de G.
4. Quando a execuo de G termina, a funo devolve 333. O espao de
trabalho de G removido da pilha e descartado. O espao de trabalho de
F (que agora est no topo da pilha de execuo) reativado e a execuo
retomada no ponto 3. A primeira instruo executada x = 333;.
5. Quando a execuo de F termina, a funo devolve 777. O espao de
trabalho de F removido da pilha e descartado. O espao de trabalho
de main reativado e a execuo retomada no ponto 4. A primeira
instruo executada y = 777;.
No nosso exemplo, F e G so funes distintas. Mas tudo funcionaria da
mesma maneira se F e G fossem idnticas, ou seja, se F fosse uma funo recursiva.

Exerccio
6.5.1 Escreva uma funo iterativa que simule o comportamento da funo recursiva
abaixo. Use uma pilha.
int TTT (int x[], int n) {
if (n == 0) return 0;
if (x[n] > 0) return x[n] + TTT (x, n - 1);
else return TTT (x, n - 1); }
Captulo 7

Busca em vetor ordenado

Binary search is to algorithms what a wheel is to mechanics:


It is simple, elegant, and immensely important.
U. Manber, Introduction to Algorithms

Um vetor de inteiros v[0 . . n1] crescente se v[0] v[1] v[n1]


e decrescente se v[0] v[1] v[n1]. O vetor ordenado se for
crescente ou decrescente.
Este captulo estuda o problema de encontrar um dado inteiro em um dado
vetor ordenado. Mais precisamente, dado um inteiro x e um vetor crescente
v[0 . . n1], queremos encontrar um ndice m tal que v[m] = x.

7.1 O problema
Comecemos com uma deciso de projeto. Em lugar de perguntar onde x est
no vetor v[0 . . n1], mais til e mais conveniente perguntar onde x deveria
estar. Nosso problema pode ser formulado assim: dado um inteiro x e um vetor
crescente v[0 . . n1], encontrar um ndice j tal que
v[j1] < x v[j] . (7.1)
De posse de um tal j, muito fcil resolver o problema enunciado na introduo
do captulo: basta comparar x com v[j].
Qualquer valor de j no intervalo fechado 0 . . n pode ser soluo do problema.
Nos dois extremos do intervalo, 0 e n, a condio (7.1) deve ser interpretada
com inteligncia: se j = 0 ento a condio se reduz a x v[0], pois v[1]
no faz sentido; se j = n, a condio se reduz a v[n1] < x, pois v[n] no faz
sentido. Tudo se passa como se nosso vetor tivesse um componente imaginrio
v[1] com valor e um componente imaginrio v[n] com valor +.
50 ALGORITMOS em linguagem C ELSEVIER

Precisamos tomar mais uma deciso de projeto. Qual o menor valor de n que
devemos aceitar? Embora o problema faa sentido quando n vale 0 (a soluo
do problema 0 nesse caso), suporemos sempre que

n1,

pois isso simplica um pouco o raciocnio.

0 n1
111 222 333 444 555 555 666 777 888 888 888 999 999

Figura 7.1: Um vetor crescente v[0 . . n1], com n = 13. Queremos encontrar j tal
que v[j1] < x v[j]. Se x vale 555 ento o valor correto de j 4. Se x vale 1000, o
valor correto de j 13. Se x vale 110 ou 111, o valor correto de j 0.

7.2 Busca sequencial


Comecemos com um algoritmo bvio e simples (mas lento) conhecido como
busca sequencial:

int BuscaSequencial (int x, int n, int v[]) {


int j = 0;
while (j < n && v[j] < x) ++j;
return j;
}

O consumo de tempo do processo iterativo comandado pelo while pro-


porcional ao nmero de iteraes, e este nmero no passa de n. O consumo de
tempo das demais linhas do cdigo pode ser ignorado pois no depende de n.
Podemos dizer, portanto, que o consumo de tempo da funo

proporcional a n

no pior caso. Em outras palavras, a funo no consome mais que n unidades


de tempo.1 Suponha, por exemplo, que a funo consome 1 milissegundo, no
1
A unidade de tempo depende do computador e dos detalhes da implementao da funo,
mas no do valor de n.
ELSEVIER Captulo 7. Busca em vetor ordenado 51

pior caso, para um determinado valor de n. Se tivermos 1000 n no lugar de n,


a funo consumir 1000 milissegundos no pior caso.
O algoritmo de busca sequencial ineciente porque, no pior caso, compara
x com cada um dos elementos do vetor. A prxima seo mostra que possvel
fazer algo muito melhor.

Exerccios
7.2.1 Critique a seguinte formulao do problema de busca: dado x e um vetor crescente
v[0 . . n1], encontrar um ndice j tal que v[j1] x v[j]. Critique a formulao
baseada em v[j1] < x < v[j].
7.2.2 Invariante. Na funo BuscaSequencial, qual o invariante do processo itera-
tivo controlado pelo while?
7.2.3 Critique a seguinte verso da funo BuscaSequencial:
int j = 0;
while (v[j] < x && j < n) ++j;
return j;
7.2.4 Verso recursiva. Escreva uma verso recursiva da funo BuscaSequencial.

7.3 Busca binria


A busca binria muito mais eciente que a busca sequencial. Ela se baseia no
mtodo que usamos s vezes para encontrar uma palavra num dicionrio.

/* Esta funo recebe um vetor crescente v[0..n-1] com


* n >= 1 e um inteiro x. Ela devolve um ndice j
* em 0..n tal que v[j-1] < x <= v[j]. */
int BuscaBinria (int x, int n, int v[]) {
int e, m, d;
e = -1; d = n;
while (e < d - 1) {
m = (e + d)/2;
if (v[m] < x) e = m;
else d = m;
}
return d;
}
52 ALGORITMOS em linguagem C ELSEVIER

(Os nomes das variveis no foram escolhidos ao acaso: e lembra esquerda,


m lembra meio e d lembra direita.) O resultado da diviso por 2 na expresso
(e + d)/2 automaticamente truncado pois s envolve variveis e constantes do
2 .
tipo int. Portanto, o valor da expresso  e+d

Exerccios
7.3.1 Discuta e critique a elegncia da seguinte variante da funo BuscaBinria:
int e, m, d;
if (v[n-1] < x) return n;
if (x <= v[0]) return 0;
e = 0; d = n-1;
while (e < d-1) {
m = (e + d)/2;
if (v[m] < x) e = m;
else d = m; }
return d;
7.3.2 Suponha que v[i] = i para todo i. Execute a funo BuscaBinria com n = 9 e
x = 3. Repita o exerccio com n = 14 e x = 7. Repita o exerccio com n = 15 e x = 7.
7.3.3 Execute a funo BuscaBinria com n = 16. Quais os possveis valores de m na
primeira iterao? Quais os possveis valores de m na segunda iterao? Na terceira?
Na quarta?
7.3.4 Na funo BuscaBinria, verique que m pertence ao intervalo 0 . . n1 (e por-
tanto v[m] faz sentido) sempre que o fragmento de cdigo if (v[m] < x) execu-
tado.
7.3.5 Conra a validade da seguinte armao: quando n+1 uma potncia de 2, o va-
lor da expresso (e + d) divisvel por 2 em todas as iteraes da funo BuscaBinria
(quaisquer que sejam v e x).
7.3.6 Responda as seguintes perguntas sobre a funo BuscaBinria. Que acon-
tece se while (e < d-1) for substitudo por while (e < d) ? Que acontece se if
(v[m] < x) for substitudo por if (v[m] <= x) ? Que acontece se e = m for subs-
titudo por e = m+1 ou por e = m-1 ? Que acontece se d = m for substitudo por
d = m+1 ou por d = m-1 ?

7.4 Prova da correo do algoritmo


Para compreender a funo BuscaBinria, basta vericar o seguinte invariante:
no incio de cada repetio do while, imediatamente antes da comparao de e
com d-1, vale a relao
v[e] < x v[d] (7.2)
ELSEVIER Captulo 7. Busca em vetor ordenado 53

(veja Exerccio 7.4.1 abaixo). O algoritmo foi, na verdade, construdo a partir


desta relao.

0 e d n1
111 222 333 444 555 555 666 777 888 888 888 999 999

Figura 7.2: Incio de uma iterao da funo BuscaBinria.

No incio da primeira iterao, a relao (7.2) est automaticamente satis-


feita, pois v[1] e v[n] no fazem sentido. (Se preferir, voc pode imaginar que
v[1] = e v[n] = +. Mas o cdigo da funo no comete o erro de usar
v[1] ou v[n].)
No incio da ltima iterao temos e = d 1 (veja Exerccio 7.4.2 abaixo) e
portanto o invariante (7.2) se reduz a v[d 1] < x v[d], donde d a soluo
de nosso problema. Assim, ao devolver d, o algoritmo est cumprindo o que
prometeu fazer.
Resta vericar que a execuo do algoritmo termina. No incio de cada
iterao, o nmero de elementos do vetor em jogo d e 1. Como e < m < d
(veja Exerccio 7.4.3), tanto dm1 quanto me1 so estritamente menores
que d e 1. Portanto, o tamanho do vetor em jogo diminui a cada iterao e
a execuo do algoritmo para, mais cedo ou mais tarde.

Exerccios
7.4.1 Suponha que estamos no incio de uma iterao (que no a ltima) da funo
BuscaBinria. Suponha que vale a relao (7.2). Mostre que (7.2) vale no incio da
prxima iterao.
7.4.2 Mostre que no incio da ltima iterao da funo BuscaBinria temos e = d 1.
7.4.3 Na funo BuscaBinria, mostre que temos e < m < d imediatamente depois
da atribuio m = (e+d)/2.

7.5 Desempenho do algoritmo


Quantas iteraes a funo BuscaBinria executa? Em cada iterao, o tama-
nho do vetor em jogo d e 1. No incio da primeira iterao, o tamanho
do vetor n. No incio da segunda, o tamanho aproximadamente n/2. No
54 ALGORITMOS em linguagem C ELSEVIER

incio da terceira, aproximadamente n/4. No incio da (k+1)-sima, aproxima-


damente n/2k . Quando k > log2 n, temos n/2k < 1 e a execuo do algoritmo
para. Assim, o nmero de iteraes (veja Exerccio 1.2.4) aproximadamente
log2 n .
O consumo de tempo da funo proporcional ao nmero de iteraes e
portanto proporcional a log2 n. Esse consumo cresce com n muito mais deva-
gar que o consumo da busca sequencial, pois log transforma multiplicaes em
somas. Por exemplo, se cada iterao consome 1 milissegundo, uma busca em
n elementos consome log2 n milissegundos, uma busca em 2n elementos con-
some apenas 1 + log2 n milissegundos, uma busca em 4n elementos consome
s 2 + log2 n milissegundos e uma busca em 1024 n elementos consumir to
somente 10 + log2 n milissegundos.

Exerccios
7.5.1 Faa uma tabela de valores log2 n para n = 10, 102, 103 , 104 , 105 . (Veja o Exer-
ccio 1.2.4.)
7.5.2 Se t segundos so necessrios para fazer uma busca binria em um vetor com n
elementos, quantos segundos sero necessrios para fazer uma busca em n2 elementos?
7.5.3 Overflow aritmtico. Se o nmero de elementos do vetor v[0 . . n1] estiver
prximo de INT_MAX (veja Seo K.5), o cdigo da funo BuscaBinria pode descar-
rilar ao calcular o valor da expresso (e + d)/2, em virtude de um overow aritmtico.
Como evitar isso?

7.6 Exerccios: variantes do cdigo


H muitas maneiras de escrever o cdigo da busca binria. Todas exigem cui-
dado e ateno aos detalhes, pois muito fcil escrever uma verso que d
respostas erradas ou entra em loop. Os exerccios abaixo introduzem algu-
mas verses diferentes da discutida na Seo 7.3. Todas prometem devolver um
ndice j no intervalo 0 . . n tal que v[j 1] < x v[j].

7.6.1 Mostre que a seguinte variante da funo BuscaBinria funciona corretamente.


e = 0; d = n;
while (e < d) { /* v[e-1] < x <= v[d] */
m = (e + d)/2;
if (v[m] < x) e = m + 1;
else d = m;
} /* e == d */
return d;
ELSEVIER Captulo 7. Busca em vetor ordenado 55

(Esta verso quase to elegante quanto a verso discutida na Seo 7.3.) Que acon-
tece se trocarmos while (e < d) por while (e <= d) ? Que acontece se trocarmos
(e+d)/2 por (e-1+d)/2 ?
7.6.2 Mostre que a seguinte verso da funo BuscaBinria funciona corretamente.
Ela um pouco menos elegante que as verses anteriores.
e = 0; d = n-1;
while (e <= d) { /* v[e-1] < x <= v[d+1] */
m = (e + d)/2;
if (v[m] < x) e = m + 1;
else d = m-1;
} /* e == d + 1 */
return d+1;
7.6.3 A seguinte alternativa para a funo BuscaBinria funciona corretamente? Que
acontece se trocarmos (e+d)/2 por (e+d+1)/2 ?
e = -1; d = n-1;
while (e < d) {
m = (e + d)/2;
if (v[m] < x) e = m;
else d = m - 1; }
return d + 1;

7.7 Verso recursiva da busca binria


A formulao do problema que usamos at aqui no se presta, diretamente,
a uma soluo recursiva. Ser necessrio reformular o problema ligeiramente.
Para fazer a transio da formulao anterior para a nova usaremos a seguinte
funo-embalagem, que tem a mesma documentao que BuscaBinria:

int BuscaBinria2 (int x, int n, int v[]) {


return BuscaBinR (x, -1, n, v);
}

A funo recursiva BuscaBinR procura x no vetor crescente v[e+1..d-1]


supondo que o valor de x est entre os extremos v[e] e v[d]:

/* O vetor v[e+1..d-1] crescente e o inteiro x tal que


* v[e] < x <= v[d]. A funo devolve um ndice j no
* intervalo e+1..d tal que v[j-1] < x <= v[j]. */
56 ALGORITMOS em linguagem C ELSEVIER

int BuscaBinR (int x, int e, int d, int v[]) {


if (e == d-1) return d;
else {
int m = (e + d)/2;
if (v[m] < x)
return BuscaBinR (x, m, d, v);
else
return BuscaBinR (x, e, m, v);
}
}

Quando a funo BuscaBinR invocada com argumentos (x, 1, n, v), ela


invoca a si mesma cerca de log2 n vezes. Este nmero de invocaes a
profundidade da recurso.

Exerccios
7.7.1 Discute a seguinte variante da funo BuscaBinria2:
if (v[n-1] < x) return n;
if (x <= v[0]) return 0;
return BuscaBinR (x, 0, n - 1, v);
7.7.2 Mostre que as condies descritas na documentao da funo BuscaBinR esto
satisfeitas no momento em que BuscaBinR invocada por BuscaBinria2.
7.7.3 Considere a funo BuscaBinR. Suponha que v[m] < x. Verique que v[m] < x
v[d], mostrando assim que a funo BuscaBinR pode ser invocada com argumentos x,
m, d, v. Agora suponha que v[m] x e verique que v[e] < x v[m], mostrando
assim que BuscaBinR pode ser invocada com argumentos x, e, m, v.
7.7.4 Leia o verbete Binary search algorithm na Wikipedia [21].

7.8 Exerccios: variaes sobre o tema


O algoritmo de busca binria um verdadeiro ovo de Colombo. A ideia bsica
do algoritmo o ponto de partida de muitos algoritmos ecientes. Os exerccios
abaixo procuram explorar o tema.
7.8.1 Outra formulao da busca binria. Escreva uma verso da busca binria
que receba um inteiro x e um vetor v[0 . . n1] e devolva j tal que em v[j1] x < v[j]
(note a posio de e <). Quais os possveis valores de j?
7.8.2 Vetor decrescente. Escreva uma verso da busca binria para resolver o
ELSEVIER Captulo 7. Busca em vetor ordenado 57

seguinte problema: dado um inteiro x e um vetor decrescente v[0 . . n1], encontrar j


tal que v[j1] > x v[j].
7.8.3 Busca simplificada. Escreva uma funo que resolva o problema formulado
na introduo do captulo: ao receber um inteiro x e um vetor crescente v[0 . . n1],
devolva um ndice m tal que v[m] = x ou devolva 1 se tal m no existe. Escreva duas
verses: uma iterativa e uma recursiva.
7.8.4 Vetor de strings. Suponha que cada elemento do vetor v[0 . . n1] uma
string. Suponha tambm que o vetor est em ordem lexicogrca (veja Seo G.3).
Escreva uma funo que receba uma string x e devolva um ndice j tal que x igual
a v[j]. Se tal j no existe, a funo deve devolver 1.
7.8.5 Vetor de structs. Suponha que cada elemento do vetor v[0 . . n1] uma
struct (veja Apndice E) com dois campos: o nome de um aluno e o nmero do aluno.
Suponha que o vetor est em ordem crescente de nmeros. Escreva uma funo de
busca binria que receba o nmero de um aluno e devolva o seu nome. Se o nmero
no estiver no vetor, a funo deve devolver a string vazia.
7.8.6 Procurando por v[i] = i. Escreva uma funo que receba um vetor estrita-
mente crescente2 v[0 . . n1] de nmeros inteiros e devolva um ndice i entre 0 e n1
tal que v[i] = i; se tal i no existe, a funo deve devolver 1. O seu algoritmo no
deve fazer mais que log2 n comparaes envolvendo elementos de v.
7.8.7 Escreva uma funo eciente que receba inteiros positivos k e n e calcule k n .
Quantas multiplicaes sua funo executa? (Compare com o Exerccio 2.3.10.)
7.8.8 A seguinte funo recursiva pretende encontrar o valor de um elemento mximo
do vetor v[e . . d] supondo e d. O vetor no est necessariamente ordenado. A
funo est correta? Ela mais rpida que a correspondente verso iterativa? Qual a
profundidade da recurso?
int max (int e, int d, int v[]) {
if (e == d) return v[d];
else {
int m, maxe, maxd;
m = (e + d)/2;
maxe = max (e, m, v);
maxd = max (m + 1, d, v);
if (maxe >= maxd) return maxe;
else return maxd; } }

2
Um vetor v[0 . . n1] estritamente crescente se v[0] < v[1] < < v[n1].
Captulo 8

Ordenao: algoritmos
elementares

Colocar um vetor numrico em ordem crescente o primeiro passo na soluo


de muitos problemas prticos. Um vetor pode ser ordenado de muitas maneiras
diferentes: algumas elementares, outras mais sosticadas e ecientes. Assim, o
problema da ordenao um verdadeiro laboratrio de projeto de algoritmos.
Trataremos do assunto neste captulo e nos trs captulos seguintes.

8.1 O problema da ordenao


Um vetor v[0 . . n1] crescente se v[0] v[1] v[n1]. O problema
da ordenao de um vetor consiste no seguinte:

Rearranjar (ou seja, permutar) os elementos de um vetor v[0 . . n1] de


tal modo que ele se torne crescente.

Este captulo discute dois algoritmos simples para o problema. Os trs captulos
seguintes examinam algoritmos mais sosticados e ecientes.

Exerccios
8.1.1 Escreva uma funo que verique se um dado vetor v[0 . . n1] crescente.
8.1.2 Leia o verbete Sorting algorithm na Wikipedia [21].
60 ALGORITMOS em linguagem C ELSEVIER

8.2 Algoritmo de insero


O algoritmo de ordenao por insero muito popular; ele frequentemente
usado para colocar em ordem um baralho de cartas.

/* Esta funo rearranja o vetor v[0..n-1] em ordem


* crescente. */
void Insero (int n, int v[]) {
int i, j, x;
for (j = 1; /*A*/ j < n; j++) {
x = v[j];
for (i = j-1; i >= 0 && v[i] > x; i--)
v[i+1] = v[i];
v[i+1] = x;
}
}

Para entender o algoritmo, basta observar que no incio de cada repetio


do for externo, ou seja, a cada passagem pelo ponto A,
1. o vetor v[0 . . n1] uma permutao do vetor original e
2. o vetor v[0 . . j1] crescente.
Estas propriedades invariantes so trivialmente verdadeiras no incio da primeira
iterao, quando j vale 1, e permanecem verdadeiras no incio das iteraes
subsequentes. No incio da ltima iterao, j vale n e portanto o vetor v[0 . . n1]
est na ordem desejada. (Note que a ltima iterao interrompida logo no
incio, pois a condio j < n falsa.)

0 crescente j1 j n1
444 555 555 666 777 222 999 222 999 222 999

Figura 8.1: Vetor v[0 . . n1] no incio de uma iterao da funo Insero.

Desempenho do algoritmo. O consumo de tempo da funo Insero


proporcional ao nmero de execues da comparao v[i] > x. Calculemos
esse nmero. Para cada valor de j, a varivel i assume no mximo j valores,
a saber, j 1, j 2, . . . , 0. Como j varia de 1 a n, o nmero de execues da
ELSEVIER Captulo 8. Ordenao: algoritmos elementares 61


comparao v[i] > x igual a n1j=1 j no pior caso. A soma vale n (n 1)/2
e este nmero essencialmente igual a n2 /2 quando n grande. Pode-se dizer,
portanto, que o consumo de tempo da funo
proporcional a n2
no pior caso. Em outras palavras, a funo consome no mximo n2 unidades
de tempo.1 Se a ordenao de n nmeros consumir t milissegundos, a ordena-
o de 2n nmeros consumir 4t milissegundos e a ordenao de 10n nmeros
consumir 100t milissegundos. Portanto, o algoritmo lento quando n grande.

Exerccios
8.2.1 No cdigo da funo Insero, troque v[i] > x por v[i] >= x. A nova
funo continua produzindo uma ordenao crescente de v[0..n-1]?
8.2.2 No cdigo da funo Insero, que acontece se trocarmos for (j = 1 por
for (j = 0 ? Que acontece se trocarmos v[i+1] = x por v[i] = x ?
8.2.3 Critique a seguinte implementao do algoritmo de ordenao por insero:
int i, j, x;
for (j = 1; j < n; j++) {
for (i = j-1; i >= 0 && v[i] > v[i+1]; i--) {
x = v[i]; v[i] = v[i+1]; v[i+1] = x; } }
8.2.4 Critique a seguinte implementao do algoritmo de ordenao por insero:
int h, i, j, x;
for (j = 1; j < n; j++) {
x = v[j];
for (h = 0; h < j && v[h] <= x; h++) ;
for (i = j-1; i >= h; i--) v[i+1] = v[i];
v[h] = x; }
8.2.5 Escreva uma verso do algoritmo de insero que tenha o seguinte invariante: no
incio de cada iterao, o vetor v[j+1 . . n1] crescente.
8.2.6 Busca binria. O for interno na funo Insero tem a misso de encontrar o
ponto onde v[j] deve ser inserido em v[0..j-1], ou seja, encontrar o ndice i tal que
v[i] v[j] < v[i+1]. Considere fazer isso com uma busca binria (veja Seo 7.3).
Analise o resultado.
8.2.7 Ordem estritamente crescente. Escreva uma funo que rearranje um vetor
v[0 . . n1] de modo que ele que em ordem estritamente crescente.
8.2.8 Ordem decrescente. Escreva uma funo que permute os elementos de um
vetor v[0 . . n1] de modo que eles quem em ordem decrescente.
1
A unidade de tempo depende do computador e dos detalhes da implementao da funo,
mas no do valor de n.
62 ALGORITMOS em linguagem C ELSEVIER

8.2.9 Verso recursiva. Escreva uma verso recursiva do algoritmo de ordenao


por insero.
8.2.10 Animaes. Veja animaes do algoritmo de insero nas pginas de Harri-
son [8] e Morin [13] da teia www.
8.2.11 Leia o verbete Insertion sort na Wikipedia [21].

8.3 Algoritmo de seleo


O algoritmo de ordenao por seleo baseado na ideia de escolher o menor
elemento do vetor, depois o segundo menor,2 e assim por diante.

/* Rearranja o vetor v[0..n-1] em ordem crescente. */


void Seleo (int n, int v[]) {
int i, j, min, x;
for (i = 0; /*A*/ i < n-1; i++) {
min = i;
for (j = i+1; j < n; j++)
if (v[j] < v[min]) min = j;
x = v[i]; v[i] = v[min]; v[min] = x;
}
}

Para entender como e por que o algoritmo funciona, basta observar que no
incio de cada repetio do for externo, ou seja, a cada passagem pelo ponto A,
valem os seguintes invariantes:
1. v[0 . . n1] uma permutao do vetor original,
2. v[0 . . i1] est em ordem crescente e
3. v[i 1] v[j] para j = i, i+1, . . . , n1.
A traduo do invariante 3 para linguagem humana a seguinte: v[0 . . i1]
contm todos os elementos pequenos do vetor original e v[i . . n1] contm
todos os elementos grandes. Os trs invariantes garantem que no incio de
cada iterao os elementos v[0], . . . , v[i 1] j esto em suas posies denitivas.

Desempenho do algoritmo. Uma anlise semelhante que zemos para


o algoritmo de insero mostra que o algoritmo de seleo faz cerca de n2 /2
2
A rigor, deveramos dizer selecionar um menor elemento, depois um segundo menor.
ELSEVIER Captulo 8. Ordenao: algoritmos elementares 63

0 i1 i n1
110 120 120 130 140 666 999 666 999 666 999
pequenos, crescente grandes

Figura 8.2: Vetor v[0 . . n1] no incio de uma iterao da funo Seleo.

comparaes entre elementos do vetor. Portanto, consome n2 unidades de tempo


no pior caso.

Exerccios
8.3.1 Que acontece se trocarmos for (i = 0 por for (i = 1 no cdigo da funo
Seleo? Que acontece se trocarmos for (i = 0; i < n-1 por for (i = 0; i < n ?
8.3.2 Troque v[j] < v[min] por v[j] <= v[min] no cdigo de Seleo. A nova
funo continua produzindo uma ordenao crescente de v[0..n-1]?
8.3.3 Ordem decrescente. Escreva uma funo que permute os elementos de um
vetor v[0 . . n1] de modo que eles quem em ordem decrescente.
8.3.4 Verso recursiva. Escreva uma verso recursiva do algoritmo de ordenao
por seleo.
8.3.5 Animaes. Veja animaes do algoritmo de seleo nas pginas de Harrison [8]
e Morin [13].
8.3.6 Leia o verbete Selection sort na Wikipedia [21].

8.4 Exerccios: ordenao de strings e listas


8.4.1 Ordenao de strings. Escreva uma funo que coloque um vetor de strings
em ordem lexicogrca (veja Seo G.3). Faa duas verses: uma baseada no algoritmo
de insero e outra baseada no algoritmo de seleo.
8.4.2 Ordenao de arquivo. Escreva uma funo que rearranje as linhas de um
arquivo (veja Apndice H) em ordem lexicogrca (veja Seo G.3). Compare com o
utilitrio sort presente em todo sistema UNIX e GNU/Linux.
8.4.3 Ordenao de structs. Suponha que cada elemento de um vetor um registro
que consiste em um inteiro e uma string:
struct elem {int i; char *s;};
Escreva uma funo que rearranje o vetor de modo que os campos i quem em ordem
crescente. Escreva outra funo que rearranje o vetor de modo que os campos s quem
em ordem lexicogrca (veja Seo G.3).
64 ALGORITMOS em linguagem C ELSEVIER

8.4.4 Ordenao de lista encadeada. Escreva uma funo que ordene uma lista
encadeada. Inspire-se no algoritmo de ordenao por insero. Faa duas verses:
uma para lista com cabea e outra para lista sem cabea. (Sua funo precisa devolver
alguma coisa?). Repita o exerccio com base no algoritmo de ordenao por seleo.
8.4.5 Projeto de programao. Digamos que duas palavras so equivalentes se
uma anagrama da outra, ou seja, se a sequncia de letras de uma permutao da
sequncia de letras da outra. Por exemplo, aberto e rebato so equivalentes.
Uma classe de equivalncia de palavras um conjunto de palavras duas a duas equi-
valentes. Escreva um programa que receba um arquivo de palavras (uma palavra por
linha) e extraia desse arquivo uma classe de equivalncia de tamanho mximo. Apli-
que o seu programa ao arquivo de palavras www.ime.usp.br/~pf/algoritmos/dicios/,
que contm todas as palavras do portugus falado no Brasil (o arquivo foi extrado do
Dicionrio br.ispell [20]).

8.5 Ordenao estvel


Um algoritmo de ordenao estvel se no altera a posio relativa de ele-
mentos que tm um mesmo valor. Por exemplo, se o vetor tiver dois elementos
de valor 222, um algoritmo de ordenao estvel manter o primeiro 222 antes
do segundo.

vetor original: 444 555 666 777 333 2221 111 2222 888
vetor ordenado: 111 2221 2222 333 444 555 666 777 888

Figura 8.3: Ordenao estvel. O vetor original tem dois elementos com valor
222 (ndices 1 e 2 so usados para distinguir o primeiro do segundo). No vetor
ordenado, o primeiro destes elementos continua frente do segundo.

Suponha, por exemplo, que os elementos de um vetor so pares da forma


(d, m) que representam datas de um certo ano: a primeira componente repre-
senta o dia e a segunda representa o ms. (Compare com o Exerccio 8.4.3.)
Suponha que o vetor est em ordem crescente das componentes d:

(1, 12), (7, 12), (16, 3), (25, 9), (30, 3), (30, 6), (31, 3).

Agora ordene o vetor pelas componentes m. Se usarmos um algoritmo de orde-


nao estvel, o resultado estar em ordem cronolgica:

(16, 3), (30, 3), (31, 3), (30, 6), (25, 9), (1, 12), (7, 12).
ELSEVIER Captulo 8. Ordenao: algoritmos elementares 65

Se o algoritmo de ordenao no for estvel, o resultado pode no car em ordem


cronolgica:

(30, 3), (16, 3), (31, 3), (30, 6), (25, 9), (7, 12), (1, 12).

Exerccios
8.5.1 O algoritmo de ordenao por insero (Seo 8.2) estvel?
8.5.2 Na cdigo da funo Insero (Seo 8.2), troque a comparao v[i] > x por
v[i] >= x. A nova funo faz uma ordenao estvel de v[0..n-1]?
8.5.3 O algoritmo de ordenao por seleo (Seo 8.3) estvel?
Captulo 9

Ordenao: algoritmo Mergesort

Considere o problema da ordenao enunciado na introduo na Seo 8.1: per-


mutar os elementos de um vetor v[0 . . n1] de modo que ele se torne crescente.
O Captulo 8 examinou dois algoritmos simples para o problema. Este cap-
tulo examina um algoritmo mais sosticado e mais rpido baseado na estratgia
dividir para conquistar.

9.1 Intercalao de vetores ordenados


Antes de tratar do problema da ordenao propriamente dito, preciso resolver
um problema auxiliar: dados vetores crescentes v[p . . q1] e v[q . . r1], rear-
ranjar v[p . . r1] em ordem crescente. Podemos dizer que o problema consiste
em intercalar os dois vetores dados.
fcil resolver o problema em tempo proporcional ao quadrado de r p:
basta aplicar um dos algoritmos do Captulo 8 ao vetor v[p . . r1] ignorando
o fato de que as duas metades esto ordenadas. Mas possvel resolver o
problema de maneira bem mais eciente. Para isso, ser preciso usar um vetor
auxiliar, digamos w, do mesmo tipo e mesmo tamanho que v[p . . r1].

p q1 q r1
111 333 555 555 777 999 999 222 444 777 888

Figura 9.1: Rearranjar o vetor v[p . . r1] em ordem crescente sabendo


que v[p . . q1] e v[q . . r1] j esto em ordem crescente.
68 ALGORITMOS em linguagem C ELSEVIER

/* A funo recebe vetores crescentes v[p..q-1] e v[q..r-1]


* e rearranja v[p..r-1] em ordem crescente. */
void Intercala (int p, int q, int r, int v[]) {
int i, j, k, *w;
w = malloc ((r-p) * sizeof (int));
i = p; j = q; k = 0;
while (i < q && j < r) {
if (v[i] <= v[j]) w[k++] = v[i++];
else w[k++] = v[j++];
}
while (i < q) w[k++] = v[i++];
while (j < r) w[k++] = v[j++];
for (i = p; i < r; i++) v[i] = w[i-p];
free (w); 1
}

Desempenho da intercalao. A funo Intercala consome tempo


proporcional ao nmero de comparaes entre elementos do vetor. Esse n-
mero menor que r p. Podemos dizer, ento, que o consumo de tempo da
funo no pior caso

proporcional ao nmero de elementos do vetor.

O algoritmo de intercalao , portanto, muito eciente.

Exerccios
9.1.1 A funo Intercala est correta nos casos extremos p = q e q = r?
9.1.2 Que acontece se trocarmos else w[k++] = v[j++] por if (v[i] > v[j])
w[k++] = v[j++] no cdigo da funo Intercala?
9.1.3 Na funo Intercala, troque o par de linhas while (j < r) . . . ; for . . . =
w[i-p]; por for (i = p; i < j; i++) v[i] = w[i-p];. Discuta o resultado.
9.1.4 Discuta a seguinte alternativa para a funo Intercala:
i = p; j = q;
for (k = 0; k < r-p; k++) {
if (j >= r || (i < q && v[i] <= v[j])) w[k] = v[i++];
else w[k] = v[j++]; }
for (i = p; i < r; i++) v[i] = w[i-p];
1
Veja Seo F.3.
ELSEVIER Captulo 9. Ordenao: algoritmo Mergesort 69

9.1.5 Critique a seguinte alternativa para a funo Intercala:


i = p; j = q; k = 0;
while (k < r-p) {
while (i < q && v[i] <= v[j])
w[k++] = v[i++];
while (j < r && v[j] <= v[i])
w[k++] = v[j++]; }
for (i = p; i < r; i++) v[i] = w[i-p];
9.1.6 A seguinte alternativa para a funo Intercala no usa vetor auxiliar. Ela est
correta? Quais os invariantes do while? Qual o consumo de tempo?
int i = p, k, t;
while (i < q && q < r) {
if (v[i] >= v[q]) {
t = v[q];
for (k = q-1; k >= i; k--) v[k+1] = v[k];
v[i] = t;
q++; }
i++; }
9.1.7 Verso recursiva. Escreva uma verso recursiva do algoritmo da intercalao.
Antes, convm reformular o problema da seguinte maneira: dados vetores crescentes
u[0 . . m1] e v[0 . . n1], produzir um vetor crescente w[0 . . m+n1] que contenha o
resultado da intercalao dos dois vetores.
9.1.8 Estabilidade. Um algoritmo de intercalao estvel (veja Seo 8.5) se no
altera a posio relativa de elementos de mesmo valor. A funo Intercala estvel?
Se v[i] <= v[j] for trocada por v[i] < v[j], a funo ca estvel?
9.1.9 Intercalao com sentinelas. Sedgewick [18] escreve a funo Intercala
de maneira muito interessante. Eis um esboo do cdigo:
int w[MAX], i, j, k;
for (i = p; i < q; i++) w[i] = v[i];
for (j = q; j < r; j++) w[r+q-j-1] = v[j];
i = p; j = r-1;
for (k = p; k < r; k++)
if (w[i] < w[j]) v[k] = w[i++];
else v[k] = w[j--];
Este esboo est sujeito restrio r MAX e desperdia espao se p for maior que 0.
Reescreva o cdigo depois de corrigir estes defeitos.
9.1.10 Listas encadeadas. Chame de lecr qualquer lista encadeada sem cabea que
contm uma sequncia crescente de nmeros inteiros. Escreva uma funo que intercale
duas lecr dadas, produzindo assim uma terceira lecr. Sua funo no deve alocar
novas clulas na memria, mas reaproveitar as clulas das duas listas dadas.
70 ALGORITMOS em linguagem C ELSEVIER

9.2 O algoritmo Mergesort


Agora podemos usar a funo Intercala para escrever um algoritmo rpido de
ordenao. Nosso cdigo recursivo. A base da recurso o caso p r 1;
nesse caso no preciso fazer nada.

/* Esta funo rearranja o vetor v[p..r-1] em ordem


* crescente. */
void Mergesort (int p, int r, int v[]) {
if (p < r - 1) {
int q = (p + r)/2;
Mergesort (p, q, v);
Mergesort (q, r, v);
Intercala (p, q, r, v);
}
}

(O resultado da diviso por 2 na expresso (p+r)/2 automaticamente


truncado pois s envolve variveis e constantes do tipo int. Portanto, o valor
da expresso  p+r
2 .) Para rearranjar v[0..n-1] em ordem crescente basta
dizer Mergesort (0, n, v).

0 1 2 3 4 5 6 7 8 9 10
999 111 222 999 888 333 444 777 555 666 555

999 111 222 999 888 333 444 777 555 666 555

999 111 222 999 888 333 444 777 555 666 555
..
.
111 999 222 888 999 333 444 777 555 555 666

111 222 888 999 999 333 444 555 555 666 777

111 222 333 444 555 555 666 777 888 999 999

Figura 9.2: Algoritmo Mergesort aplicado ao vetor v[0 . . 10]. Nas primeiras rodadas,
o algoritmo no faz mais que quebrar o vetor em segmentos (veja as gradaes de cinza).
Nas rodadas subsequentes, segmentos vizinhos so intercalados.
ELSEVIER Captulo 9. Ordenao: algoritmo Mergesort 71

Exerccios
9.2.1 Que acontece se trocarmos (p+r)/2 por (p+r-1)/2 no cdigo de Mergesort?
Que acontece se trocarmos (p+r)/2 por (p+r+1)/2 ?
9.2.2 Submeta um vetor v[1..4] funo Mergesort. Teremos a seguinte sequncia
de invocaes da funo:
Mergesort (1, 5, v)
Mergesort (1, 3, v)
Mergesort (1, 2, v)
Mergesort (2, 3, v)
Mergesort (3, 5, v)
Mergesort (3, 4, v)
Mergesort (4, 5, v)
Faa uma gura anloga para um vetor v[1..5].
9.2.3 A funo Mergesort estvel? (Veja Seo 8.5 e Exerccio 9.1.8.)
9.2.4 Discuta a seguinte implementao da funo Mergesort:
if (p < r) {
int q = (p + r)/2;
Mergesort (p, q, v);
Mergesort (q, r, v);
Intercala (p, q, r, v); }
9.2.5 Discuta a seguinte implementao da funo Mergesort:
if (p < r-1) {
int q = (p + r - 1)/2;
Mergesort (p, q, v);
Mergesort (q, r, v);
Intercala (p, q, r, v); }
9.2.6 Critique a implementao da funo Mergesort abaixo. Repita o exerccio com
(p+r+1)/2 no lugar de (p+r)/2.
if (p < r-1) {
int q = (p + r)/2;
Mergesort (p, q-1, v);
Mergesort (q-1, r, v);
Intercala (p, q-1, r, v); }
9.2.7 Critique a seguinte implementao da funo Mergesort:
if (p < r-1) {
q = r - 1;
Mergesort (p, q, v);
Intercala (p, q, r, v); }
9.2.8 Suponha que sua biblioteca tem uma funo Mrg com parmetros v, p, q, r que
72 ALGORITMOS em linguagem C ELSEVIER

funciona assim: ao receber um vetor v tal que v[p . . q] e v[q+1 . . r] so crescentes,


rearranja o vetor v[p . . r] em ordem crescente. Use Mrg para implementar o algoritmo
Mergesort.

9.3 Desempenho do algoritmo


Quanto tempo a funo Mergesort consome para ordenar um vetor v[0 . . n1]?
O nmero de elementos do vetor reduzido aproximadamente metade em cada
invocao da funo. Assim, o nmero total de rodadas aproximadamente
log2 n. Na primeira rodada, nosso problema original reduzido a dois outros:
ordenar
v[0 . . n2 1] e v[ n2 . . n1]
(para simplicar, estou supondo que n uma potncia de 2). Na segunda rodada
temos quatro problemas: ordenar

v[0 . . n4 1] , v[ n4 . . n2 1] , v[ n2 . . 3n 3n
4 1] e v[ 4 . . n1] .

E assim por diante. O tempo total que Intercala gasta em cada rodada
proporcional a n (veja Exerccio 9.3.1). Concluso: Mergesort consome tempo
proporcional a
n log2 n .
Isto bem melhor que o tempo proporcional a n2 gasto pelos algoritmos ele-
mentares do Captulo 8. (Na prtica, Mergesort s realmente mais rpido
que os algoritmos do Captulo 8 quando n sucientemente grande, uma vez
que a constante de proporcionalidade na expresso proporcional a maior no
caso do Mergesort.)
Suponha que Mergesort consome t milissegundos para ordenar n nmeros.
Ento a mesma funo consumir menos que 32t milissegundos para ordenar 16n
nmeros e menos que 352t milissegundos para ordenar 128n nmeros. (Estamos
supondo n 16 em todas as estimativas.) Compare isso com o desempenho
dos algoritmos elementares do Captulo 8: se um daqueles algoritmos consumir
t milissegundos para ordenar n nmeros, consumir 256t milissegundos para
ordenar 16n nmeros e 16384t milissegundos para ordenar 128n nmeros.

Exerccios
9.3.1 Mostre que o consumo total de tempo de Intercala proporcional a n em cada
rodada de Mergesort.
ELSEVIER Captulo 9. Ordenao: algoritmo Mergesort 73

9.3.2 Invocaes repetidas de malloc. Durante uma execuo de Mergesort


(Seo 9.2), a funo Intercala invocada muitas vezes e cada execuo de Intercala
invoca as funes malloc e free. Para evitar as repetidas execues de malloc e
free, escreva uma verso da funo Mergesort que incorpore o cdigo da funo de
intercalao e invoque malloc e free uma s vez.
9.3.3 Overflow aritmtico. Se o nmero de elementos do vetor estiver prximo
de INT_MAX (veja Seo K.5), a execuo da funo Mergesort pode descarrilar, em
virtude de um overow aritmtico, ao calcular o valor da expresso (p+r)/2. Como
evitar isso? (Veja Exerccio 7.5.3.)
9.3.4 Projeto de programao. Escreva um programa para comparar experimen-
talmente o desempenho da funo Mergesort com o das funes Insero e Seleo
do Captulo 8. (Para a fase de testes, escreva uma pequena funo que verique se sua
implementao do Mergesort est produzindo uma ordenao correta do vetor.) Use
um vetor aleatrio (veja Apndice I) para fazer os testes.
9.3.5 Ordem decrescente. Escreva uma verso do algoritmo Mergesort que rear-
ranje um vetor v[p . . r1] em ordem decrescente. (Ser preciso reescrever o algoritmo
da intercalao.)
9.3.6 Animaes. Veja animaes do algoritmo Mergesort nas pginas de Harrison [8]
e Morin [13].
9.3.7 Leia o verbete Merge sort na Wikipedia [21].

9.4 Verso iterativa


Na verso iterativa do algoritmo Mergesort, cada iterao intercala dois blocos
de b elementos: o primeiro bloco com o segundo, o terceiro com o quarto etc.
A varivel b assume os valores 1, 2, 4, 8, . . .

0 p p+b p+2b n1
111 999 222 999 333 888 444 777 555 666 555

Figura 9.3: Incio da uma iterao da funo MergesortI com b = 2.

/* Rearranja o vetor v[0..n-1] em ordem crescente. */


void MergesortI (int n, int v[]) {
int p, r;
int b = 1;
74 ALGORITMOS em linguagem C ELSEVIER

while (b < n) {
p = 0;
while (p + b < n) {
r = p + 2*b;
if (r > n) r = n;
Intercala (p, p + b, r, v);
p = p + 2*b;
}
b = 2*b;
}
}

Exerccios
9.4.1 Segmentos crescentes maximais. A verso iterativa do Mergesort comea
por quebrar o vetor original em segmentos de comprimento 1. Quem sabe melhor
quebrar o vetor em seus segmentos crescentes maximais (os segmentos crescentes ma-
ximais de 1 2 3 0 2 4 6 4 5 6 7 8 9, por exemplo, so 1 2 3 , 0 2 4 6 e 4 5 6 7 8 9 ).
Escreva uma verso do Mergesort baseada nesta ideia.
9.4.2 Ordenao de strings. Escreva uma verso do algoritmo Mergesort que co-
loque um vetor de strings em ordem lexicogrca (veja Seo G.3).
9.4.3 Listas encadeadas. Escreva uma verso do algoritmo Mergesort que rearranje
uma lista encadeada de modo que ela que em ordem crescente. Sua funo no deve
alocar novas clulas na memria. (Veja Exerccios 9.1.10 e 4.7.7.) Faa duas verses:
uma recursiva e uma iterativa.
Captulo 10

Ordenao: algoritmo Heapsort

Heap: monto, amontoado, pilha.


Dicionrio Michaelis

O algoritmo Heapsort [22] resolve o problema da ordenao introduzido na Se-


o 8.1, ou seja, rearranja um vetor em ordem crescente. Para simplicar ligei-
ramente a descrio do algoritmo, suporemos neste captulo que os ndices do
vetor so 1 . . n e no 0 . . n1.
O algoritmo Heapsort bem mais rpido que os algoritmos elementares do
Captulo 8 e, ao contrrio do Mergesort do Captulo 9, no requer um vetor
auxiliar.

10.1 Heap
O segredo do algoritmo Heapsort uma estrutura de dados conhecida como
heap.1 H dois sabores dessa estrutura: o max-heap e o min-heap. Apenas
o primeiro ser usado neste captulo. Um max-heap um vetor v[1 . . m] tal
que2
v[ 12 f ] v[f ]
para f = 2, 3, . . . , m. (Num min-heap temos no lugar de .) Segue
imediatamente da denio que v[1] um elemento mximo do max-heap.
A estrutura de um heap ca mais clara se o conjunto de ndices 1 . . m for
entendido como um rvore binria (veja Captulo 14):
1
A palavra heap tambm designa a parte da memria do computador usada para alo-
cao dinmica, mas este signicado no tem nenhuma relao com o conceito que estamos
introduzindo aqui.
2
Se f par ento f /2 = f /2, seno f /2 = (f 1)/2.
76 ALGORITMOS em linguagem C ELSEVIER

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
999 888 666 333 777 555 555 333 222 111 444 111 222 444 111

Figura 10.1: Max-heap v[1 . . 15]. Observe que v[5] v[10] e v[5] v[11].

a. o ndice 1 a raiz da rvore;


b. o pai de um ndice f  12 f ;
c. o lho esquerdo de um ndice p 2p e o lho direito 2p + 1.
( claro que 1 no tem pai, que o lho esquerdo de p s existe se 2p m e que
o lho direito de p s existe se 2p + 1 m.) Nesta rvore de ndices, o valor do
ndice p v[p]. Podemos dizer ento que um vetor um max-heap se todo pai
pelo menos to valioso quanto qualquer dos seus dois lhos.

1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

Figura 10.2: A gura representa um vetor v[1 . . 56] em camadas. O vetor denido
por v[i] = i e portanto est longe de ser um max-heap. Cada lho est na camada
imediatamente inferior do pai. Cada camada, exceto talvez a ltima, tem duas vezes
mais elementos que a camada anterior. Nesta representao, o nmero de camadas de
v[1 . . m] exatamente 1 + log2 m. (Veja Exerccio 1.2.4.)

Exerccios
10.1.1 Mostre que todo vetor decrescente um max-heap. Mostre que a recproca no
verdadeira.
10.1.2 O vetor 161 41 101 141 71 91 31 21 81 17 16 um max-heap?
10.1.3 Escreva uma funo que decida se um vetor v[1 . . m] ou no um max-heap.
10.1.4 Mostre que v[1 . . m] um max-heap se e somente se valem as seguintes relaes:
(1) v[p] v[2p] para cada ndice p tal que 2p m e (2) v[p] v[2p+1] para cada
ndice p tal que 2p + 1 m.
ELSEVIER Captulo 10. Ordenao: algoritmo Heapsort 77

10.1.5 Suponha que v[1 . . m] um max-heap. Mostre que v[1] v[j] para j = 2, . . . , m.
10.1.6 Suponha que v[1 . . 2k 1] um max-heap. Mostre que mais da metade dos
elementos do vetor est na ltima camada do max-heap, ou seja, em v[2k1 . . 2k 1].
10.1.7 Suponha que v[1 . . m] um max-heap. Sejam i e j dois ndices tais que i < j
e v[i] < v[j]. Se os valores de v[i] e v[j] forem trocados, v[1 . . m] continuar sendo um
max-heap? Repita o exerccio sob a hiptese v[i] > v[j].

10.2 Insero em um heap


fcil inserir um novo elemento em um max-heap de tal forma que a estrutura
continue sendo um max-heap: basta subir em direo raiz do heap procura
de um lugar apropriado para o novo elemento. A funo abaixo insere v[m+1]
no max-heap v[1 . . m]:

/* Esta funo recebe um max-heap v[1..m] e transforma


* v[1..m+1] em max-heap. */
void InsereEmHeap (int m, int v[]) {
int f = m+1;
while /*X*/ (f > 1 && v[f /2] < v[f ]) {
int t = v[f /2]; v[f /2] = v[f ]; v[f ] = t;
f = f /2;
}
}

( claro que o valor da expresso f /2  12 f .) No incio de cada iterao,


ou seja, a cada passagem pelo ponto X, v[1 . . m+1] uma permutao do vetor
original e a relao
v[ 12 i] v[i]
vale para todo i em 2 . . m+1 que seja diferente de f . Em virtude desses inva-
riantes, v[1 . . m+1] um max-heap no incio da ltima iterao.

Desempenho. A funo InsereEmHeap muito rpida. Como o valor da


varivel f comea em m + 1 e reduzido metade em cada iterao, a funo
consome no mximo
log2 (m + 1)
unidades de tempo. (O valor de cada unidade de tempo pode ser ligeiramente
reduzido se reescrevermos o cdigo como no Exerccio 10.2.1.)
78 ALGORITMOS em linguagem C ELSEVIER

1 2 3 4 5 6 7 8 9 10 11 12 13 14
98 97 96 95 94 93 92 91 90 89 87 86 85 99

98 97 96 95 94 93 99 91 90 89 87 86 85 92

98 97 99 95 94 93 96 91 90 89 87 86 85 92

99 97 98 95 94 93 96 91 90 89 87 86 85 92

Figura 10.3: Insero de v[14] no max-heap v[1 . . 13]. Cada linha


da gura mostra o resultado de uma iterao.

Exerccios
10.2.1 Por que a seguinte implementao da funo InsereEmHeap ligeiramente mais
eciente que a dada no texto?

int p, f = m+1;
while (f > 1 && v[p = f /2] < v[f ]) {
int t = v[p]; v[p] = v[f ]; v[f ] = t;
f = p; }

10.2.2 Escreva uma verso recursiva da funo InsereEmHeap.


10.2.3 Construo de um max-heap. Escreva uma funo eciente que rearranje
um vetor arbitrrio de modo a transform-lo em um max-heap. (Sugesto: use a funo
InsereEmHeap.)
10.2.4 Critique a seguinte ideia: para transformar um vetor arbitrrio em max-heap,
basta coloc-lo em ordem decrescente.

10.3 Um algoritmo auxiliar


Digamos que um vetor v[1 . . m] um quase max-heap se v[ 12 f ] v[f ] para
f = 4, 5, . . . , m. Para transformar um quase max-heap em max-heap basta
sacudir o vetor at que v[1] desa para sua posio correta.

/* Rearranja um quase max-heap v[1..m] de modo a


* transform-lo em um max-heap. */
ELSEVIER Captulo 10. Ordenao: algoritmo Heapsort 79

void SacodeHeap (int m, int v[]) {


int t, f = 2;
while /*X*/ (f <= m) {
if (f < m && v[f ] < v[f +1]) ++f ;
if (v[f /2] >= v[f ]) break;
t = v[f /2]; v[f /2] = v[f ]; v[f ] = t;
f *= 2;
}
}

(O primeiro if dentro do while faz com que f seja o lho mais valioso de
f /2.) Os invariantes do processo iterativo so simples: a cada passagem pelo
ponto X, v[1 . . m] uma permutao do vetor original e

v[ 12 i] v[i]

para todo i em 2 . . m que seja diferente de f e de f +1.


A anlise da ltima iterao complicada pelo fato de que o processo itera-
tivo pode ser interrompido em dois pontos diferentes. Suponha que estamos na
ltima passagem pelo ponto X. Se tivermos f > m, a desigualdade v[ 12 i] v[i]
vale para todo i sem excees e portanto v[1 . . m] um max-heap. Se f = m
ento o processo iterativo terminar no break com v[ f2 ] v[f ], donde o ve-
tor v[1 . . m] um max-heap. Finalmente, se f < m ento o processo iterativo
terminar no break com v[ f2 ] v[f ] e v[ f +1
2 ] v[f +1] e portanto v[1 . . m]
um max-heap.

Desempenho. A funo SacodeHeap muito rpida. Como o valor de f


pelo menos dobra a cada iterao, a funo no consome mais que

log2 m

unidades de tempo. (O valor de cada unidade de tempo pode ser reduzido se


reescrevermos o cdigo como no Exerccio 10.3.2.)

Exerccios
10.3.1 Mostre que os invariantes da funo SacodeHeap valem no incio da primeira
iterao. Supondo que os invariantes valem no incio de uma iterao, mostre que elas
continuam vlidas no incio da iterao seguinte.
10.3.2 Verique que a seguinte implementao da funo SacodeHeap ligeiramente
mais eciente que a dada no texto:
80 ALGORITMOS em linguagem C ELSEVIER

int p = 1, f = 2, t = v[1];
while (f <= m) {
if (f < m && v[f ] < v[f +1]) ++f ;
if (t >= v[f ]) break;
v[p] = v[f ];
p = f ; f *= 2; }
v[p] = t;
10.3.3 Por que a seguinte verso da funo SacodeHeap incorreta?
p = 1, f = 2;
while (f <= m) {
if (v[p] < v[f]) {
t = v[p], v[p] = v[f], v[f] = t;
p = f, f = 2*p; }
else {
if (f < m && v[p] < v[f+1]) {
t = v[p], v[p] = v[f+1], v[f+1] = t;
p = f+1, f = 2*p; }
else break; } }
10.3.4 Escreva uma verso recursiva da funo SacodeHeap.

10.4 O algoritmo Heapsort


O algoritmo Heapsort usa as funes InsereEmHeap e SacodeHeap para rear-
ranjar um vetor v[1 . . n] em ordem crescente. O algoritmo tem duas fases: a
primeira transforma o vetor dado em um max-heap; a segunda usa a estrutura
do max-heap para rearranjar o vetor em ordem crescente.

/* Rearranja o vetor v[1..n] de modo que ele fique


* crescente. */
void Heapsort (int n, int v[]) {
int m;
for (m = 1; m < n; m++)
InsereEmHeap (m, v);
for (m = n; /*X*/ m > 1; m--) {
int t = v[1]; v[1] = v[m]; v[m] = t;
SacodeHeap (m-1, v);
}
}
ELSEVIER Captulo 10. Ordenao: algoritmo Heapsort 81

O invariante do primeiro processo iterativo simples: no incio de cada


iterao v[1 . . m] um max-heap. Agora considere o segundo processo iterativo.
A cada passagem pelo ponto X, valem os seguintes invariantes:
1. v[1 . . n] uma permutao do vetor original,
2. o vetor v[1 . . m] um max-heap,
3. v[i] v[j] para todo i em 1 . . m e todo j em m+1 . . n e
4. o vetor v[m+1 . . n] est em ordem crescente.
No incio da ltima iterao (quando m vale 1), em virtude dos invariantes 3
e 4, o vetor v[1 . . n] crescente.

1 max-heap m crescente n
777 777 666 444 222 111 444 333 777 888 888 999 999
elementos pequenos elementos grandes

Figura 10.4: Incio de uma iterao da segunda fase da funo Heapsort.

Exerccios
10.4.1 Use a funo Heapsort para ordenar o vetor 16 15 14 13 12 11 10 9 8 7 6 5 4 .
10.4.2 Suponha que o vetor v[1 . . n] um max-heap. O seguinte fragmento de cdigo
rearranja o vetor em ordem crescente?
for (m = n; m >= 2; m--) {
int x = v[1];
for (j = 1; j < m; ++j) v[j] = v[j+1];
v[m] = x; }
10.4.3 Verique os invariantes da funo Heapsort.

10.5 Desempenho do algoritmo


Cada invocao de InsereEmHeap consome no mximo log2 (m + 1) unidades
de tempo. Logo, o consumo
 de tempo do primeiro processo iterativo da funo
Heapsort no passa de n1m=1 log2 (m + 1) unidades de tempo.
Agora considere o segundo processo iterativo. Como a funo SacodeHeap
consome no mximo log2 m unidades de tempo, o processo todo consome no
82 ALGORITMOS em linguagem C ELSEVIER


mximo nm=1 log2 m unidades de tempo. (Essas unidade de tempo no so
necessariamente iguais s da primeira fase do algoritmo.) Resumindo: a funo
Heapsort no consome mais que

n log2 n

unidades de tempo.

Exerccios
10.5.1 Ordem decrescente. Escreva uma verso do algoritmo Heapsort que rear-
ranje um vetor v[1 . . n] de modo que ele que em ordem decrescente.
10.5.2 Ordenao de strings. Escreva uma verso do algoritmo Heapsort que co-
loque um vetor de strings em ordem lexicogrca (veja Seo G.3).
10.5.3 Animaes. Veja animaes do algoritmo Heapsort nas pginas de Harrison [8]
e Morin [13].
10.5.4 Leia o verbete Heapsort na Wikipedia [21].
Captulo 11

Ordenao: algoritmo Quicksort

O algoritmo Quicksort [9] resolve o problema da ordenao introduzido na Se-


o 8.1, ou seja, rearranja um vetor v[0 . . n1] de modo que ele que crescente.
Em geral, o algoritmo muito mais rpido que os algoritmos elementares do Ca-
ptulo 8, mas pode ser to lento quanto aqueles para certas instncias especiais
do problema.
Usaremos duas abreviaturas: a expresso v[h . . j] x ser usada como
abreviatura de v[i] x para todo i no conjunto de ndices h . . j e a expresso
v[h . . j] v[k . . m] ser interpretada como v[i] v[l] para todo i no conjunto
h . . j e todo l no conjunto k . . m .

11.1 O problema da separao


O ncleo do algoritmo Quicksort o seguinte problema da separao, que for-
mularemos de maneira propositalmente vaga:
rearranjar um vetor v[p . . r] de modo que os elementos pequenos quem
todos do lado esquerdo e os grandes quem todos do lado direito.
Gostaramos que os dois lados tivessem aproximadamente o mesmo nmero de
elementos, mas estamos dispostos a aceitar resultados menos equilibrados. De
todo modo, importante que a separao no resulte degenerada, deixando um
dos lados vazio. A diculdade est em construir um algoritmo que resolva o
problema de maneira rpida e no use um vetor auxiliar.
O problema da separao admite vrias formulaes concretas. Eis uma
primeira: rearranjar v[p . . r] de modo que tenhamos

v[p . . j] v[j+1 . . r] (11.1)


84 ALGORITMOS em linguagem C ELSEVIER

para algum j em p . . r1. Outra formulao: rearranjar v[p . . r] de modo que

v[p . . j1] v[j] < v[j+1 . . r] (11.2)

para algum j em p . . r. Este captulo usa a segunda formulao; outras formu-


laes sero mencionadas nos exerccios.

Exerccios
11.1.1 Escreva uma funo que rearranje um vetor v[p . . r] de nmeros inteiros de modo
que os elementos negativos e nulos quem esquerda e os positivos quem direita. Em
outras palavras, rearranje o vetor de modo que tenhamos v[p . . j1] 0 e v[j . . r] > 0
para algum j em p . . r+1. (Faz sentido exigir que j esteja em p . . r?) Procure escrever
uma funo eciente que no use vetor auxiliar. Repita o exerccio depois de trocar
v[j . . r] > 0 por v[j . . r] 0.
11.1.2 Digamos que um vetor v[p . . r] est arrumado se existe j em p . . r que satis-
faz (11.2). Escreva um algoritmo que decida se v[p . . r] est arrumado. Em caso
armativo, o seu algoritmo deve devolver o valor de j.
11.1.3 Critique a soluo do problema da separao dada abaixo. Qual das formulaes
concretas do problema ela satisfaz?
int w[1000], c, i, j, k;
c = v[p]; i = p; j = r;
for (k = p+1; k <= r; k++)
if (v[k] <= c) w[i++] = v[k];
else w[j--] = v[k];
w[j] = c;
for (k = p; k <= r; k++) v[k] = w[k];
return j;
11.1.4 Um programador inexperiente arma que a seguinte funo resolve a formula-
o (11.1) do problema da separao. Mostre uma instncia em que a funo no d o
resultado esperado.
int q, i, j, t;
i = p; q = (p + r)/2; j = r;
do {
while (v[i] < v[q]) i++;
while (v[j] > v[q]) j--;
if (i <= j) {
t = v[i], v[i] = v[j], v[j] = t;
i++, j--; }
} while (i < j);
return i;
ELSEVIER Captulo 11. Ordenao: algoritmo Quicksort 85

11.2 Algoritmo da separao


A seguinte funo uma soluo eciente da formulao (11.2) do problema
da separao.1 Ela comea por escolher um piv c que denir o signicado
de pequeno e grande: os elementos do vetor que forem maiores que c sero
considerados grandes e os demais sero considerados pequenos.

/* Recebe um vetor v[p..r] com p <= r. Rearranja os


* elementos do vetor e devolve j em p..r tal que
* v[p..j-1] <= v[j] < v[j+1..r]. */
int Separa (int p, int r, int v[]) {
int c, j, k, t;
c = v[r]; j = p;
for (k = p; /*A*/ k < r; k++)
if (v[k] <= c) {
t = v[j], v[j] = v[k], v[k] = t;
j++;
}
v[r] = v[j], v[j] = c;
return j;
}

No incio de cada iterao, ou seja, a cada passagem pelo ponto A, valem os


seguintes invariantes:
1. v[p . . r] uma permutao do vetor original,
2. v[p . . j1] c < v[j . . k1], v[r] = c e
3. p j k r.
Na ltima passagem por A teremos k = r e portanto v[j . . r1] > c. Assim,
depois da troca de v[j] com v[r], teremos v[p . . j1] v[j] < v[j+1 . . r].

Desempenho do algoritmo da separao. O consumo de tempo da


funo Separa proporcional ao nmero de iteraes. Como o nmero de ite-
raes r p + 1, podemos dizer que o consumo de tempo proporcional ao
nmero de elementos do vetor.
1
Esta verso da funo consta do livro de Cormen et al. [5]. Compare-a com a funo
RemoveZeros na Seo 3.5.
86 ALGORITMOS em linguagem C ELSEVIER

p j k r
c c c >c >c >c ? ? ? =c

j k
c c c c >c >c >c >c >c =c

j
c c c c =c >c >c >c >c >c

Figura 11.1: A parte superior da gura mostra a congurao no incio de uma iterao
qualquer da funo Separa. A parte mdia mostra a congurao na ltima passagem
pelo ponto A. A parte inferior mostra o resultado nal de Separa.

Exerccios
11.2.1 A funo Separa produz o resultado correto quando p = r?
11.2.2 Aplique a funo Separa a um vetor cujos elementos so todos iguais. Aplique
a funo a um vetor cujos elementos s tm dois possveis valores. Aplique a funo a
um vetor crescente e a um vetor decrescente.
11.2.3 Critique a seguinte variante da parte central do cdigo da funo Separa:
for (k = p; k < r; k++) {
if (v[k] <= c) {
if (j < k) t = v[j], v[j] = v[k], v[k] = t;
j++; } }
if (j < r) v[r] = v[j], v[j] = c;
11.2.4 Critique a seguinte verso da funo Separa. Quais os invariantes?
int c = v[p], i = p+1, j = r, t;
while (i <= j) {
if (v[i] <= c) ++i;
else {
t = v[i], v[i] = v[j], v[j] = t;
--j; } }
v[p] = v[j], v[j] = c;
return j;
11.2.5 A seguinte verso da funo Separa aparece no livro de Gries [7]. Quais os
invariantes do processo iterativo?
int c = v[p], i = p+1, j = r, t;
while (1) {
while (i <= r && v[i] <= c) ++i;
while (c < v[j]) --j;
if (i >= j) break;
ELSEVIER Captulo 11. Ordenao: algoritmo Quicksort 87

t = v[i], v[i] = v[j], v[j] = t;


++i, --j; }
v[p] = v[j], v[j] = c;
return j;
11.2.6 Verique que a seguinte verso da funo Separa resolve a formulao (11.2) do
problema da separao (veja Seo 11.1). Mostre que ela equivalente do Exerc-
cio 11.2.5. Quais so os invariantes no ponto A?
int c = v[p], i = p + 1, j = r, t;
while (/*A*/ i <= j) {
if (v[i] <= c) ++i;
else if (c < v[j]) --j;
else {
t = v[i], v[i] = v[j], v[j] = t;
++i, --j; } } /* agora i == j + 1 */
v[p] = v[j], v[j] = c;
return j;
11.2.7 Estabilidade. A funo Separa produz um rearranjo estvel (veja Seo 8.5)
do vetor?
11.2.8 Escreva uma verso recursiva do algoritmo da separao.
11.2.9 Desafio. Escreva uma verso do algoritmo da separao que produza um ndice
j tal que j p que entre 14 (r p) e 34 (r p).

11.3 Algoritmo Quicksort bsico


Resolvido o problema da separao, podemos cuidar do Quicksort propriamente
dito. O algoritmo usa a estratgia dividir para conquistar e poderia ser descrito
vagamente como um Mergesort ao contrrio:

/* Esta funo rearranja o vetor v[p..r], com p <= r+1,


* de modo que ele fique em ordem crescente. */
void Quicksort (int p, int r, int v[]) {
int j;
if (p < r) {
j = Separa (p, r, v);
Quicksort (p, j - 1, v);
Quicksort (j + 1, r, v);
}
}
88 ALGORITMOS em linguagem C ELSEVIER

(Observe que a funo est correta mesmo quando p > r, ou seja,


quando o vetor est vazio.) Para ordenar um vetor v[0 . . n1], basta dizer
Quicksort (0, n - 1, v).

Exerccios
11.3.1 Que acontece se trocarmos if (p < r) por if (p != r) no cdigo da funo
Quicksort?
11.3.2 No cdigo da funo Quicksort, que acontece se trocarmos a invocao
Quicksort (p, j-1, v) por Quicksort (p, j, v) ? Que acontece se trocarmos a
invocao Quicksort (j+1, r, v) por Quicksort (j, r, v) ?
11.3.3 Compare o cdigo da funo Quicksort com o da funo Mergesort (Cap-
tulo 9). Discuta as semelhanas e diferenas.
11.3.4 Submeta funo Quicksort o vetor 99 55 33 77 indexado por 1..4. Teremos
a seguinte sequncia de invocaes da funo:
Quicksort (1, 4, v)
Quicksort (1, 2, v)
Quicksort (1, 0, v)
Quicksort (2, 2, v)
Quicksort (4, 4, v)
Repita o exerccio com o vetor 55 44 22 11 66 33 indexado por 1..6.
11.3.5 Reescreva a funo Quicksort trocando a invocao de Separa pelo cdigo da
funo.
11.3.6 Tail recursion. Mostre que a segunda invocao da funo Quicksort pode
ser eliminada se trocarmos o if por um while apropriado.
11.3.7 Escreva uma implementao do algoritmo Quicksort que evite aplicar a funo
a vetores com menos que dois elementos.
11.3.8 Separao reformulada. Suponha dada uma verso da funo Separa que
resolve a formulao (11.1) do problema da separao (veja Seo 11.1). Escreva uma
variante do Quicksort que use essa verso de Separa.
11.3.9 Separao reformulada. Suponha dada uma verso da funo Separa que
rearranja o vetor v[p . . r] e devolve um ndice i em p . . r tal que v[p . . i1] v[i]
v[i+1 . . r]. Escreva uma variante do Quicksort que use essa verso de Separa.
11.3.10 Estabilidade. A funo Quicksort produz uma ordenao estvel (veja Se-
o 8.5)?
11.3.11 Verso iterativa. Escreva uma verso no recursiva do algoritmo Quicksort.
ELSEVIER Captulo 11. Ordenao: algoritmo Quicksort 89

11.4 Desempenho do algoritmo


Quanto tempo a funo Quicksort consome para ordenar um vetor v[0 . . n1]?
O consumo de tempo proporcional ao nmero de comparaes entre elementos
do vetor. Se o ndice j devolvido por Separa estiver sempre mais ou menos a
meio caminho entre p e r, o nmero de comparaes ser aproximadamente

n log2 n .

Caso contrrio, o nmero de comparaes estar na ordem de n2 . (Isso acontece,


por exemplo, se o vetor j estiver ordenado ou quase ordenado.) Portanto, o pior
caso do Quicksort no melhor que o dos algoritmos elementares do Captulo 8.
Felizmente, o pior caso raro. O consumo de tempo mdio do Quicksort
proporcional a n log2 n. (Veja Cormen et al. [5]).

Exerccios
11.4.1 Aplique a funo Quicksort a um vetor crescente com n elementos. Mostre
que o nmero de comparaes entre elementos do vetor proporcional a n2 . (Veja
Exerccio 11.2.2.) Repita o exerccio com vetor decrescente.
11.4.2 Ordenao de strings. Escreva uma verso do algoritmo Quicksort que
coloque um vetor de strings em ordem lexicogrca (veja Seo G.3).
11.4.3 Ordenao de lista encadeada. Escreva uma verso do algoritmo Quick-
sort que rearranje uma lista encadeada de modo que ela que em ordem crescente. Sua
funo no deve alocar novas clulas na memria.

11.5 Altura da pilha de execuo do Quicksort


Na verso bsica do Quicksort (veja Seo 11.3), o cdigo cuida imediata-
mente do subvetor v[p . . j1] e trata do subvetor v[j+1 . . r] somente depois
que v[p . . j1] estiver ordenado. Dependendo do valor de j nas sucessivas in-
vocaes da funo, a pilha de execuo (veja Seo 6.5) pode crescer muito,
atingindo altura igual ao nmero de elementos do vetor. (Isso acontece, por
exemplo, se o vetor estiver em ordem decrescente.) O fenmeno no afeta o
consumo de tempo do algoritmo, mas pode esgotar o espao de memria. Para
controlar o crescimento da pilha de execuo preciso tomar duas providncias:

1. cuidar primeiro do menor dos subvetores v[p . . j1] e v[j+1 . . r] e


2. eliminar a segunda invocao recursiva da funo
(veja Exerccio 11.3.6 acima).
90 ALGORITMOS em linguagem C ELSEVIER

Se adotarmos estas providncias, o tamanho do subvetor que est no topo da


pilha de execuo ser menor que a metade do tamanho do subvetor que est
logo abaixo na pilha. De modo mais geral, o subvetor que est em qualquer
das posies da pilha de execuo ser menor que metade do subvetor que
est imediatamente abaixo. Assim, se a funo for aplicada a um vetor com n
elementos, a altura da pilha no passar de log2 n.

/* Esta funo rearranja o vetor v[p..r], com p <= r+1,


* de modo que ele fique em ordem crescente. */
void QuickSort (int p, int r, int v[]) {
int j;
while (p < r) {
j = Separa (p, r, v);
if (j - p < r - j) {
QuickSort (p, j - 1, v);
p = j + 1;
} else {
QuickSort (j + 1, r, v);
r = j - 1;
}
}
}

Exerccios
11.5.1 A seguinte verso do algoritmo Quicksort consta da primeira edio do livro de
Cormen et al. [5]. Verique que ela se baseia na formulao (11.1) do problema da
separao (Seo 11.1). D os invariantes do while externo.
void QuicksortCLR (int p, int r, int v[]) {
int c = v[p], i = p - 1, j = r + 1, t;
if (p < r) {
while (1) {
do --j; while (v[j] > c);
do ++i; while (v[i] < c);
if (i >= j) break;
t = v[i], v[i] = v[j], v[j] = t; }
QuicksortCLR (p, j, v);
QuicksortCLR (j + 1, r, v); } }
11.5.2 A verso abaixo do Quicksort semelhante do livro de Sedgewick [18]. Formule
o problema da separao que esta verso resolve. D os invariantes do while externo.
ELSEVIER Captulo 11. Ordenao: algoritmo Quicksort 91

void QuicksortS (int p, int r, int v[]) {


int c = v[(p+r)/2], i = p, j = r, t;
if (p < r) {
while (i <= j) {
while (v[i] < c) ++i;
while (c < v[j]) --j;
if (i <= j) {
t = v[i], v[i] = v[j], v[j] = t;
++i, --j; } }
QuicksortS (p, j, v);
QuicksortS (i, r, v); } }
11.5.3 Quicksort aleatorizado. Para tentar evitar o comportamento de pior
caso da funo Separa, podemos escolher o piv aleatoriamente, recorrendo funo
InteiroAleatrio (veja Apndice I):
int SeparaAleatorizado (int p, int r, int v[]) {
int i, t;
i = InteiroAleatrio (p, r);
t = v[p], v[p] = v[i], v[i] = t;
return Separa (p, r, v); }
Use esta funo para escrever uma implementao aleatorizada do algoritmo Quicksort.
11.5.4 Animaes. Veja animaes do algoritmo Quicksort nas pginas de Harrison [8]
e Morin [13].
11.5.5 Leia o verbete Quicksort na Wikipedia [21].
11.5.6 Familiarize-se com a funo qsort da biblioteca stdlib (veja Seo K.1).
Captulo 12

Algoritmos de enumerao

Often it appears that there is no better way to solve a problem


than to try all possible solutions.
This approach, called exhaustive search, is almost always slow,
but sometimes it is better than nothing.
I. Parberry, Problems on Algorithms

Para resolver certos problemas combinatrios, necessrio enumerar ou seja,


fazer uma lista de todos os objetos de um determinado tipo. O nmero de
objetos a enumerar tipicamente muito grande, e portanto os algoritmos enume-
rativos consomem muito tempo. Algoritmos de enumerao esto relacionados
com palavras-chave como busca exaustiva, fora bruta e backtracking.
Este captulo trata da enumerao de sequncias de nmeros naturais, mas
as ideias tambm se aplicam enumerao de outros tipos de objetos. Os
algoritmos no so complexos, mas tm suas sutilezas. As verses recursivas
so particularmente teis e interessantes.

12.1 Enumerao de subsequncias


Uma subsequncia o que sobra de uma sequncia quando alguns de seus ter-
mos so apagados. Mais precisamente, uma subsequncia de s1 , s2 , . . . , sn
qualquer sequncia da forma si1 , si2 , . . . , sik onde 1 i1 < i2 < < ik n.
Por exemplo, 2, 3, 5, 8 uma subsequncia de 1, 2, 3, 4, 5, 6, 7, 8.

Problema: Enumerar todas as subsequncias de 1, 2, . . . , n, ou seja, fazer


uma lista em que cada subsequncia aparece uma e uma s vez.
94 ALGORITMOS em linguagem C ELSEVIER

1
1 2
1 2 3
1 2 3 4
1 2 4
1 3
1 3 4
1 4 Figura 12.1: Todas as subsequncias no vazias
2 de 1, 2, 3, 4, em ordem lexicogrca.
2 3
2 3 4
2 4
3
3 4
4

H uma correspondncia biunvoca bvia entre as subsequncias de 1, 2, . . . , n e


os subconjuntos do conjunto {1, 2, . . . , n}. Portanto, o nmero de subsequncias
de 1, 2, . . . , n 2n . Este nmero aumenta explosivamente com n: ele dobra toda
vez que n aumenta de uma unidade.
Nossas sequncias sero armazenadas em vetores. A sequncia s1 , . . . , sk ,
por exemplo, ser armazenada num vetor s[1 . . k]. Com isso, as expresses s[i]
e si sero consideradas sinnimas. A primeira mais apropriada no cdigo C,
enquanto a segunda mais apropriada no texto em portugus.

12.2 Subsequncias em ordem lexicogrca


A ordem em que as subsequncias de 1, 2, . . . , n so enumeradas no muito
importante, mas certas ordens so mais naturais que outras. Uma das ordens
mais naturais a lexicogrca (esta a ordem em que palavras aparecem em
um dicionrio, conforme Seo G.3). Uma sequncia r1 , r2 , . . . , rj lexicogra-
camente menor que outra s1 , s2 , . . . , sk se
1. j < k e r1 , . . . , rj igual a s1 , . . . , sj ou
2. existe i tal que r1 , . . . , ri1 igual a s1 , . . . , si1 e ri < si .
A seguinte funo gera uma lista de sequncias em ordem lexicogrca:

/* Esta funo recebe n >= 1 e imprime todas as


* subsequncias no vazias de 1,2,...,n
* em ordem lexicogrfica. */
ELSEVIER Captulo 12. Algoritmos de enumerao 95

void SubseqLex (int n) {


int *s, k;
s = malloc ((n+1) * sizeof (int));
s[0] = 0; k = 0;
while (1) {
if (s[k] < n) {
s[k+1] = s[k] + 1;
k += 1;
} else {
s[k-1] += 1;
k -= 1;
}
if (k == 0) break;
imprima (s, k);
}
free (s);
}

Cada iterao comea com uma subsequncia s1 , s2 , . . . , sk de 1, 2, . . . , n


armazenada no vetor s[1..k]. A primeira iterao comea com a subsequncia
vazia. Cada iterao gera a sucessora de s1 , s2 , . . . , sk na ordem lexicogrca.
Se s1 , s2 , . . . , sk no tiver sucessora, o processo termina.
Trs detalhes da funo SubseqLex merecem comentrio. (1) A expresso
imprima (s, k) no faz mais que imprimir o vetor s[1..k]. (2) A sentinela
s[0] foi denida para que o comando s[k-1] += 1 possa ser executado quando
k vale 1, coisa que acontece somente na ltima iterao. (3) O vetor s[1..k]
comporta-se como uma pilha (veja Captulo 6), sendo k o ndice do topo da
pilha.

k
0 1 2 3 4 5 6 7
0 2 4 5 7 8 ? ?

Figura 12.2: Vetor s no incio de uma iterao de SubseqLex


com argumento n = 7.
96 ALGORITMOS em linguagem C ELSEVIER

Exerccios
12.2.1 Analise a seguinte variante da funo SubseqLex:
s[0] = 0; s[1] = 1; k = 1;
while (k >= 1) {
imprima (s, k);
if (s[k] < n) {
s[k+1] = s[k] + 1; k += 1; }
else {
s[k-1] += 1; k -= 1; } }
12.2.2 Analise a seguinte verso alternativa da funo SubseqLex:
s[1] = 1; k = 1;
imprima (s, 1);
while (s[1] < n) {
if (s[k] < n) {
s[k+1] = s[k] + 1; k += 1; }
else {
s[k-1] += 1; k -= 1; }
imprima (s, k); }
12.2.3 Escreva uma funo que imprima uma lista de todos os subconjuntos do conjunto
{1, 2, . . . , n}.

12.3 Verso recursiva do algoritmo


A verso recursiva de SubseqLex muito interessante. A interface com o usurio
ca a cargo da seguinte funo-embalagem:

/* Recebe n >= 1 e imprime, em ordem lexicogrfica,


* todas as subsequncias no vazias de 1,2,...,n. */
void SubseqLex2 (int n) {
int *s;
s = malloc ((n+1) * sizeof (int));
SseqR (s, 0, 1, n);
free (s);
}

O servio pesado todo executado pela funo recursiva SseqR. Para cada valor
de m, ela imprime todas as subsequncias que incluem m e depois todas as que
no incluem m.
ELSEVIER Captulo 12. Algoritmos de enumerao 97

void SseqR (int s[], int k, int m, int n) {


if (m <= n) {
s[k+1] = m;
imprima (s, k+1);
SseqR (s, k+1, m+1, n); /* inclui m */
SseqR (s, k, m+1, n); /* no inclui m */
}
}

A explicao dada acima sobre o que faz (veja introduo do Captulo 1) a


funo SseqR muito vaga, pois omite o papel dos parmetros s e k. Eis uma
documentao precisa:

/* A funo SseqR recebe s[1..k] e m e imprime,


* em ordem lexicogrfica, cada uma das subsequncias
* no vazias de m,...,n precedida do prefixo s[1..k].
* Em outras palavras, imprime todas as sequncias que
* tm a forma s[1],..,s[k],t[k+1],..., sendo t[k+1],...
* uma subsequncia no vazia de m,...,n. */

Portanto, o clculo da expresso SseqR (s, 0, 1, n) faz exatamente o que que-


remos: imprime todas as subsequncias t1 , t2 , . . . de 1, 2, . . . , n que tm pelo
menos um termo.

2 4 7
2 4 7 8
2 4 7 8 9
2 4 7 9
2 4 8
2 4 8 9
2 4 9

Figura 12.3: Resultado de SseqR (s,2,7,9) com s[1] = 2 e s[2] = 4.


A primeira linha gerada por imprima (s,3). As trs linhas seguintes so
geradas por SseqR (s,3,8,9). As demais, por SseqR (s,2,8,9).

Exerccios
12.3.1 Escreva uma funo que receba um vetor estritamente crescente s[1 . . k] repre-
sentando uma subsequncia s1 , s2 , . . . , sk de 1, 2, . . . , n e grave, no espao alocado a s,
98 ALGORITMOS em linguagem C ELSEVIER

a prxima subsequncia na ordem lexicogrca. A funo deve devolver o nmero de


termos (k + 1 ou k 1) da nova subsequncia.

12.4 Subsequncias em ordem lexicogrca especial


A ordem lexicogrca especial d preferncia s subsequncias mais longas.
Eis a denio formal: uma sequncia r1 , r2 , . . . , rj precede outra s1 , s2 , . . . , sk
na ordem lexicogrca especial se
1. j > k e r1 , . . . , rk igual a s1 , . . . , sk ou
2. existe i tal que r1 , . . . , ri1 igual a s1 , . . . , si1 e ri < si .
A seguinte funo gera uma lista de sequncias em ordem lexicogrca es-
pecial:

/* Recebe n >= 1 e imprime, em ordem lexicogrfica especial,


* todas as subsequncias no vazias de 1,2,...,n. */
void SubseqLexEsp (int n) {
int *s, k;
s = malloc ((n+1) * sizeof (int));
s[1] = 0; k = 1;
while (1) {
if (s[k] == n) {
k -= 1;
if (k == 0) break;
} else {
s[k] += 1;
while (s[k] < n) {
s[k+1] = s[k] + 1;
k += 1;
}
}
imprima (s, k);
}
free (s);
}

Na verso recursiva da funo, convm imprimir tambm a subsequncia


vazia:
ELSEVIER Captulo 12. Algoritmos de enumerao 99

1 2 3 4
1 2 3
1 2 4
1 2
1 3 4
1 3
1 4
Figura 12.4: Todas as subsequncias no vazias
1 de 1, 2, 3, 4, em ordem lexicogrca especial.
2 3 4
2 3
2 4
2
3 4
3
4

/* Recebe n >= 1 e imprime, em ordem lexicogrfica


* especial, todas as subsequncias de 1,2,...,n. */
void SubseqLexEsp2 (int n) {
int *s;
s = malloc ((n+1) * sizeof (int));
SseqEspR (s, 0, 1, n);
free (s);
}
/* Esta funo auxiliar recebe um vetor s[1..k] e imprime,
* em ordem lexicogrfica especial, todas as sequncias
* da forma s[1],...,s[k],t[k+1],... tais que
* t[k+1],... uma subsequncia de m,m+1,...,n.
* Em seguida, imprime a sequncia s[1],...,s[k]. */
void SseqEspR (int s[], int k, int m, int n) {
if (m > n) imprima (s, k);
else {
s[k+1] = m;
SseqEspR (s, k+1, m+1, n); /* inclui m */
SseqEspR (s, k, m+1, n); /* no inclui m */
}
}

De acordo com a documentao da funo, o comando SseqEspR (s, 0,


1, n) imprime todas subsequncias de 1, 2, . . . , n, como desejado.
100 ALGORITMOS em linguagem C ELSEVIER

2 4 7 8 9
2 4 7 8
2 4 7 9
2 4 7
Figura 12.5: Lista impressa por SseqEspR (s,2,7,9)
2 4 8 9 supondo que s[1] = 2 e s[2] = 4.
2 4 8
2 4 9
2 4 9
2 4

Exerccios
12.4.1 Ordem militar. A lista abaixo (leia a coluna esquerda, depois a do meio,
depois a direita) exibe as subsequncias de 1, 2, 3, 4 em ordem militar. Analise esta
ordem. Escreva uma funo que imprima todas as subsequncias de 1, 2, . . . , n em
ordem militar. Escreva duas verses: uma iterativa e uma recursiva.
1 1 3 1 2 3
2 1 4 1 2 4
3 2 3 1 3 4
4 2 4 2 3 4
1 2 3 4 1 2 3 4
12.4.2 Subset sum. Suponha que voc emitiu cheques com valores p1 , . . . , pn ao longo
de um ms. No m do ms, o banco informa que uma quantia T foi descontada de
sua conta. Quais dos cheques foram descontados? Por exemplo, se p = 61, 62, 63, 64
e T = 125 ento s h duas possibilidades: ou foram descontados os cheques 1 e 4 ou
foram descontados os cheques 2 e 3. Esta uma instncia do problema subset sum
(soma de subconjunto): dado um nmero T e um vetor p[1 . . n], encontrar todas as
subsequncias s1 , s2 , . . . , sk de 1, 2, . . . , n para as quais p[s1 ] + + p[sk ] = T . Escreva
uma funo que resolva o problema.
12.4.3 Combinaes. Escreva uma funo que imprima todas as subsequncias de
1, 2, . . . , n que tm exatamente k termos. (Isso corresponde aos subconjuntos de
{1, 2, . . . , n} que tm exatamente k elementos.)
12.4.4 Permutaes. Uma permutao da sequncia 1, 2, . . . , n qualquer rearranjo
desta sequncia. Por exemplo, as seis permutaes de (1, 2, 3) so (1, 2, 3), (1, 3, 2),
(2, 1, 3), (2, 3, 1), (3, 1, 2) e (3, 2, 1). Escreva uma funo que imprima, exatamente
uma vez, cada uma das n! permutaes de 1, 2, . . . , n.
12.4.5 Desarranjos. Um desarranjo da sequncia 1, 2, . . . , n qualquer permutao
desta sequncia que muda todos os termos de posio. Em outras palavras, um desar-
ranjo de 1, 2, . . . , n qualquer permutao p1 , p2 , . . . , pn de 1, 2, . . . , n tal que pi = i
para todo i. Por exemplo, os nove desarranjos de (1, 2, 3, 4) so (2, 1, 4, 3), (2, 3, 4, 1),
(2, 4, 1, 3), (3, 1, 4, 2), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 3, 1, 2) e (4, 3, 2, 1). Escreva
uma funo que imprima, exatamente uma vez, cada desarranjo de 1, 2, . . . , n.
ELSEVIER Captulo 12. Algoritmos de enumerao 101

12.4.6 Parties. Escreva uma funo que imprima uma lista de todas as parties1
do conjunto {1, 2, . . . , n} em m blocos no vazios. Uma tal partio pode ser represen-
tada por um vetor p[1 . . n] com valores no conjunto {1, 2, . . . , m} dotado da seguinte
propriedade: para cada i entre 1 e m, existe pelo menos um j tal que p[j] = i.
12.4.7 Problema das rainhas. possvel colocar 8 rainhas do jogo de xadrez sobre
o tabuleiro de modo que nenhuma das rainhas possa atacar outra?
12.4.8 Passeio do cavalo. Suponha dado um tabuleiro de xadrez n-por-n. Determine
se possvel que um cavalo do jogo de xadrez parta da posio (1, 1) do tabuleiro e
complete um passeio por todas as n2 posies do tabuleiro em n2 1 passos vlidos. Por
exemplo para um tabuleiro 5-por-5 uma soluo do problema indicada pela matriz
abaixo.
1 6 15 10 21
14 9 20 5 16
19 2 7 22 11
8 13 24 17 4
25 18 3 12 23
Sugesto: Numere as casas do tabuleiro e examine todas as permutaes de 1, 2, . . . , n2 .
Para cada permutao, verique se ela representa um passeio do cavalo.
12.4.9 Familiarize-se com a pgina Combinatorial Object Server de Ruskey [17].

1
Uma partio de um conjunto X qualquer coleo P de subconjuntos no vazios de X
dotada da seguinte propriedade: todo elemento de X pertence a um e apenas um dos elementos
de P. Por exemplo, {{1, 3}, {2, 4, 7}, {5}, {6, 8}} uma partio de {1, 2, 3, 4, 5, 6, 7, 8}. O
conjunto {6, 8} um dos blocos da partio.
Captulo 13

Busca de palavras em um texto

Considere o problema de encontrar as ocorrncias de uma dada sequncia curta


em outra longa. O problema, conhecido como string searching ou string mat-
ching, aparece naturalmente em um sem-nmero de aplicaes, de editores de
texto a gentica computacional. O problema no to simples quanto parece
se insistirmos em algoritmos ecientes.

13.1 O problema da busca


Um vetor a[1 . . m] suxo de um vetor b[1 . . k] se m k e a[1 . . m] =
b[km+1 . . k], ou seja, a[1] = b[km+1], a[2] = b[km+2], . . . , a[m] = b[k].
Dizemos que um vetor a[1 . . m] ocorre em um vetor b[1 . . n] se existe k no
intervalo m . . n tal que a[1 . . m] suxo de b[1 . . k].
Este captulo estuda o problema de localizar todas as ocorrncias de um
vetor a[1 . . m] em um vetor b[1 . . n]. Para simplicar, trataremos apenas de
contar o nmero de ocorrncias.

Problema: Encontrar o nmero de ocorrncias de a[1 . . m] em b[1 . . n].

Suporemos que a e b so vetores de caracteres, embora o problema tambm faa


sentido para outros tipos de dados. Diremos que o vetor a uma palavra e que
b um texto.
typedef unsigned char *palavra;
typedef unsigned char *texto;
Com isso, nosso problema pode ser resumido assim: encontrar o nmero de
ocorrncias de uma dada palavra em um dado texto.
Para garantir que o nmero de ocorrncias de a em b seja nito, suporemos
104 ALGORITMOS em linguagem C ELSEVIER

a l g o r t i m o
O s a l g o r t i m o s d e o r d e n a o

T A C T A
G T A G T A T A T A T A T A T A C T A C T A G T A G

Figura 13.1: Dois exemplos de busca de uma palavra a[1 . . m] em um texto b[1 . . n].
O primeiro sugere a busca de um erro de graa em um documento. O segundo sugere
a busca de um gene em um cromossomo. O sinal indica um ndice k tal que a[1 . . m]
suxo de b[1 . . k]. No segundo exemplo h duas ocorrncias sobrepostas de a em b.

que m 1. Quanto a n, no h razo para excluir o caso n = 0. claro que se


m > n, o nmero de ocorrncias de a em b nulo.
H duas questes simples que convm esclarecer desde j. Para procurar
uma ocorrncia de a em b, podemos varrer b da esquerda para a direita ou da
direita para a esquerda. As duas alternativas so equivalentes, mas vamos ado-
tar sempre a primeira: comparar a com b[1 . . m], depois com b[2 . . m+1], etc.
Agora considere a questo de decidir se a suxo de b[1 . . k] para um k xo. A
comparao de a[1 . . m] com b[km+1 . . k] pode ser feita da esquerda para a
direita ou da direita para a esquerda. Em geral, as duas alternativas so equi-
valentes; mas um dos algoritmos que veremos adiante exige que a comparao
seja feita na direo contrria da varredura do texto. Por isso, adotaremos
sempre comparao da direita para a esquerda: primeiro a[m] com b[k], depois
a[m1] com b[k1] etc.

13.2 Algoritmo trivial


A seguinte funo resolve nosso problema da maneira mais bvia: paciente-
mente, procura a como suxo de b[1 . . m], depois como suxo de b[1 . . m+1], e
assim por diante.

/* Recebe uma palavra a[1..m] e um texto b[1..n],


* com m >= 1 e n >= 0, e devolve o nmero de
* ocorrncias de a em b. */
ELSEVIER Captulo 13. Busca de palavras em um texto 105

int trivial (palavra a, int m, texto b, int n) {


int k, r, ocorrs;
ocorrs = 0;
for (k = m; k <= n; k++) {
r = 0;
while (r < m && a[m-r] == b[k-r]) r += 1;
if (r >= m) ocorrs += 1;
}
return ocorrs;
}

A funo trivial faz no mximo mn comparaes entre os elementos dos


dois vetores. Portanto, consome tempo proporcional a mn no pior caso. Gosta-
ramos de encontrar um algoritmo que no consumisse mais que m + n unidades
de tempo.

Exerccio
13.2.1 D um exemplo em que o algoritmo trivial faz o maior nmero possvel de
comparaes entre elementos de a e b. Descreva o exemplo com preciso.

13.3 Primeiro algoritmo de BoyerMoore


Suponha que o conjunto a que pertencem os elementos de a e b conhecido de
antemo. Diremos que este conjunto o alfabeto do problema. (O alfabeto
pode ser, por exemplo, o conjunto de todos os 256 caracteres.) Neste caso,
possvel construir um algoritmo melhor que o trivial. Para cada caractere c do
alfabeto, dena o nmero T1[c] da seguinte maneira: se c est em a ento

T1[c] o menor t em 0 . . m1 tal que a[m t] = c (13.1)

e T1[c] = m se c no est em a. Portanto, T1[c] corresponde ltima ocorrn-


cia, no sentido esquerdaparadireita, do caractere c em a. Diremos que T1[c]
o deslocamento correspondente a c.
Suponha agora que c igual a b[k+1] para um determinado k m. Se
T1[c] = 4, por exemplo, ento os caracteres a[m], a[m1], a[m2], a[m3] so
todos diferentes de c e portanto a[1 . . m] no suxo de

b[1 . . k+1], nem de b[1 . . k+2], nem de b[1 . . k+3], nem de b[1 . . k+4]
(supondo k+4 n). De modo mais geral, se k um ndice em m . . n1 e c o
caractere b[k+1], ento os caracteres a[m], a[m1], a[m2], . . . , a[mT1[c]+1]
106 ALGORITMOS em linguagem C ELSEVIER

so todos diferentes de c e portanto a no suxo de

b[1 . . k+1], nem de b[1 . . k+2], . . . , nem de b[1 . . ] , (13.2)

sendo o menor dentre n e k + T1[c].


Suponha que j determinamos o nmero, digamos N , de ocorrncias de a em
b[1 . . k]. Seja c o caractere b[k+1]. Se k +T1[c] n ento, em virtude de (13.2),
nosso problema est resolvido: o nmero de ocorrncias de a em b[1 . . n] N .
Se k + T1[c] < n ento podemos concluir, sem fazer quaisquer comparaes
adicionais, que o nmero de ocorrncias de a em b[1 . . k+T1[c]] tambm N .
A ideia que acabamos de descrever a base do primeiro algoritmo de Boyer
Moore [3]. A primeira fase do algoritmo, conhecida como pr-processamento da
palavra, calcula a tabela T1. Na segunda fase, o algoritmo usa T1 para procurar
as ocorrncias de a em b.

/* Recebe uma palavra a[1..m] e um texto b[1..n], com


* m >= 1 e n >= 0, e devolve o nmero de ocorrncias
* de a em b. Supe que cada elemento de a e b pertence
* ao conjunto de caracteres 0..255. */
int BoyerMoore1 (palavra a, int m, texto b, int n) {
int T1[256], i, k, r, ocorrs;
/* pr-processamento da palavra a */
for (i = 0; i < 256; i++) T1[i] = m;
for (i = 1; i <= m; i++) T1[a[i]] = m - i;
/* busca da palavra a no texto b */
ocorrs = 0; k = m;
while (k <= n) {
r = 0;
while (m - r >= 1 && a[m-r] == b[k-r]) r += 1;
if (m - r < 1) ocorrs += 1;
if (k == n) k += 1;
else k += T1[b[k+1]] + 1;
}
return ocorrs;
}

O invariante principal simples: no comeo de cada iterao da fase de


busca, o valor de ocorrs o nmero de ocorrncias de a em b[1 . . k1].
ELSEVIER Captulo 13. Busca de palavras em um texto 107

B C B A
X C B A B X C B A A X B C B A B X

1 2 3 4 c ? @ A B C D E F G
B C B A T1[c] 4 4 0 1 2 4 4 4 4

Figura 13.2: Primeiro algoritmo de BoyerMoore aplicado palavra BCBA e


a um texto b. Assinalamos com as nicas posies k em que a palavra
efetivamente comparada com b[k3 . . k]. A parte inferior da gura exibe a
tabela de deslocamentos T1.

Desempenho. O pr-processamento da palavra a consome m unidades


de tempo. Na fase da busca, o consumo de tempo proporcional ao nmero de
comparaes entre elementos de a e b. Tal como no algoritmo trivial, o nmero
de tais comparaes no passa de
mn
no pior caso. Mas o pior caso deste algoritmo mais raro que o do algoritmo
trivial. Assim, em geral, o nmero de comparaes bem menor que mn.

Exerccios
13.3.1 Mostre que a fase de pr-processamento na funo BoyerMoore1 preenche cor-
retamente a tabela T1.
13.3.2 D um exemplo em que a funo BoyerMoore1 faz o maior nmero possvel de
comparaes entre elementos de a e b. Descreva o exemplo com preciso.
13.3.3 Mostre que possvel eliminar o incmodo if (k == n) k += 1; else no c-
digo da funo BoyerMoore1 com o auxlio de uma sentinela postada em b[n+1].
13.3.4 Mostre que a seguinte variante da funo BoyerMoore1 est correta:
int T1[256], i, k, r, ocorrs;
for (i = 0; i < 256; i++) T1[i] = m;
for (i = 1; i < m; i++) T1[a[i]] = m - i;
ocorrs = 0; k = m;
while (k <= n) {
r = 0;
while (m - r >= 1 && a[m-r] == b[k-r]) r += 1;
if (m - r < 1) ocorrs += 1;
k += T1[b[k]]; }
return ocorrs;
108 ALGORITMOS em linguagem C ELSEVIER

13.4 Segundo algoritmo de BoyerMoore


Para introduzir a ideia do segundo algoritmo de BoyerMoore, considere o se-
guinte exemplo. Suponha que a[i . . m] suxo de b[1 . . k]. Suponha tambm
que i 3 e a[i . . m] no suxo de

a[1 . . m1], nem de a[1 . . m2], nem de a[1 . . m3] .

Nestas condies, fcil deduzir que a no suxo de

b[1 . . k+1], nem de b[1 . . k+2], nem de b[1 . . k+3] .

Para generalizar esta ideia, necessrio introduzir uma tabela T2[1 . . m]. Dado
i no intervalo 1 . . m, digamos que um ndice j em 1 . . m1 bom para i se

a[m . . m] = a[j . . j] ,

sendo o menor dentre m i e j 1. (Se = m i ento j m + i 1 e


a[i . . m] = a[jm+i . . j]; caso contrrio, m j + 1 i e a[mj+1 . . m] =
a[1 . . j].) Em outras palavras, j bom para i se

a[i . . m] suxo de a[1 . . j] ou a[1 . . j] suxo de a[i . . m] .

Para cada i no intervalo 1 . . m, dena T2[i] da seguinte maneira: se existe um


ndice bom para i ento

T2[i] o menor t em 1 . . m1 tal que m t bom para i ; (13.3)

seno, T2[i] = m. (No exemplo acima, temos T2[i] 4.) Diremos que T2[i]
o deslocamento correspondente a i. Com essa denio, se a[i . . m] suxo de
b[1 . . k] ento podemos garantir que a no suxo de

1 2 3 4 5 6 i 6 5 4 3 2 1
C A A B A A T2[i] 1 3 6 6 6 6

1 2 3 4 5 6 7 8 i 8 7 6 5 4 3 2 1
B A - B A . B A T2[i] 3 3 6 6 6 6 6 6

1 2 3 4 5 6 7 8 9 10 11 i 11 10 9 8 7 6 5 4 3 2 1
B A - B A * B A * B A T2[i] 3 3 3 3 3 9 9 9 9 9 9

Figura 13.3: Trs exemplos de palavra a[1 . . m] e correspondente tabela de desloca-


mentos T2. A tabela usada pelo segundo algoritmo de BoyerMoore.
ELSEVIER Captulo 13. Busca de palavras em um texto 109

b[1 . . k+1], nem de b[1 . . k+2], . . . , nem de b[1 . . k+T2[i]1] .

Portanto, nossa prxima tentativa deve decidir se a suxo de b[1 . . k+T2[i]].


O segundo algoritmo de BoyerMoore comea por pr-processar a palavra a
para construir a tabela de deslocamentos T2. Em seguida, procura as ocorrncias
de a em b:

/* Recebe uma palavra a[1..m] com 1 <= m <= MAX e um texto


* b[1..n] e devolve o nmero de ocorrncias de a em b. */
int BoyerMoore2 (palavra a, int m, texto b, int n) {
int T2[MAX], i, j, k, r, ocorrs;
/* pr-processamento da palavra a */
for (i = m; i >= 1; i--) {
j = m-1; r = 0;
while (m - r >= i && j - r >= 1)
if (a[m-r] == a[j-r]) r += 1;
else j -= 1, r = 0;
T2[i] = m - j;
}
/* busca da palavra a no texto b */
ocorrs = 0; k = m;
while (k <= n) {
r = 0;
while (m - r >= 1 && a[m-r] == b[k-r]) r += 1;
if (m - r < 1) ocorrs += 1;
if (r == 0) k += 1;
else k += T2[m-r+1];
}
return ocorrs;
}

No comeo de cada iterao da fase de busca, o valor de ocorrs o nmero


de ocorrncias de a em b[1 . . k1].

Desempenho. O pr-processamento da palavra consome m2 unidades de


tempo no pior caso (mas veja o Exerccio 13.4.3). A fase de busca consome mn
unidades de tempo no pior caso. No caso mdio, entretanto, a fase de busca
consome apenas n unidades de tempo.
110 ALGORITMOS em linguagem C ELSEVIER

Exerccios
13.4.1 Calcule a tabela T2 no caso em que a[1] = a[2] = = a[m]. Calcule a tabela
T2 no caso em que os elementos de a[1 . . m] so distintos dois a dois.
13.4.2 Mostre que a fase de pr-processamento na funo BoyerMoore2 preenche cor-
retamente a tabela T2.
13.4.3 Pr-processamento eficiente da palavra. Mostre que o cdigo abaixo
calcula corretamente a tabela T2. Mostre que a execuo do cdigo no consome mais
que m unidades de tempo (e portanto bem mais eciente que o cdigo exibido acima).
i = j = m;
do {
j -= 1; r = 0;
while (j - r >= 1 && a[m-r] == a[j-r])
r += 1;
while (i > m - r)
T2[i--] = m - j;
} while (j - r >= 1);
while (i >= 1)
T2[i--] = m - j;
13.4.4 D um exemplo em que a funo BoyerMoore2 faz o maior nmero possvel de
comparaes entre elementos de a e b. Descreva o exemplo com preciso.

13.5 Terceiro algoritmo de BoyerMoore


O terceiro algoritmo de BoyerMoore uma fuso dos dois anteriores: a cada
passo, o algoritmo escolhe o maior dos deslocamentos ditados pelas tabelas T1
e T2. Infelizmente, mesmo este algoritmo consome mn unidades de tempo no
pior caso. No caso mdio, entretanto, ele muito rpido e consome apenas n
unidades de tempo.
A denio da tabela T2 pode ser aperfeioada de tal maneira que o terceiro
algoritmo consuma apenas m + n unidades de tempo, mesmo no pior caso.

Exerccios
13.5.1 Escreva o cdigo do terceiro algoritmo de BoyerMoore.
13.5.2 Projeto de programao. Implemente e teste o terceiro algoritmo de Boyer
Moore. Faa uma verso que produza uma lista de todas as ocorrncias da palavra no
texto. (Para a fase de testes, escreva uma funo que conra o resultado do algoritmo
de BoyerMoore comparando-o com o resultado do algoritmo trivial.) Compare, na
prtica, o desempenho de sua implementao com o do algoritmo trivial. Invente pares
palavra/texto interessantes para fazer os testes.
ELSEVIER Captulo 13. Busca de palavras em um texto 111

13.5.3 Difcil. Investigue as alteraes que devem ser feitas na denio da tabela
T2 para que o terceiro algoritmo de BoyerMoore faa apenas 3n comparaes entre
elementos da palavra e do texto.
13.5.4 Leia o verbete BoyerMoore string search algorithm na Wikipedia [21]. Leia o
verbete String searching algorithm na Wikipedia.
13.5.5 Veja a pgina de Charras e Lecroq [4], que contm animaes de diversos algo-
ritmos de busca de palavra em texto.
Captulo 14

rvores binrias

As rvores da computao tm a tendncia de crescer para baixo:


a raiz ca no ar enquanto as folhas se enterram no cho.
folclore

Uma rvore binria uma estrutura de dados mais geral que uma lista enca-
deada. Este captulo introduz as operaes mais simples sobre rvores binrias.
O captulo seguinte trata de uma aplicao bsica.

14.1 Denio
fcil transmitir a ideia intuitiva de rvore binria por meio de uma gura
(veja Figura 14.1), mas surpreendentemente difcil dar uma denio precisa
do conceito. Uma rvore binria um conjunto de registros (veja Apndice E)
que satisfaz certas condies, detalhadas adiante. Os registros sero chamados
ns (poderiam tambm ser chamados clulas). Suporemos, por enquanto, que
cada n tem trs campos: um nmero inteiro e dois ponteiros (veja Apndice D)
para ns. Os ns podem, ento, ser denidos assim:

struct cel {
int contedo; contedo
struct cel *esq; 999
struct cel *dir; esq r r dir
};  A
  AAU
typedef struct cel n;
114 ALGORITMOS em linguagem C ELSEVIER

O campo contedo a carga til do n, enquanto os outros dois campos do


estrutura rvore. O campo esq contm o endereo de um n ou NULL. Hiptese
anloga vale para o campo dir. Se o campo esq de um n X o endereo de
um n Y, diremos que Y o lho esquerdo de X. Se X.esq = NULL, ento X no
tem lho esquerdo. Se X.dir = &Y, diremos que Y o lho direito de X. Se Y
lho (esquerdo ou direito) de X, ento X pai de Y. Uma folha um n que
no tem lho algum.
Um ciclo qualquer sequncia (X0 , X1 , . . . , Xk ) de ns tal que Xi+1 lho
de Xi para i = 0, 1, . . . , k1 e X0 lho de Xk . Por exemplo, se X.esq = &X
ento (X) um ciclo. Se X.esq = &Y e Y.dir = &X ento (X, Y) um ciclo.
Podemos agora denir o conceito central do captulo. Uma rvore binria
um conjunto A de ns tal que (1) os lhos de cada elemento de A pertencem
a A, (2) todo elemento de A tem no mximo um pai, (3) um e apenas um
dos elementos de A no tem pai em A, (4) os lhos esquerdo e direito de cada
elemento de A so distintos e (5) no h ciclos em A. (Em geral, o progra-
mador no tem conscincia dos detalhes dessa denio porque as rvores so
construdas n a n de modo a satisfazer as condies naturalmente.) O nico
elemento de A que no tem pai em A chamado raiz da rvore.
Suponha, por exemplo, que P, X, Y e Z so ns distintos, que X lho esquerdo
de P, que Y lho esquerdo de X, que Z lho direito de X e que Y e Z so folhas.
Ento o conjunto {P, X, Y, Z} uma rvore binria. O conjunto {X, Y, Z} tambm
uma rvore binria.

Subrvores. Um caminho em uma rvore binria qualquer sequncia


(Y0 , Y1 , . . . , Yk ) de ns da rvore tal que Yi+1 lho de Yi para i = 0, 1, . . . , k1.
Dizemos que Y0 a origem, Yk o trmino e k o comprimento do caminho.
Um n Z descendente de um n X se existe um caminho com origem X e
trmino Z.
Para todo n X de uma rvore binria, o conjunto formado por X e todos
os seus descendentes uma rvore binria. Dizemos que esta a subrvore
com raiz X. Se P um n, ento P.esq a raiz da subrvore esquerda de P e
P.dir a raiz da subrvore direita de P.

Endereo de uma rvore. O endereo de uma rvore binria o ende-


reo de sua raiz. (O endereo da rvore vazia NULL.) Em discusses informais,
conveniente confundir rvores com seus endereos. Assim, se r o endereo
de uma rvore, podemos dizer r uma rvore e considere a rvore r. Isso
sugere a introduo do nome alternativo rvore para o tipo de dados ponteiro
ELSEVIER Captulo 14. rvores binrias 115

paran:
typedef n *rvore;

Recurso. A seguinte observao coloca em evidncia a natureza recur-


siva das rvores binrias. Para toda rvore binria r, vale uma das seguintes
alternativas:
1. r NULL ou
2. r->esq e r->dir so rvores binrias.
Muitos algoritmos sobre rvores cam mais simples quando escritos em estilo
recursivo.

Exerccios
14.1.1 Dado o endereo x de um n em uma rvore binria, considere a sequncia de
endereos que se obtm pela iterao das atribuies x = x->esq e x = x->dir em
qualquer ordem. Mostre que esta sequncia descreve um caminho.
14.1.2 Mostre que os ns de qualquer caminho em uma rvore binria so distintos
dois a dois.
14.1.3 Sejam X e Z dois ns de uma rvore binria. Mostre que existe no mximo um
caminho com origem X e trmino Z.
14.1.4 Sequncias de parnteses. rvores binrias tm uma relao muito ntima
com certas sequncias bem-formadas de parnteses (veja Seo 6.2). Discuta essa
relao.
14.1.5 Expresses aritmticas. rvores binrias podem ser usadas, de maneira
muito natural, para representar expresses aritmticas (como ((a+b)cd)/(ef )+g,
por exemplo). Discuta os detalhes desta representao.

5 t
@
@
@
Rt
@
3 t 8
J

J

J
J


^
J 

^
J
1 t 4 t 6 t 9 t
A A
A
 U AU
0 t 2 t 7 t

Figura 14.1: Uma rvore binria. Os ns da rvore


esto numerados em ordem e-r-d.
116 ALGORITMOS em linguagem C ELSEVIER

14.2 Varredura esquerda-raiz-direita


Os ns de uma rvore binria podem ser visitados em muitas ordens diferen-
tes. Cada ordem dene uma varredura da rvore. Na varredura e-r-d, ou
esquerda-raiz-direita (inorder traversal ), visitamos
1. a subrvore esquerda da raiz, em ordem e-r-d,
2. depois a raiz,
3. depois a subrvore direita da raiz, em ordem e-r-d.
Eis uma funo recursiva que faz a varredura e-r-d de uma rvore:

/* Recebe uma rvore binria r e imprime o contedo


* de seus ns em ordem e-r-d. */
void Erd (rvore r) {
if (r != NULL) {
Erd (r->esq);
printf ("%d\n", r->contedo);
Erd (r->dir);
}
}

A verso iterativa da funo Erd usa uma pilha (veja Captulo 6) de ns. A
pilha armazenada num vetor p[0..t-1] e h sempre um n x pronto para ser
colocado na pilha. A sequncia de ns p[0], p[1], . . . , p[t-1], x um roteiro do
que ainda precisa ser feito: x representa a instruo imprima a subrvore x e
cada p[i] representa a instruo imprima o n p[i] e em seguida a subrvore
direita de p[i].

/* Recebe uma rvore binria r e imprime o contedo de


* seus ns em ordem e-r-d. Supe que
* a rvore no tem mais que 100 ns. */
void ErdI (rvore r) {
n *p[100], *x;
int t = 0;
x = r;
while (x != NULL || t > 0) {
/* o topo da pilha p[0..t-1] est em t-1 */
if (x != NULL) {
p[t++] = x;
ELSEVIER Captulo 14. rvores binrias 117

x = x->esq;
}
else {
x = p[--t];
printf ("%d\n", x->contedo);
x = x->dir;
}
}
}

As varreduras r-e-d (raiz-esquerda-direita ou preorder traversal ) e e-d-r


(esquerda-direita-raiz ou postorder traversal ) so denidas por analogia com a
varredura e-r-d.

Exerccios
14.2.1 Encontre um n com contedo k em uma rvore binria.

5
3 5
1 3 5
0 1 3 5
N 0 1 3 5
0 N 1 3 5
0 1 2 3 5
0 1 N 2 3 5
0 1 2 N 3 5
0 1 2 3 4 5
0 1 2 3 N 4 5
0 1 2 3 4 N 5
0 1 2 3 4 5 8
0 1 2 3 4 5 6 8
0 1 2 3 4 5 N 6 8
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 N 7 8
0 1 2 3 4 5 6 7 N 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 N 9
0 1 2 3 4 5 6 7 8 9 N
Figura 14.2: Funo ErdI aplicada rvore binria da Figura 14.1. Para simplicar,
confundimos o contedo de cada n com o seu endereo. Cada linha da tabela resume
o estado de coisas no incio de uma iterao: esquerda esto os ns que j foram
impressos; direita est a pilha x, p[t-1], . . . , p[0]. A letra N representa NULL.
118 ALGORITMOS em linguagem C ELSEVIER

14.2.2 Calcule o nmero de ns de uma rvore binria.


14.2.3 Imprima as folhas de uma rvore binria em ordem e-r-d.
14.2.4 Verique que o cdigo abaixo equivalente ao da funo ErdI:
while (1) {
while (x != NULL) {
p[t++] = x;
x = x->esq; }
if (t == 0) break;
x = p[--t];
printf ("%d\n", x->contedo);
x = x->dir; }
14.2.5 Escreva uma funo que faa a varredura r-e-d de uma rvore binria. Escreva
uma funo que faa a varredura e-d-r de uma rvore binria.
14.2.6 Escreva uma funo que receba uma rvore binria no vazia e devolva o ende-
reo do primeiro n da rvore na ordem e-r-d. Faa duas verses: uma iterativa e uma
recursiva. Repita o exerccio com ltimo no lugar de primeiro.
14.2.7 Expresses aritmticas. Discuta a relao entre a varredura e-r-d e a no-
tao inxa de expresses aritmticas. Discuta a relao entre a varredura e-d-r e a
notao posxa. (Veja Seo 6.3 e Exerccio 14.1.5.)

14.3 Altura
A altura de um n em uma rvore binria a distncia entre o n e o seu des-
cendente mais afastado. Mais precisamente, a altura de um n o comprimento
do mais longo caminho que leva do n at uma folha.
A altura de uma rvore a altura de sua raiz. Por exemplo, uma rvore

H
t
@
@
@

D t
R
@
K t

J
J

J
J


^
J t

^t
J
B t F t J L
A A 
t
AU t t AUt t

A C E G I

Figura 14.3: rvore binria quase completa. (A ordem alfabtica dos


ns descreve uma varredura e-r-d.) A altura da rvore log2 12.
ELSEVIER Captulo 14. rvores binrias 119

com um nico n tem altura 0 e a rvore da Figura 14.3 tem altura 3. A altura
de uma rvore binria com n ns ca entre log2 n e n: se h a altura da rvore
ento
log2 n h < n .
Uma rvore binria de altura n 1 um tronco sem galhos: cada n tem
no mximo um lho. Uma rvore binria de altura log2 n completa ou
quase completa: todos os nveis esto lotados exceto talvez o ltimo. (Veja
Exerccio 1.2.4.)
Eis como a altura de uma rvore binria pode ser calculada:

/* Devolve a altura da rvore binria r. */


int Altura (rvore r) {
if (r == NULL)
return -1; /* a altura de uma rvore vazia -1 */
else {
int he = Altura (r->esq);
int hd = Altura (r->dir);
if (he < hd ) return hd + 1;
else return he + 1;
}
}

rvores balanceadas. Uma rvore binria balanceada se as subr-


vores esquerda e direita de cada n tiverem aproximadamente a mesma altura.
Uma rvore binria balanceada com n ns tem altura prxima de log2 n.
Muitos algoritmos sobre rvores binrias consomem tempo proporcional
altura da rvore. Por isso, convm trabalhar com rvores balanceadas. Mas
difcil manter o balanceamento se a rvore sofre insero e remoo de ns ao
longo da execuo do algoritmo.

Exerccios
14.3.1 Desenhe uma rvore binria com 17 ns que tenha a menor altura possvel.
14.3.2 Escreva uma funo iterativa que calcule a altura de uma rvore binria.
14.3.3 rvores AVL. Uma rvore balanceada no sentido AVL se, para cada n x,
as alturas das subrvores esquerda e direita de x diferem em no mximo uma unidade.
Escreva uma funo que decida se uma dada rvore balanceada no sentido AVL.
Procure escrever sua funo de modo que ela visite cada n no mximo uma vez.
120 ALGORITMOS em linguagem C ELSEVIER

14.4 Ns com campo pai


Em algumas aplicaes (veja seo seguinte, por exemplo) conveniente ter
acesso imediato ao pai de qualquer n. Para isso, preciso acrescentar um
campo pai a cada n:

struct cel {
int contedo;
struct cel *pai;
struct cel *esq;
struct cel *dir;
};
typedef struct cel n;

um bom exerccio escrever uma funo que preencha o campo pai de todos
os ns de uma rvore binria.

Exerccios
14.4.1 Escreva uma funo que preencha corretamente todos os campos pai de uma
rvore binria.
14.4.2 A profundidade de um n em uma rvore binria a distncia entre o n e
a raiz da rvore. Mais precisamente, a profundidade de um n X o comprimento
do (nico) caminho que vai da raiz at X. Por exemplo, a profundidade da raiz 0
e a profundidade de qualquer lho da raiz 1. Escreva uma funo que determine a
profundidade de um n dado.
14.4.3 verdade que uma rvore binria balanceada se e somente se todas as suas
folhas tm aproximadamente a mesma profundidade?
14.4.4 Escreva uma funo que imprima o contedo de cada n de uma rvore binria
precedido de um recuo em relao margem esquerda do papel. Esse recuo deve ser
proporcional profundidade do n. Veja Figura 14.4.
14.4.5 Heap. Em que condies uma rvore binria pode ser considerada um heap
(veja Seo 10.1)? Escreva uma funo que transforme um max-heap em uma rvore
binria quase completa. Escreva uma verso da funo SacodeHeap (Seo 10.3) para
um max-heap representado por uma rvore binria.

14.5 N seguinte
Suponha que x o endereo de um n de uma rvore binria. Queremos calcular
o endereo do n seguinte na ordem e-r-d. Para resolver o problema, necessrio
ELSEVIER Captulo 14. rvores binrias 121

555
333
111
- 555
- s
S
444  S
- /

s Sws
S
- 333 888
A A
888  A A
- s

 AU
A s AAU s
999
111 444 999
-
-
Figura 14.4: O lado esquerdo da gura uma representao da rvore binria que
est direita. O nmero de espaos que precede o contedo de cada n proporcional
profundidade do n. Os caracteres - representam NULL. Veja Exerccio 14.4.4.

que os ns tenham um campo pai, conforme a seo anterior. A funo abaixo


devolve o endereo do n seguinte a x ou devolve NULL se x o ltimo n.
(s vezes convm confundir, a ttulo de atalho verbal, um n com o seu en-
dereo. Na documentao da funo abaixo, por exemplo, a expresso recebe
um n x deve ser entendida como recebe o endereo x de um n. Analoga-
mente, a expresso devolve o n seguinte deve ser entendida como devolve o
endereo do n seguinte.)

/* Recebe um n x de uma rvore binria cujos ns tm


* campo pai e devolve o n seguinte na ordem e-r-d.
* A funo supe que x != NULL. */
n *Seguinte (n *x) {
if (x->dir != NULL) {
n *y = x->dir;
while (y->esq != NULL) y = y->esq;
return y; /* 1 */
}
while (x->pai != NULL && x->pai->dir == x) /* 2 */1
x = x->pai; /* 3 */
return x->pai;
}

1
A expresso x->pai->dir equivale a (x->pai)->dir, conforme o Seo J.5.
122 ALGORITMOS em linguagem C ELSEVIER

Na linha 1 da funo Seguinte, y o primeiro n, na ordem e-r-d, da


subrvore direita de x. As linhas 2 e 3 fazem com que x suba na rvore enquanto
for lho direito de algum.

Exerccios
14.5.1 Escreva uma funo que receba um n x de uma rvore binria e encontre o n
anterior a x na ordem e-r-d.
14.5.2 Escreva uma funo que faa varredura e-r-d de uma rvore binria usando a
funo Seguinte e a funo sugerida no Exerccio 14.2.6.
14.5.3 Leia o verbete Binary tree na Wikipedia [21].
Captulo 15

rvores binrias de busca

Assim como as rvores binrias so uma generalizao das listas encadeadas,


as rvores binrias de busca (ou search trees) so uma generalizao das listas
encadeadas crescentes.

15.1 Denio
Considere uma rvore binria cujos ns tm um campo chave de um tipo li-
nearmente ordenado, como int ou string, por exemplo. Podemos supor que os
ns da rvore tm a seguinte estrutura:

struct cel {
int chave;
int contedo;
struct cel *esq;
struct cel *dir;
};
typedef struct cel n;

(veja Seo 14.1). Uma rvore binria deste tipo de busca (em relao ao
campo chave) se cada n X tem a seguinte propriedade: a chave de X
1. maior ou igual chave de qualquer n na subrvore esquerda de X e
2. menor ou igual chave de qualquer n na subrvore direita de X.
Em outras palavras, para todo n X, todo n E na subrvore esquerda de X e
todo n D na subrvore direita de X, tem-se

E.chave X.chave D.chave .


124 ALGORITMOS em linguagem C ELSEVIER

Esta propriedade equivale seguinte: a varredura da rvore em ordem e-r-d


(veja Seo 14.2) v as chaves em ordem crescente.
Examinaremos abaixo os problemas de busca, remoo e insero em rvores
de busca. Para estudar esses problemas, convm denir o tipo de dados rvore
(conforme Seo 14.1):

typedef n *rvore;

Exerccios
15.1.1 Escreva uma funo que decida se uma dada rvore binria ou no de busca.
15.1.2 Suponha que X.esq->chave X.chave X.dir->chave para cada n X de
uma rvore binria.1 Esta rvore de busca?

15.2 Busca
Dada uma rvore de busca, queremos encontrar um n cuja chave tenha um
certo valor. Eis uma funo recursiva que devolve (o endereo de) um n cuja
chave vale k:

/* Recebe k e uma rvore de busca r. Devolve um n cuja


* chave k ou devolve NULL se tal n no existe. */
n *Busca (rvore r, int k) {
if (r == NULL || r->chave == k)
return r;
if (r->chave > k)
return Busca (r->esq, k);
else
return Busca (r->dir, k);
}

No pior caso, a funo consome tempo proporcional altura da rvore (veja


Seo 14.3). Se a rvore for balanceada, o consumo ser proporcional a log2 n,
sendo n o nmero de ns.
Eis uma verso iterativa da funo Busca:
1
A expresso X.esq->chave equivale a (X.esq)->chave, conforme o Seo J.5.
ELSEVIER Captulo 15. rvores binrias de busca 125

while (r != NULL && r->chave != k) {


if (r->chave > k) r = r->esq;
else r = r->dir;
}
return r;

Exerccios
15.2.1 Escreva uma funo que encontre uma chave mnima em uma rvore de busca.
Escreva uma funo que encontre uma chave mxima.
15.2.2 Suponha que as chaves de nossa rvore de busca so distintas duas a duas.
Escreva uma funo que receba uma chave k e devolva a chave seguinte na ordem
crescente.
15.2.3 Escreva uma funo que transforme um vetor crescente em uma rvore de busca
balanceada (veja Seo 14.3).
15.2.4 Escreva uma funo que transforme uma rvore de busca em um vetor crescente.
15.2.5 Busca binria. H uma relao muito ntima entre rvores de busca e o
algoritmo de busca binria num vetor (veja Captulo 7). Qual , exatamente, esta
relao?

15.3 Insero
Considere o problema de inserir um novo n em uma rvore de busca de tal
maneira que a rvore resultante continue sendo de busca. Podemos supor que
o novo n criado antes que a funo de insero seja invocada:

n *novo;
novo = malloc (sizeof (n));
novo->chave = k;
novo->esq = novo->dir = NULL;

O novo n ser uma folha da rvore. A raiz da nova rvore ser a mesma da
rvore original, a menos que a rvore original seja vazia.

/* Recebe uma rvore de busca r e uma folha avulsa novo.


* Insere novo na rvore de modo que a rvore continue
* sendo de busca e devolve o endereo da nova rvore. */
126 ALGORITMOS em linguagem C ELSEVIER

rvore Insere (rvore r, n *novo) {


n *f, *p;
if (r == NULL) return novo;
f = r;
while (f != NULL) {
p = f;
if (f->chave > novo->chave) f = f->esq;
else f = f->dir;
}
if (p->chave > novo->chave) p->esq = novo;
else p->dir = novo;
return r;
}

Exerccios
15.3.1 Critique a elegncia do cdigo da funo Insere. Escreva uma verso mais
elegante. Sugesto: use um ponteiroparaponteiro, ou seja, um objeto do tipo n **
ou rvore *.
15.3.2 Escreva uma verso recursiva da funo Insere.

15.4 Remoo
Considere o problema de remover um n de uma rvore de busca de tal forma
que a rvore resultante continue sendo de busca. Convm tratar, em primeiro
lugar, da remoo da raiz da rvore. Se a raiz tiver apenas um lho, ele assume
o papel de raiz. Seno, basta fazer com que o n anterior raiz na ordem e-r-d
(veja Exerccio 14.5.1) assuma o papel de raiz.

/* Recebe uma rvore no vazia r, remove a raiz da rvore


* e rearranja a rvore de modo que ela continue sendo
* de busca. Devolve o endereo da nova raiz. */
rvore RemoveRaiz (rvore r) {
n *p, *q;
if (r->esq == NULL) q = r->dir;
ELSEVIER Captulo 15. rvores binrias de busca 127

else {
p = r; q = r->esq;
while (q->dir != NULL) {
p = q; q = q->dir;
}
/* q o n anterior a r na ordem e-r-d */
/* p o pai de q */
if (p != r) {
p->dir = q->esq;
q->esq = r->esq;
}
q->dir = r->dir;
}
free (r);
return q;
}

Agora podemos tratar do caso em que o n a ser removido no a raiz da


rvore. Para remover o lho esquerdo de um n x, basta fazer
x->esq = RemoveRaiz (x->esq);
e para remover o lho direito de x, basta fazer
x->dir = RemoveRaiz (x->dir);

Exerccios
15.4.1 Suponha que ns com chaves 50, 30, 70, 20, 40, 60, 80, 15, 25, 35, 45, 36 so inse-
ridos, nesta ordem, numa rvore de busca inicialmente vazia. Desenhe a rvore que
resulta. Em seguida, remova o n que tem chave 30 de modo que a rvore continue
sendo de busca.
15.4.2 Critique a elegncia do cdigo da funo RemoveRaiz. Tente escrever uma verso
mais elegante.
15.4.3 Escreva uma verso recursiva da funo RemoveRaiz.

15.5 Desempenho dos algoritmos


O consumo de tempo de qualquer dos trs algoritmos busca, insero e remo-
o , no pior caso, proporcional altura da rvore. Segue da que convm
128 ALGORITMOS em linguagem C ELSEVIER

r q
t t
@ @
11 @
2 t
R t12 10 @
2 t
R t12
@ @
@
1 t
R
4 t
4@
1 t
Rt
@ @
@
3 t
R
6 tp
6@
3 t
R tp
@ @
5 t
10@R tq 5 t
@ R tf
@
f t 8 @
7 t
R t9
@
8 @
7 t
R t9

Figura 15.1: Aplicao do algoritmo RemoveRaiz rvore representada esquerda.


(Os ns esto numerados em ordem e-r-d.) O n q anterior a r na ordem e-r-d. O n
p o pai de q e o n f o (nico) lho de q. O algoritmo faz com que f seja o lho
direito de p e coloca o n q no lugar de r (os lhos de r passaro a ser os lhos de q).
A rvore resultante est representada direita.

trabalhar com rvores balanceadas (veja Seo 14.3). Essas rvores tm altura
prxima de log2 n, sendo n o nmero de ns.
Infelizmente, os algoritmos de insero e remoo descritos neste captulo
no produzem rvores balanceadas: se a funo Insere for repetidamente apli-
cada a uma rvore balanceada, o resultado pode ser uma rvore bastante desba-
lanceada; algo anlogo pode acontecer depois de uma sequncia de invocaes
da funo RemoveRaiz. Para enfrentar isso, preciso inventar algoritmos que
faam um rebalanceamento da rvore aps cada insero e cada remoo. Veja,
por exemplo, os livros de Sedgewick [18] e Cormen et al. [5].

Exerccio
15.5.1 Leia o verbete Binary search tree na Wikipedia [21].
Apndice A

Leiaute

Programming is best regarded as the process of creating works of literature,


which are meant to be read.
D. E. Knuth, Literate Programming

Any fool can write code that a computer can understand.


Good programmers write code that humans can understand.
M. Fowler, Refactoring: Improving the Design of Existing Code

Programas precisam ser compreendidos no s por computadores mas tambm


por seres humanos. Embora ignorado pelo computador, o leiaute de um pro-
grama a disposio do cdigo na folha de papel muito importante para
o leitor humano. Dois aspectos do leiaute so fundamentais:
os espaos entre as palavras e smbolos em uma linha de cdigo;
a indentao de cada linha do cdigo (produzida pelos espaos
em branco no incio da linha).
Embora tratemos aqui do leiaute de programas em linguagem C, as recomen-
daes podem ser aplicadas a muitas outras linguagens de programao.

A.1 Um bom leiaute


Segue uma amostra de bom leiaute. Observe a indentao, o uso correto dos
espaos e a posio dos caracteres { e }.
130 ALGORITMOS em linguagem C ELSEVIER

int Funcao (int n, int v[]) {


int i, j;
i = 0;
while (i < n) {
if (v[i] != 0)
i = i + 1;
else {
for (j = i + 1; j < n; j++)
v[j-1] = v[j];
n = n - 1;
}
}
return n;
}

Quando as circunstncias exigem economia de espao, podemos recorrer ao


leiaute compacto abaixo. A indentao deixa clara a estrutura do cdigo sem
que o leitor tenha que tropear nas chaves { e }.

int Funcao (int n, int v[]) {


int i, j;
i = 0;
while (i < n) {
if (v[i] != 0) i = i + 1;
else {
for (j = i + 1; j < n; j++) v[j-1] = v[j];
n = n - 1; } }
return n; }

fcil habituar-se a produzir um bom leiaute. Com um pouco de prtica,


os dedos do programador, danando sobre o teclado, passaro a fazer a coisa
certa de maneira autnoma, deixando a mente livre para cuidar de assuntos
mais importantes.

Exerccio
A.1.1 Critique a tipograa do seguinte cdigo:
int Funcao (int n, int v[]) {
int i, j;
i = 0;
while (i < n) {
ELSEVIER Apndice A. Leiaute 131

if (v[i] != 0) i = i + 1;
else {
for (j = i + 1; j < n; j++) v[j-1] = v[j];
n = n - 1; } }
return n; }

A.2 Mau exemplo


O leiaute do exemplo abaixo pssimo. Ele inconsistente (no faz as coisas
sempre da mesma maneira), deixa espaos onde no deve e omite os espaos
onde eles so importantes. (Os espaos no cdigo so to importantes quanto
as pausas na msica!)
int Funcao ( int n,int v[] ){
int i,j;
i=0;
while(i<n){
if(v[i] !=0)
i= i +1;
else
{
for (j=i+1;j<n;j++)
v[j-1]=v[j];
n =n- 1;
}
}
return n;
}

A.3 Sugestes
Para produzir um bom leiaute, no necessrio inventar nada novo. Basta usar
as regras tipogrcas adotadas por todos os bons livros, revistas e jornais. Eis
algumas destas regras:

1. use um espao para separar uma palavra da palavra seguinte


(os smbolos =, <=, while, if, for etc. contam como palavras);
2. deixe um espao depois, mas no antes, de cada sinal de pontuao;
3. deixe um espao depois, mas no antes, de fechar um parntese;
4. deixe um espao antes, mas no depois, de abrir um parntese.
132 ALGORITMOS em linguagem C ELSEVIER

A expresso while(j < n), escrita como est, tem o desagradvel sabor de
enquantoj for menor que n . Portanto, no escreva
while(j < n) no lugar de while (j < n),
else{ no lugar de else {,
for (i=1;i<n;i++) no lugar de for (i = 1; i < n; i++),
e assim por diante. H algumas excees notrias a essas regras: escreva
x->seg e no x -> seg,
x[i] e no x [i],
x++ e no x ++.
Tambm razovel suprimir o espao entre o nome de uma funo e o abre-
parntese seguinte. Por exemplo, usual escrever Funcao(9, v) em lugar
de Funcao (9, v). Mas isso no se aplica aos operadores while, for, if,
return, sizeof etc., que no so funes.

Exerccios
A.3.1 Para cada um dos pares de linhas abaixo, diga qual das duas linhas tem o melhor
leiaute.
para j variando de 1 at n de 1 em 1, faa
paraj variando de1 at n de 1em1,faa
for (j = 0; j < n; j++) {
for(j = 0; j < n; j++) {
for (j = 0; j < n; j++){
for (j = 0; j < n; j++) {
A.3.2 Corrija os erros de leiaute do texto abaixo.
Em 1959 e nas dcadas seguintes nenhum programador Cobol
poderia imaginar que os programas de computador que estava
criando ainda estariam em operao no fim do sculo.Poucos se
lembram hoje de que os primeiros PCs possuam apenas 64Kbytes
de memria.Como a quantidade de memria disponvel era
pequena,usavam-se muitos truques para economizar esse recurso.
Para representar o ano,armazenava-se(por exemplo )"85"em vez
de"1985". com a chegada do ano 2000 , essa codificao
econmica transformou-se em um erro em potencial .
A.3.3 Reescreva o cdigo abaixo com leiaute decente.
int separa(int v[],int p,int r){int c=v[p],i=p+1,j=r,t;
while(i<=j){if(v[i]<=c){v[i-1]=v[i];++i;}else{t=v[i];
v[i]=v[j];v[j]=t;--j;}}v[j]=c;return j;}
ELSEVIER Apndice A. Leiaute 133

A.3.4 Reescreva o fragmento de programa abaixo usando leiaute decente.


esq= 0; dir=N-1;
i=(esq+dir)/2; /*indice do "meio"de R[]*/
while(esq<= dir && R[i] != X){
if(R[i]<X) esq = i+1;
else dir = i-1; /* novo indice do "meio"de R[] */
i= (esq + dir)/2;
}
A.3.5 Aprenda a usar o programa indent, que ajuda a produzir um bom leiaute. O
programa est presente em todo sistema UNIX e GNU/Linux.
A.3.6 Leia o verbete Programming style na Wikipedia [21].
A.3.7 Familiarize-se com as recomendaes do GNU Coding Standards, www.gnu.org/
prep/standards/html_node/index.html.

A.4 Cdigo enfeitado


Neste livro, o cdigo de funes precisa, muitas vezes, ser discutido no texto
adjacente. Em tais discusses, conveniente e apropriado escrever os nomes
das variveis em fonte itlica. Se as mesmas variveis forem escritas em fonte
monoespaada dentro do cdigo, teremos margem para confuso. Para evitar
esse problema, o livro toma a liberdade, ocasionalmente, de usar fonte itlica
dentro do cdigo C. Veja um exemplo:

int Funo (int n, int v[]) {


int i, j;
i = 0;
while (i < n) {
if (v[i] != 0)
i = i + 1;
else {
for (j = i + 1; j < n; j++)
v[j-1] = v[j];
n = n - 1;
}
}
return n;
}

O livro tambm toma a liberdade de usar letras acentuadas em nomes de


funes e de variveis, embora o alfabeto da linguagem C no tenha tais letras.
134 ALGORITMOS em linguagem C ELSEVIER

Supe-se que palavras como Funo sero substitudas por Funcao, antes
que o cdigo seja submetido ao compilador C.

Exerccio
A.4.1 Familiarize-se, ainda que supercialmente, com o sistema CWEB de Knuth e
Levy [11]. Trata-se de uma sosticada ferramenta que ajuda a escrever cdigo C
integrado com a correspondente documentao. Amostras de programas escritos em
CWEB podem ser vistas em www.ime.usp.br/~pf/CWEB/exemplo1/tex/mdp.pdf e www.
ime.usp.br/~pf/CWEB/exemplo2/tex/isort.pdf.
Apndice B

Caracteres

H dois tipos de caracteres em C: sem sinal e com sinal. Um caractere


sem sinal (ou unsigned character ) nada mais que um nmero do conjunto
0, 1, 2, . . . , 254, 255, enquanto um caractere com sinal (ou signed character )
um nmero do conjunto 128, 127, . . . , 126, 127. A distino entre os dois
tipos , em geral, irrelevante. (Os dois tipos so apenas duas interpretaes
diferentes do conjunto de todas as sequncias de 8 bits.) Cada caractere (de
qualquer dos dois tipos) armazenado em um byte (veja Apndice C) na me-
mria do computador. Para criar uma varivel u do primeiro tipo, diga
unsigned char u;
Para criar uma varivel c do segundo tipo, diga
char c;

B.1 Representao grca dos caracteres


Quando um caractere exibido na impressora ou na tela do monitor, ele
representado por um smbolo grco. No intervalo 0 . . 127, os dois tipos de
caracteres tm os mesmos smbolos grcos. O smbolo do caractere 65, por
exemplo,
A
e o smbolo do caractere 66 B . Alguns caracteres tm smbolos grcos
especiais. Por exemplo, o caractere 32 representado por um espao, o carac-
tere 0 tem representao vazia (no ocupa espao algum), e o caractere 10
representado por uma mudana de linha.
Os smbolos grcos e os efeitos especiais dos caracteres 0 a 127 foram
estabelecidos pelo American Standard Code for Information Interchange. A
136 ALGORITMOS em linguagem C ELSEVIER

32 54 6 76 L 98 b 120 x 199 231


33 ! 55 7 77 M 99 c 121 y 200 232
34 " 56 8 78 N 100 d 122 z 201 233
35 # 57 9 79 O 101 e 123 { 202 234
36 $ 58 : 80 P 102 f 124 | 203 235
37 % 59 ; 81 Q 103 g 125 } 204 236
38 & 60 < 82 R 104 h 126 205 237
39 61 = 83 S 105 i 161 209 241
40 ( 62 > 84 T 106 j 166 210 242
41 ) 63 ? 85 U 107 k 167 211 243
42 * 64 @ 86 V 108 l 170 212 244
43 + 65 A 87 W 109 m 186 213 245
44 , 66 B 88 X 110 n 188 214 246
45 - 67 C 89 Y 111 o 189 217 249
46 . 68 D 90 Z 112 p 190 218 250
47 / 69 E 91 [ 113 q 191 220 252
48 0 70 F 92 \ 114 r 192 224 255
49 1 71 G 93 ] 115 s 193 225
50 2 72 H 94 116 t 194 226
51 3 73 I 95 _ 117 u 195 227
52 4 74 J 96 118 v 196 228
53 5 75 K 97 a 119 w 198 230

Figura B.1: Amostra da tabela de caracteres ISO 8859-1. A gura mostra os smbolos
grcos da maioria dos caracteres do tipo unsigned char. Esto ausentes os caracteres
especiais 0, . . . , 31 (veja Figura B.3) e diversos outros, irrelevantes no nosso contexto.

correspondncia conhecida como tabela ascii. Os smbolos dos caracteres


sem sinal 128 a 255 e os dos caracteres com sinal 128 a 1 no esto bem
padronizados: cada sistema escolhe a tabela que mais lhe agrada. Uma das
tabelas mais difundidas a ISO 8859-1, tambm conhecida como ISO Latin1
(veja Figura B.1). Nesta tabela, cada char negativo c tem o mesmo smbolo
grco que o unsigned char c + 256 (veja Figura B.2).
Quando os caracteres do tipo char so colocados em ordem crescente, as
letras acentuadas (como ) precedem as letras no acentuadas (como a). O
contrrio acontece na sequncia crescente dos unsigned char. Esta a nica
diferena relevante entre unsigned char e char.

Exerccios
B.1.1 Escreva um fragmento de cdigo que receba dois caracteres (sem sinal) via teclado
e diga se o primeiro vem antes ou depois do segundo na tabela ISO 8859-1.
ELSEVIER Apndice B. Caracteres 137

char 0 1 127 128 127 126 2 1


unsigned char 0 1 127 128 129 130 254 255

Figura B.2: Caracteres com sinal e caracteres sem sinal. Os caracteres que esto na
mesma coluna da tabela tm o mesmo smbolo grco. Cada char negativo c tem o
mesmo smbolo grco que o unsigned char c + 256.

B.1.2 Escreva um programa que exiba na tela do monitor os smbolos grcos dos
caracteres (sem sinal) 32 a 255.

B.2 Constantes e brancos


No cmodo usar expresses como 97 e -29 para representar constantes
do tipo char dentro de um programa C:

char c, d, e;
c = 97; d = -29; e = 48;

muito mais cmodo escrever o smbolo grco do caractere entre aspas simples:
c = a; d = ; e = 0;
Quanto aos caracteres que produzem efeitos especiais, usa-se uma representao
especial que comea com uma barra invertida. Por exemplo, \n o mesmo
que 10, e \0 o mesmo que 0 (veja Figura B.3).

char constante C smbolo grco


0 \0 caractere nulo (nul )
9 \t tabulao horizontal (tab)
10 \n mudana de linha (newline)
11 \v tabulao vertical (vertical tab)
12 \f quebra de pgina (form feed )
13 \r carriage return
32 espao
39 \
48 0 0
65 A A
79 O O
97 a a

Figura B.3: Representao e efeito de algumas constantes do tipo char.


138 ALGORITMOS em linguagem C ELSEVIER

x = A x = 65 CHAR=A INT=65
x = 0 x = 48 CHAR=0 INT=48
x = x = -29 CHAR= INT=-29
x = \0 x=0 CHAR= INT=0
x = \n x = 10 CHAR=
INT=10
Figura B.4: Um char pode ser convertido num int e vice-versa. Em todos os exemplos
acima, x tanto pode ser uma varivel do tipo char quanto uma do tipo int. Em cada
linha, as duas atribuies do lado esquerdo tm exatamente o mesmo efeito. O lado
direito mostra o resultado do comando printf ("CHAR=%c INT=%d", x, x), que
imprime x nos formatos %c e %d.

Os caracteres 9, 10, 11, 12, 13 e 32 so conhecidos como brancos (ou white-


spaces). Muitas funes das bibliotecas da linguagem C tratam todos os brancos
como se fossem . o caso, por exemplo, da funo scanf (veja Seo H.1).

Exerccios
B.2.1 Qual a diferena entre O, 0 e \0?
B.2.2 Interprete os elementos do vetor 70 65 67 73 76 32 67 79 77 79 32 50 43 50 46
como caracteres. Qual o resultado?
B.2.3 Familiarize-se com a funo isspace, denida na biblioteca ctype. Ela recebe
um caractere sem sinal c e devolve um inteiro no nulo ou 0 conforme c seja ou no um
white-space. (Na verdade, o argumento de isspace um int cujo valor deve pertencer
ao conjunto 0, 1, . . . , 255 ou ser igual constante EOF discutida no Seo H.3.)
B.2.4 Familiarize-se com o programa od (o nome uma abreviatura de octal dump),
que recebe um arquivo e imprime o smbolo grco e o valor numrico de cada caractere
do arquivo. Este utilitrio est presente em todo sistema UNIX e GNU/Linux.

B.3 Operaes aritmticas


As operaes aritmticas envolvendo variveis do tipo unsigned char e char so
executadas em aritmtica int (veja Seo C.3) e no em aritmtica mdulo 256,
como algum poderia supor. Assim, por exemplo, se as variveis u e v so do
tipo unsigned char e valem 255 e 2 respectivamente, o valor da expresso u+v
257.
J as atribuies de um inteiro a um caractere so feitas mdulo 256. Assim,
ELSEVIER Apndice B. Caracteres 139

se u uma varivel do tipo unsigned char e c uma varivel do tipo char ento
depois de
u = 256;
c = 130;
o valor de u ser 0 e o valor de c ser 126. Por isso, os processos iterativos
abaixo entram em loop (nunca terminam):
unsigned char u;
for (u = 0; u < 256; u++) printf (".");
char c;
for (c = 0; c < 128; c++) printf (".");
J os seguintes fragmentos de cdigo imprimem todas as letras minsculas, como
seria de se esperar:
char c;
for (c = a; c <= z; c += 1) printf ("%c\n", c);
int i;
for (i = 1; i < 26; i++) printf ("%c\n", a + i - 1);
Apndice C

Nmeros: naturais e inteiros

A memria de qualquer computador uma sequncia de bytes. Cada byte


consiste em 8 bits e portanto tem 256 possveis valores: 00000000, 00000001, . . . ,
11111110, 11111111. Este apndice procura mostrar como os nmeros naturais
(0, 1, 2, 3 etc.) e os nmeros inteiros (positivos e negativos) so representados
por sequncias de bytes na memria do computador.
O ponto de partida da representao a notao binria: cada sequncia
de bits representa o resultado da soma das potncias de 2 que correspondem
aos bits 1. Por exemplo, a sequncia 010011 representa o nmero 19, pois
025 + 124 + 023 + 022 + 121 + 120 = 19. O conjunto de todas as
sequncias de k bits representa os nmeros naturais de 0 a 2k 1.

C.1 Representao de nmeros naturais


Na linguagem C, os nmeros naturais so conhecidos como inteiros sem sinal.
Para declarar uma varivel n deste tipo, diga
unsigned int n;
Um unsigned int armazenado em s bytes consecutivos, sendo s o valor da
expresso sizeof (unsigned int). Portanto, um unsigned int representado
por 8s bits e assim pode assumir 28s valores, a saber, 0, 1, . . . , 28s 1. Em
alguns computadores, s vale 2 e portanto um unsigned int tem 216 valores, de
0 a 65535. Em outros computadores, s vale 4 e assim um unsigned int tem 232
valores, que representam os nmeros 0 a 4294967295.
Nmeros naturais maiores que 28s 1 so representados mdulo 28s . Se
s = 2, por exemplo, os nmeros 65536 e 65537 so representados por 0 e 1
respectivamente. Em geral, o programador ignora esse efeito pois trabalha com
nmeros muito menores que 28s .
142 ALGORITMOS em linguagem C ELSEVIER

unsigned int bits


0 0000
1 0001
2 0010
3 0011 15 0 1
4 0100 14 2
5 0101
13 3
6 0110
7 0111 12 4
8 1000
11 5
9 1001
10 1010 10 6
11 1011 9 8 7
12 1100
13 1101
14 1110
15 1111

Figura C.1: Imagine um computador em que um unsigned int ocupa 1 byte e cada
byte tem apenas 4 bits. Os possveis valores de um unsigned int so 0, . . . , 15. A
correspondncia entre estes nmeros e as sequncias de 4 bits dada na tabela. A
tabela se fecha num ciclo. Assim, por exemplo, 16 e 17 so representados por 0 e 1.

Exerccios
C.1.1 Qual o efeito do cdigo abaixo se cada unsigned int representado por 16 bits?
unsigned int n;
for (n = 0; n < 65536; n++) x[n] = 0;
C.1.2 Queremos contar o nmero de ocorrncias de um fenmeno que no acontece mais
que 65535 vezes. Podemos usar uma varivel do tipo unsigned int para a contagem?
Suponha agora que o fenmeno ocorre mais que 65535 vezes e proponha um contador
em base 100 (ou base 256, ou base 65536) implementado em uma lista encadeada.
C.1.3 Projeto de programao. Escreva um programa que receba um nmero
natural n e imprima as potncias n2 , n3 , n4 , n5 etc. O programa s deve parar quando
no for possvel armazenar uma potncia em um unsigned int.

C.2 Representao de nmeros inteiros


Nmeros inteiros (positivos e negativos) so conhecidos em C como inteiros
com sinal. Para criar uma varivel i deste tipo, basta dizer
int i;
ELSEVIER Apndice C. Nmeros: naturais e inteiros 143

unsigned int int bits


0 +0 0000
1 +1 0001
2 +2 0010
3 +3 0011 1 +0 +1
4 +4 0100 2 +2
5 +5 0101
3 +3
6 +6 0110
7 +7 0111 4 +4
8 8 1000
5 +5
9 7 1001
10 6 1010 6 +6
11 5 1011 7 8 +7
12 4 1100
13 3 1101
14 2 1110
15 1 1111

Figura C.2: Imagine que cada unsigned int e cada int ocupa apenas 1 byte e que
cada byte tem apenas 4 bits. Os possveis valores de um unsigned int so 0, 1, . . . , 15
e os possveis valores de um int so 8, 7, . . . , 6, 7. A correspondncia entre estes
nmeros e as sequncias de 4 bits dada na tabela. Os inteiros positivos so represen-
tados em notao binria. Os inteiros negativos usam a notao complemento-de-dois:
i representado pelo inteiro positivo 24 i em notao binria. Para efeito das ope-
raes aritmticas, a tabela se fecha num ciclo e as operaes seguem este ciclo. Por
exemplo, se uma varivel i do tipo int vale 6, as expresses i+1, i+2 e 2*i valem 7,
8 e 4 respectivamente. Nos dois ltimos exemplos temos um overow aritmtico.

Cada int armazenado em s bytes consecutivos, sendo s o valor da expresso


sizeof (int) (igual ao valor de sizeof (unsigned int)). Assim, cada int
representado por uma sequncia de 8s bits e portanto tem 28s possveis valores.
O conjunto desses valores

28s1 , . . . , 1, 0, 1, . . . , 28s1 1 .

Cada sequncia de 8s bits que comea com 0 representa, em notao binria,


um int no negativo. Cada sequncia de 8s bits que comea com 1 representa o
inteiro negativo k 28s , sendo k o valor da sequncia em notao binria. Esta
maneira de representar os inteiros negativos conhecida como complemento-
de-dois (ou twos-complement ).
Nmeros inteiros fora do intervalo 28s1 . . 28s1 1 so representados m-
dulo 28s (ou seja, um nmero inteiro j representado pelo nico i no intervalo
28s1 . . 28s1 1 tal que a diferena j i um mltiplo inteiro de 28s ). As
atribuies entre ints e unsigned ints tambm so feitas mdulo 28s .
144 ALGORITMOS em linguagem C ELSEVIER

Se s = 2, por exemplo, o conjunto de valores dos ints vai de 215 a 215 1,


ou seja, de 32768 a 32767. Os nmeros 32769 e 32768 so representados por
32767 e 32768 respectivamente. Depois do fragmento de cdigo
unsigned int n; int i;
n = -1; i = 65535;
o valor de n ser 65535 e o valor de i ser 1.

Exerccio
C.2.1 Suponha que cada nmero inteiro representado por 4 bits. Imagine que cada
inteiro negativo de 7 a 1 representado da seguinte maneira (diferente da conven-
o complemento-de-dois): primeiro, o valor absoluto do nmero representado em
binrio, da maneira usual; depois, o primeiro bit assume o valor 1. Por exemplo, 6
representado por 1110. Discuta as desvantagens desta notao.

C.3 Aritmtica int


As operaes aritmticas envolvendo ints, unsigned ints, chars e unsigned
chars so executadas como se seus operandos fossem do tipo int e produzem
resultados do tipo int. Em particular, os clculos so feitos mdulo 28s . Por
exemplo, se m e n so variveis do tipo unsigned int e valem 2 e 3 respectiva-
mente, ento a expresso m-n tem valor 1. Outro exemplo: se uma varivel i
do tipo int vale 32767 e s vale 2 ento o valor da expresso i+1 32768.
Se o resultado de uma operao precisa ser reduzido mdulo 28s para car
no intervalo 28s1 . . 28s1 1, dizemos que ocorreu um overow aritmtico.
Em geral, o programador ignora a possibilidade de overow pois trabalha com
nmeros de valor absoluto pequeno.
O resultado das divises truncado. A expresso 9/2, por exemplo, tem
valor 9/2. No caso de nmeros negativos, a diviso no obedece o operador
piso: na maioria dos computadores, a expresso -9/2 tem valor 9/2, que
diferente de 9/2.

C.4 Representao por sequncias de caracteres


Nmeros inteiros tambm podem ser representados por sequncias de caracteres.
O inteiro 123, por exemplo, pode ser representado pela sequncia de caracteres

- 1 2 3
ELSEVIER Apndice C. Nmeros: naturais e inteiros 145

(veja Apndice B). De acordo com a tabela ISO 8859-1 (veja Seo B.1), esta
sequncia de caracteres nada mais que a sequncia

45 49 50 51

de nmeros. Como cada caractere armazenado em um byte de 8 bits, o


resultado a sequncia

00101101 00110001 00110010 00110011

de quatro bytes. Para contrastar esta representao com a discutida no Se-


o C.2, observe que a representao de 123 em notao complemento-de-
dois
11111111 10000101
(supondo que cada int armazenado em 2 bytes).
Para converter a representao por sequncia de caracteres em representao
complemento-de-dois, usa-se a funo atoi (abreviatura de alphanumeric to
integer ), que est na biblioteca stdlib (veja Seo K.1). A funo recebe
uma string (veja Apndice G) e devolve o int correspondente. Por exemplo,
atoi ("9876") vale 9876 e atoi ("-9876") vale 9876.
A representao de inteiros por sequncias de caracteres no muito econ-
mica, pois exige muitos bytes. Alm disso, difcil fazer operaes aritmticas
diretamente sobre esta representao. Por isso, a representao no usada
na memria do computador. Entretanto, a representao muito usada em
arquivos (veja Seo H.2). Se um arquivo usa esta maneira de representar in-
teiros, dizemos que um arquivo-texto (ou text le); se usa a representao
complemento-de-dois, dizemos que um arquivo binrio (ou binary le).
Apndice D

Endereos e ponteiros

Os conceitos de endereo e ponteiro so fundamentais em qualquer linguagem


de programao, embora sejam mais visveis em C que em outras linguagens. O
conceito de ponteiro difcil; para domin-lo, preciso fazer um certo esforo.

D.1 Endereos
A memria de qualquer computador uma sequncia de bytes. Cada byte
armazena um de 256 possveis valores. Os bytes so numerados sequencialmente
e o nmero de um byte o seu endereo.
Cada objeto na memria do computador ocupa um certo nmero de bytes
consecutivos. No meu computador, um char ocupa 1 byte, um int ocupa
4 bytes e um double ocupa 8 bytes. Cada objeto na memria do computador
tem um endereo. (Na maioria dos computadores, o endereo de um objeto
o endereo do seu primeiro byte.)
Em C, o endereo de um objeto dado pelo operador & . (No confunda
este & com o operador lgico and, representado por &&.) Assim, se i uma

char c; c 89421
int i; i 89422
struct { ponto 89426
int x, y; v[0] 89434
} ponto; v[1] 89438
int v[4]; v[2] 89442

Figura D.1: O lado direito da gura d os endereos das variveis declaradas


do lado esquerdo. Portanto, &i vale 89422 e &v[3] vale 89446 . (Os endereos
no so muito realistas. . . )
148 ALGORITMOS em linguagem C ELSEVIER

varivel ento &i o seu endereo.


O operador & aparece frequentemente nas invocaes da funo scanf
(veja Seo H.1), por exemplo. Se i uma varivel inteira, podemos escre-
ver scanf ("%d", &i).

89422 9999 s - 9999

60001 89422 p

Figura D.2: Um ponteiro p, armazenado no endereo 60001, contm o endereo de


um inteiro. Neste exemplo, p vale 89422 enquanto &p vale 60001 e *p vale 9999. A
parte direita da gura uma representao esquemtica muito til da parte esquerda.

D.2 Ponteiros
Um ponteiro (ou apontador) um tipo especial de varivel destinado a ar-
mazenar endereos. Todo ponteiro pode ter o valor

NULL

que um endereo invlido. A constante NULL est denida no arquivo-


interface stdlib (veja Seo K.1) e seu valor 0 na maioria dos computadores.
Se um ponteiro p armazena o endereo de uma varivel i, podemos dizer
p aponta para i ou p o endereo de i. Se um ponteiro p tem valor diferente
de NULL ento
*p
o valor do objeto apontado por p. (No confunda esse uso de * com o operador
de multiplicao!) Por exemplo, se i uma varivel e p = &i ento escrever
*p o mesmo que escrever i.
H vrios tipos de ponteiros: ponteiros para caracteres, ponteiros para in-
teiros, ponteiros para registros, ponteiros para ponteiros para inteiros etc. Para
declarar um ponteiro p para um inteiro, escreva1
int *p;
1
O leiaute das declaraes de ponteiros sabidamente desconfortvel. Conceitualmente,
um ponteiroparaint um novo tipo de dados, o que sugere que se escreva int* p. Do
ponto de vista tcnico, entretanto, * modica a nova varivel e no o int. Isto sugere que
se escreva int *p. O compilador C aceita qualquer das formas. Tambm aceita int * p.
ELSEVIER Apndice D. Endereos e ponteiros 149

Para declarar um ponteiro q para um registro cel, escreva


struct cel *q;

int x, i = 888;
int *p; /* p um ponteiro para um inteiro */
p = &i; /* p aponta para i */
x = *p + 999;

Figura D.3: Exemplo de ponteiro. O cdigo mostra um jeito bobo de


fazer x = i+999.

Exerccios
D.2.1 Se i uma varivel do tipo int, que sentido fazem as expresses *&i e &&i?
D.2.2 Leia o verbete Pointer na Wikipedia [21].

D.3 Uma aplicao


Suponha que precisamos de uma funo que troque os valores de duas variveis
inteiras i e j. A funo

void troca (int i, int j) { /* errado! */


int temp;
temp = i; i = j; j = temp;
}

no produz o efeito desejado, pois recebe apenas os valores das variveis e no


as variveis propriamente ditas. Para obter o efeito desejado, preciso passar
funo os endereos das variveis:

void troca (int *p, int *q) {


int temp;
temp = *p; *p = *q; *q = temp;
}

Para aplicar a funo s variveis i e j basta dizer


troca (&i, &j);
150 ALGORITMOS em linguagem C ELSEVIER

Exerccios
D.3.1 Por que o cdigo abaixo est errado?
void troca (int *i, int *j) {
int *temp;
*temp = *i; *i = *j; *j = *temp; }
D.3.2 Um ponteiro pode ser usado para dizer a uma funo onde ela deve depositar
o resultado de seus clculos. Escreva uma funo hm que converta minutos em horas
eminutos. A funo recebe um inteiro t e os endereos de duas variveis inteiras,
digamos h e m, e atribui valores no negativos a essas variveis de modo que tenhamos
m < 60 e 60 h + m = t. Escreva tambm uma funo main que use a funo hm.
D.3.3 Escreva uma funo mm que receba um vetor inteiro v[0..n-1] e os endereos
de duas variveis inteiras, digamos min e max, e deposite nestas variveis o valor de um
elemento mnimo e o valor de um elemento mximo do vetor. Escreva tambm uma
funo main que use a funo mm.

D.4 Vetores e endereos


Os elementos de qualquer vetor so alocados consecutivamente na memria do
computador. Se cada elemento ocupa l bytes, a diferena entre os endereos de
dois elementos consecutivos ser l. Para livrar o programador da preocupao
com o valor de l, o compilador C cria a iluso de que l vale 1 qualquer que seja
o tipo dos elementos do vetor. Por exemplo, depois dos comandos

int *v;
v = malloc (100 * sizeof (int));

o ponteiro v aponta o primeiro elemento de um vetor de 100 inteiros. O endereo


do segundo elemento do vetor v+1 e o endereo do terceiro v+2. Se i uma
varivel do tipo int ento v + i o endereo do (i+1)-simo elemento do
vetor.
A propsito, as expresses v + i e &v[i] tm exatamente o mesmo valor e
portanto as atribuies
*(v+i) = 987 e v[i] = 987
tm o mesmo efeito. Todas estas consideraes tambm valem se o vetor tiver
sido alocado estaticamente, por uma declarao como

int v[100];

(Mas nesse caso v uma espcie de ponteiro constante, cujo valor no pode
ser alterado.)
ELSEVIER Apndice D. Endereos e ponteiros 151

for (i = 0; i < 100; i++) scanf ("%d", v + i);


for (i = 0; i < 100; i++) scanf ("%d", &v[i]);

Figura D.4: Qualquer dos dois fragmentos de cdigo pode ser usado
para carregar o vetor v[0..99].

Exerccios
D.4.1 Seja v um vetor de ints. Suponha que cada int ocupa 8 bytes no seu computa-
dor. Se o endereo de v[0] 55000, qual o valor da expresso v + 3?
D.4.2 Se v um vetor, qual a diferena conceitual entre as expresses v[3] e v+3.
D.4.3 Suponha que o vetor v e a varivel k foram declarados assim:
int v[100], k;
Descreva, em portugus, a sequncia de operaes que deve ser executada para calcular
o valor da expresso &v[k+9].
Apndice E

Registros e structs

Um registro uma coleo de variveis, possivelmente de tipos diferentes. Cada


uma dessas variveis um campo do registro. Na linguagem C, registros so
conhecidos como structs (abreviatura de structures).

E.1 Denio e manipulao de structs


O exemplo abaixo declara um registro x com trs campos inteiros:

struct {
int dia, ms, ano;
} x;

uma boa ideia dar um nome dma, por exemplo ao tipo de registro:

struct dma {
int dia, ms, ano;
};
struct dma x; /* um registro x do tipo dma */
struct dma y; /* um registro y do tipo dma */

Use o operador ponto (.) para atribuir valores aos campos do registro:

x.dia = 31; x.ms = 12; x.ano = 2008;

Exerccios
E.1.1 Escreva uma funo que receba dois structs do tipo dma, cada um representando
uma data vlida, e devolva o nmero de dias que decorreram entre as duas datas.
154 ALGORITMOS em linguagem C ELSEVIER

struct dma FimEvento (struct dma dataincio, int durao) {


struct dma datafim;
datafim.dia = ...
datafim.ms = ...
datafim.ano = ...
return datafim;
}
int main (void) {
struct dma a, b;
int d;
scanf ("%d %d %d", &a.dia, &a.ms, &a.ano); 1
scanf ("%d", &d);
b = FimEvento (a, d);
printf ("Termina em %d/%d/%d\n", b.dia, b.ms, b.ano);
return EXIT_SUCCESS;
}

Figura E.1: Um exemplo de struct. A funo FimEvento recebe a data de incio de


um evento e a durao do evento em dias. Ela devolve a data de m do evento. O
cdigo foi omitido porque um tanto enfadonho (deve levar em conta o nmero de dias
de diferentes meses, os anos bissextos etc.).

E.1.2 Escreva uma funo que receba um nmero inteiro que representa um intervalo
de tempo medido em minutos e devolva o correspondente nmero de horas e minutos
(por exemplo, 131 minutos o mesmo que 2 horas e 11 minutos). Use uma struct para
representar o par (horas, minutos).
E.1.3 Complete o cdigo da funo FimEvento da Figura E.1.

E.2 Ponteiros para structs


Cada registro tem um endereo (veja Seo D.1) na memria do computador.
(Voc pode imaginar que o endereo do registro o endereo de seu primeiro
campo, mas este detalhe irrelevante.) muito comum usar um ponteiro para
guardar o endereo de um registro. Por exemplo,

struct dma *p; /* p um ponteiro para registros dma */


struct dma x;
p = &x; /* p aponta para x */
(*p).dia = 31; 2 /* mesmo efeito que x.dia = 31 */
1
A expresso &a.dia interpretada como &(a.dia). Veja Seo J.5.
2
A expresso (*p).dia diferente de *p.dia, que equivale a *(p.dia). Veja Seo J.5.
ELSEVIER Apndice E. Registros e structs 155

A expresso p->ms uma abreviatura muito til da expresso (*p).ms:

p->ms = 12; /* mesmo efeito que (*p).ms = 12 */


p->ano = 2008;

Exerccios
E.2.1 Dena um registro empregado para armazenar os dados (nome, data de nasci-
mento, nmero do documento de identidade, data de admisso, salrio) de um em-
pregado de sua empresa. Dena um vetor de empregados para armazenar todos os
empregados de sua empresa.
E.2.2 Um racional qualquer nmero da forma p/q, sendo p inteiro e q inteiro no
nulo. conveniente representar cada racional por um registro:
struct racional { int p, q; };
Vamos convencionar que o campo q de todo racional estritamente positivo e que o
mximo divisor comum dos campos p e q 1. Escreva uma funo que receba inteiros a
e b e devolva o racional que representa a/b. Escreva uma funo que receba um racional
x e devolva o racional x. Escreva funes que recebam racionais x e y e devolvam
(1) o racional que representa a soma de x e y, (2) o racional que representa o produto
de x por y e (3) o racional que representa o quociente de x por y.
Apndice F

Alocao dinmica de memria

A alocao esttica designa um lugar para cada varivel na memria do com-


putador antes que o programa comece a ser executado. As declaraes abaixo,
por exemplo, alocam memria para duas variveis simples e um vetor:
char c; int i; int v[10];
Em certas aplicaes, a quantidade de memria necessria s se torna conhecida
durante a execuo do programa. Para lidar com essa situao preciso recorrer
alocao dinmica de memria. A alocao dinmica administrada pelas
funes malloc e free, que esto na biblioteca stdlib (veja Seo K.1). Para
usar a biblioteca, preciso dizer
#include <stdlib.h>
no incio do programa.

F.1 Funo malloc


A funo malloc (abreviatura de memory allocation) aloca um bloco de bytes
consecutivos na memria e devolve o endereo desse bloco (veja Apndice D). O
nmero de bytes especicado no argumento da funo. No seguinte fragmento
de cdigo, malloc aloca 1 byte:

char *ptr;
ptr = malloc (1);
scanf ("%c", ptr);

O endereo devolvido por malloc do tipo genrico void *. No exem-


plo acima, o endereo armazenado no ponteiro ptr, tornando-se assim um
ponteiroparachar.
158 ALGORITMOS em linguagem C ELSEVIER

Para alocar um tipo de dados que ocupa vrios bytes, preciso recorrer ao
operador sizeof, que d o nmero de bytes do tipo especicado:

typedef struct {
int dia, ms, ano;
} data;
data *d;
d = malloc (sizeof (data)); 1
d->dia = 31; d->ms = 12; d->ano = 2008;

F.2 A memria nita


Se a memria j estiver toda ocupada, malloc no consegue alocar mais espao
e devolve NULL. Antes de prosseguir, convm vericar se esse desastre aconteceu.
Por exemplo:

d = malloc (sizeof (data));


if (d == NULL) {
printf ("Socorro! Malloc devolveu NULL!\n");
exit (EXIT_FAILURE);
}

Para no cansar o leitor, os fragmentos de cdigo neste livro no vericam


se malloc devolveu NULL. Mas voc no deve deixar de fazer a vericao ao
escrever os seus prprios programas.

F.3 Funo free


As variveis alocadas estaticamente dentro de uma funo deixam de existir
quando a execuo da funo termina. J as variveis alocadas dinmicamente
continuam existindo mesmo depois que a execuo da funo termina. Se for
necessrio liberar a memria ocupada por essas variveis, preciso fazer isso
explicitamente.
A funo free libera a poro de memria alocada por malloc. O comando
free (ptr) informa o sistema de que o bloco de bytes apontado por ptr no
ser mais usado pelo programa. A prxima invocao de malloc poder tomar
posse desses bytes.
A funo free no deve ser aplicada a uma parte apenas do bloco de bytes
alocado por malloc; aplique a funo sempre ao bloco todo.
1
As aparncias enganam: sizeof no uma funo.
ELSEVIER Apndice F. Alocao dinmica de memria 159

Convm no deixar ponteiros soltos (dangling pointers) no seu programa,


pois isso pode ser explorado por hackers para atacar o seu computador. Por-
tanto, depois de cada free (ptr), atribua NULL a ptr:

free (ptr);
ptr = NULL;

(Atribuir um valor a um ponteiro que se tornou intil decididamente desele-


gante, mas no h como tratar hackers com elegncia . . . ) Para no cansar o
leitor com detalhes repetitivos, o livro no segue esta norma de segurana.

F.4 Alocao de vetores


Eis como um vetor com n elementos pode ser alocado (e depois desalocado)
durante a execuo de um programa:

int *v;
int n, i;
scanf ("%d", &n);
v = malloc (n * sizeof (int));
for (i = 0; i < n; i++) scanf ("%d", &v[i]);
for (i = n; i > 0; i--) printf ("%d ", v[i-1]);
free (v);

(Convm lembrar que a padronizao ANSI da linguagem C no permite


escrever int v[n]; se n for uma varivel.)

Exerccios
F.4.1 Escreva um programa que leia um inteiro positivo n seguido de n nmeros inteiros
e imprima esses n nmeros em ordem invertida (primeiro o ltimo, depois o penltimo
etc.). O seu programa no deve impor quaisquer restries ao valor de n.

F.5 Alocao de matrizes


Matrizes bidimensionais so implementadas como vetores de vetores. Uma ma-
triz com m linhas e n colunas um vetor cada um de cujos m elementos um
vetor de n elementos. O seguinte fragmento de cdigo faz a alocao dinmica
de uma tal matriz:
160 ALGORITMOS em linguagem C ELSEVIER

int **A;
int i;
A = malloc (m * sizeof (int *));
for (i = 0; i < m; i++)
A[i] = malloc (n * sizeof (int));

O elemento de A que est no cruzamento da linha i com a coluna j denotado


por A[i][j].
Apndice G

Strings

Considere um vetor de caracteres que contm pelo menos uma ocorrncia do


caractere nulo (veja Seo B.2). O segmento inicial do vetor que vai at primeira
ocorrncia do caractere nulo uma string, ou cadeia de caracteres. Depois
da execuo do fragmento de cdigo abaixo, por exemplo, o vetor s[0..3]
uma string (a poro s[4..9] do vetor no faz parte da string):

char *s;
s = malloc (10 * sizeof (char));
s[0] = A;
s[1] = B;
s[2] = C;
s[3] = \0;
s[4] = D;

O comprimento de uma string o seu nmero de caracteres, sem contar o


caractere nulo nal. O endereo de uma string o endereo do seu primeiro
caractere (veja Seo D.4). Em discusses informais, usual confundir uma
string com o seu endereo: a expresso considere a string s deve ser entendida
como considere a string cujo endereo s.

G.1 Strings constantes


Para especicar uma string constante, basta embrulhar uma sequncia de ca-
racteres num par de aspas duplas. O caractere nulo nal ca subentendido. Por
exemplo, "ABC" uma string constante e o fragmento de cdigo
char *s;
s = "ABC";
162 ALGORITMOS em linguagem C ELSEVIER

tem essencialmente o mesmo efeito que o fragmento na introduo deste apn-


dice. O primeiro argumento das funes printf e scanf quase sempre uma
string constante. Veja, por exemplo, a expresso printf ("%d\n", k).

int ContaVogais (char s[]) {


int i, j, num = 0;
char *vogais;
vogais = "aeiouAEIOU";
for (i = 0; s[i] != \0; i++)
for (j = 0; vogais[j] != \0; j++)
if (vogais[j] == s[i]) {
num += 1;
break;
}
return num;
}

Figura G.1: Esta funo conta o nmero de vogais em uma string s.

Exerccios
G.1.1 Qual a diferena entre "A" e A ?
G.1.2 Qual a diferena entre "mnop" e "m\nop" ? Qual a diferena entre "MNOP",
"MN\0P" e "MN0P" ?
G.1.3 Qual o comprimento da string "x=%d\n"?
G.1.4 Escreva uma funo que receba um caractere c e devolva uma string que tem c
como nico elemento.
G.1.5 O que h de errado com o fragmento de cdigo abaixo? (Veja Seo D.4.)
char s[10]; s = "ABC";
G.1.6 Escreva uma funo que receba uma string s e inteiros no negativos i e j e
devolva a string que corresponde ao segmento s[i..j]. Sua funo no deve alocar
novo espao mas pode alterar a string s que recebeu.
G.1.7 Escreva uma funo que receba uma string e imprima o nmero de ocorrncias
de cada caractere na string. Escreva um programa para testar sua funo.
G.1.8 Escreva uma funo que receba uma string e substitua cada segmento de dois
ou mais caracteres por um s caractere .
G.1.9 Escreva uma funo que receba uma string de 0s e 1s, interprete essa string como
um nmero em notao binria (veja Seo C.1) e devolva o valor desse nmero.
ELSEVIER Apndice G. Strings 163

G.1.10 Escreva uma funo que imite o comportamento de atoi: ao receber uma
string que representa um inteiro devolve o valor desse inteiro. Por exemplo, ao receber
"-9876" devolve 9876.

G.2 A biblioteca string


A biblioteca string (no confunda com a biblioteca strings) da linguagem C
contm vrias funes de manipulao de strings. Para ter acesso biblioteca,
o seu programa deve incluir o arquivo-interface string.h (veja Seo K.4):
#include <string.h>
Descrevemos a seguir duas das funes da biblioteca; uma terceira ser discutida
na seo seguinte. Nessas descries, adotaremos a denio
typedef char *string;
ou seja, trataremos strings como um novo tipo de dados (veja Seo J.3).
A funo strlen (o nome uma abreviatura de string length) recebe uma
string e devolve o seu comprimento. O cdigo da funo poderia ser escrito
assim:
unsigned int strlen (string s) {
int i;
for (i = 0; s[i] != \0; i++) ;
return i;
}

A funo strcpy (abreviatura de string copy) recebe duas strings e copia a


segunda (inclusive o caractere nulo nal) para o espao ocupado pela primeira.
O contedo original da primeira string perdido. O usurio s deve invocar
esta funo se souber que o espao alocado para a primeira string suciente
para acomodar a cpia da segunda.1 O cdigo dessa funo poderia ser escrito
assim:
string strcpy (string s, string t) {
int i;
for (i = 0; t[i] != \0; i++) s[i] = t[i];
s[i] = \0;
return s;
}
1
Buer overow uma das origens mais comuns de bugs de segurana!
164 ALGORITMOS em linguagem C ELSEVIER

(A funo devolve o endereo da primeira string, mas o usurio geralmente


descarta essa informao redundante.) A funo strcpy pode ser usada para
obter um efeito semelhante ao do exemplo que abriu este apndice:

char s[10];
strcpy (s, "ABC");

G.3 Ordem lexicogrca e a funo strcmp


A ordem lexicogrca entre strings anloga ordem entre as palavras em
um dicionrio. Para comparar duas strings s e t, procure a primeira posio,
digamos k, em que as duas strings diferem. Se s[k] < t[k] ento s lexico-
gracamente menor que t e se s[k] > t[k] ento s lexicogracamente
maior que t. Se k no est denido ento s e t so idnticas ou uma prexo
prprio da outra; nesse caso, a string mais curta lexicogracamente menor que
a mais longa.
A funo strcmp (abreviatura de string compare) da biblioteca string faz
a comparao lexicogrca de duas strings. Ela devolve um nmero negativo se
a primeira string for lexicogracamente menor que a segunda, devolve 0 se as
duas strings so iguais e devolve um nmero positivo se a primeira string for
lexicogracamente maior que a segunda.
Embora os parmetros da funo sejam do tipo char *, a funo se comporta
como se eles fossem do tipo unsigned char * (veja Apndice B). Assim, por
exemplo, "bb" considerada lexicogracamente menor que "bb", e "ba"
considerada lexicogracamente menor que "b". O cdigo da funo poderia
ser escrito assim:
int strcmp (string s, string t) {
int i;
unsigned char usi, uti;
for (i = 0; s[i] == t[i]; i++)
if (s[i] == \0) return 0;
usi = s[i]; uti = t[i];
return usi - uti;
}

Exerccios
G.3.1 Discuta as diferenas entre os trs fragmentos de cdigo a seguir:
char a[8], b[8];
ELSEVIER Apndice G. Strings 165

strcpy (a, "abacate");


strcpy (b, "banana");
char *a, *b;
a = malloc (8); strcpy (a, "abacate");
b = malloc (8); strcpy (b, "banana");
char *a, *b;
a = "abacate";
b = "banana";
G.3.2 O que h de errado com o seguinte fragmento de cdigo?
char *b, *a;
a = "abacate"; b = "banana";
if (a < b) printf ("%s vem antes de %s no dicionrio", a, b);
else printf ("%s vem depois de %s no dicionrio", a, b);
G.3.3 O que h de errado com o seguinte fragmento de cdigo?
char *a, *b;
a = "abacate"; b = "amora";
if (*a < *b) printf ("%s vem antes de %s no dicionrio", a, b);
else printf ("%s vem depois de %s no dicionrio", a, b);
Apndice H

Entrada e sada

Este apndice descreve supercialmente as funes de entrada e de sada mais


importantes da linguagem C. Todas esto na biblioteca stdio (veja Seo K.2).
Para ter acesso a essa biblioteca, o seu programa deve incluir uma cpia do
arquivo-interface stdio.h:
#include <stdio.h>

H.1 Tela e teclado: printf e scanf


A funo printf (abreviatura de print formatted) exibe uma lista de nmeros,
caracteres, strings etc. na tela do monitor. O primeiro argumento da funo
uma string (veja Apndice G) que especica o formato da exibio.
A funo scanf (abreviatura de scan format ) l do teclado uma lista de
nmeros, caracteres, strings etc. O primeiro argumento da funo uma string
que especica o formato da lista a ser lida. Os demais argumentos so os
endereos (veja Seo D.1) das variveis onde os valores lidos sero armazenados.
A funo trata todos os brancos (white-spaces, Seo B.2) como se fossem .

Exerccio
H.1.1 Leia os verbetes Printf e Scanf na Wikipedia [21].

H.2 Arquivos
Um arquivo (ou le) uma sequncia de bytes que reside no disco (ou outro
dispositivo de armazenamento). A estrutura de um arquivo semelhante da
168 ALGORITMOS em linguagem C ELSEVIER

#include <stdio.h>
#include <stdlib.h>
int main (void) {
int a, b;
double media;
scanf ("%d %d", &a, &b);
media = (a + b) / 2.0;
printf ("A mdia de %d e %d %f\n", a, b, media);
return EXIT_SUCCESS;
}

Figura H.1: Funes printf e scanf. Abaixo, o comportamento do


programa, supondo que seu nome prog.

prompt> prog
222 333
A mdia de 222 e 333 277.500000
prompt>

memria do computador, mas os bytes de um arquivo no podem ser endere-


ados individualmente. O acesso a um arquivo estritamente sequencial: para
chegar ao 5o byte preciso passar pelo 1o , 2o , 3o e 4o bytes.
Para que um programa possa manipular um arquivo, preciso associar a ele
uma varivel do tipo FILE (trata-se de uma struct denida no arquivo stdio.h).
Esta operao de associao conhecida como abertura do arquivo e exe-
cutada pela funo fopen (abreviatura de le open). O primeiro argumento
de fopen o nome do arquivo e o segundo argumento "r" ou "w" para in-
dicar se o arquivo deve ser aberto para leitura (read) ou para escrita (write).
A funo devolve o endereo de um FILE (ou NULL, se no encontra o arquivo
especicado). Depois de usar o arquivo, convm fech-lo com a funo fclose
(abreviatura de le close).
Para ler um arquivo, usa-se a funo fscanf (abreviatura de le scanf ). Tal
como scanf, a funo fscanf devolve o nmero de objetos efetivamente lidos.
O programa da Figura H.2 usa isso para detectar o m do arquivo.

Stdin e stdout. O teclado o arquivo padro de entrada (standard


input ). Ele est permanente aberto e representado pela constante stdin.
Portanto fscanf (stdin,...) equivale a scanf (...). Algo anlogo acontece
com as funes printf, fprintf e o arquivo padro de sada stdout, que
representa a tela do monitor.
ELSEVIER Apndice H. Entrada e sada 169

#include <stdio.h>
#include <stdlib.h>
int main (void) {
int x, n, k;
double soma;
FILE *entrada;
entrada = fopen ("dados.txt", "r");
if (entrada == NULL) {
printf ("\nNo encontrei o arquivo!\n");
exit (EXIT_FAILURE);
}
soma = n = 0;
while (1) {
k = fscanf (entrada, "%d", &x);
if (k != 1) break;
soma += x;
n += 1;
}
fclose (entrada);
printf ("A mdia dos nmeros %f\n", soma/n);
return EXIT_SUCCESS;
}

Figura H.2: Leitura de um arquivo. O arquivo dados.txt contm um ou


mais nmeros inteiros separados por brancos (veja Seo B.2). O programa
calcula a mdia dos nmeros.

H.3 As funes putc e getc


Uma funo de sada de dados mais bsica que printf putc (o nome uma
abreviatura de put character ). Cada invocao da funo grava um caractere no
arquivo especicado. Se c um caractere e f aponta um arquivo, putc (c, f)
grava c no arquivo f. Por exemplo, putc (*, stdout) exibe um * na tela do
monitor.
A funo correspondente de leitura de caracteres getc (abreviatura de get
character ). Cada invocao da funo l um caractere do arquivo especicado.
Por exemplo, getc (stdin) l o prximo caractere do teclado.

Tipo de objeto que getc devolve. Que acontece se getc tenta ler o
prximo caractere de um arquivo que j acabou (por exemplo, tenta ler um
arquivo vazio)? Gostaramos que getc devolvesse algum tipo de caractere in-
vlido, mas todos os 256 caracteres so vlidos! Para resolver o impasse, getc
no devolve um char mas sim um int, pois o conjunto de valores do tipo int
170 ALGORITMOS em linguagem C ELSEVIER

#include <stdio.h>
#include <stdlib.h>
int main (void) {
char linha[100];
int i, j;
for (j = 0; ; j++) {
linha[j] = getc (stdin);
if (linha[j] == \n) break;
}
for (i = 0; i <= j; i++)
putc (linha[i], stdout);
return EXIT_SUCCESS;
}

Figura H.3: Uso das funes getc e putc. O programa l uma linha de
caracteres do teclado, armazena esta linha em um vetor e em seguida exibe os
caracteres na tela do monitor.

#include <stdio.h>
#include <stdlib.h>
int main (void) {
char c; /* erro */
FILE *entrada;
entrada = fopen ("dados.txt", "r");
if (entrada == NULL) exit (EXIT_FAILURE);
c = getc (entrada);
fclose (entrada);
putc (c, stdout);
return EXIT_SUCCESS;
}

Figura H.4: Uso de getc. O programa promete ler o primeiro caractere do


arquivo dados.txt e exibir esse caractere na tela do monitor. Se o arquivo
estiver vazio, o programa no funciona corretamente.

int c;
c = getc (entrada);
if (c != EOF) putc (c, stdout);

Figura H.5: Alteraes do programa da Figura H.4. Se o arquivo estiver


vazio, o programa no imprime nada.
ELSEVIER Apndice H. Entrada e sada 171

contm propriamente o conjunto de valores do tipo char. Se o arquivo tiver


acabado, getc devolve um int que no possa ser confundido com um char.
Mais especicamente,
1. se houver um prximo caractere no arquivo, getc l o caractere como
se ele fosse um unsigned char, transforma-o em um int e devolve o
resultado;
2. se o arquivo no tiver mais caracteres, getc devolve 1.
Mais precisamente, se o arquivo no tiver mais caracteres, a funo devolve a
constante EOF (abreviatura de end of le), que est denida no arquivo-interface
stdio.h (veja Seo K.2). O valor usual de EOF 1.
A soluo adotada por getc uma boa lio de projeto de algoritmos: a
funo devolve um objeto que pertence a um superconjunto do conjunto em que
estamos realmente interessados. Esse tipo de truque usado em muitas outras
situaes.

Exerccios
H.3.1 Suponha que o arquivo dados.txt contm a sequncia de caracteres ABCDEF
e nada mais. Qual o resultado do seguinte programa? Que acontece se trocarmos a
declarao int c por char c ou por unsigned char c?
int main (void) {
int c;
FILE *entrada;
entrada = fopen ("dados.txt", "r");
while ((c = getc (entrada)) != EOF)
printf ("%c ", c);
fclose (entrada);
return EXIT_SUCCESS; }
H.3.2 Escreva um programa que faa uma cpia de um arquivo. Os nomes dos dois
arquivos so digitados pelo usurio.
H.3.3 Suponha dado um arquivo contendo cdigo C com comentrios. Escreva um
programa que leia esse arquivo, remova os comentrios, e grave o cdigo limpo em
outro arquivo.

H.4 Argumentos na linha de comando


A funo main, como qualquer outra funo, admite argumentos. Eles so
conhecidos como argumentos na linha de comando (ou command-line argu-
ments). O primeiro argumento um inteiro que d o nmero de argumentos.
172 ALGORITMOS em linguagem C ELSEVIER

O segundo, um vetor de strings (veja Apndice G) que armazena os demais


argumentos. A funo main deve ser especicada assim:

int main (int numargs, char *arg[]) {


. . .
}

Se o nome do arquivo que contm o programa for prog ento depois do comando
prog a bb 999
o parmetro numargs ter valor 4 e os elementos arg[0] a arg[3] do vetor arg
sero as strings "prog", "a", "bb" e "999" respectivamente.

#include <stdio.h>
#include <stdlib.h>
int main (int numargs, char *arg[]) {
int n;
double soma = 0;
for (i = 1; i < numargs; i++)
soma += atoi (arg[i]);
n = numargs - 1;
printf ("mdia = %.2f\n", soma / n);
return EXIT_SUCCESS;
}

Figura H.6: O programa calcula a mdia dos nmeros fornecidos


como argumentos na linha de comando. Abaixo, o comportamento
do programa, supondo que seu nome prog.

prompt> prog +22 33 -11 +44


mdia = 22.00
prompt>

Exerccio
H.4.1 Escreva um programa que conte o nmero de ocorrncias de cada um dos ca-
racteres de um arquivo. O seu programa deve receber o nome do arquivo na linha de
comando e imprimir uma tabela com o nmero de ocorrncias de cada caractere do ar-
quivo. Para ganhar inspirao, analise o comportamento do utilitrio wc (abreviatura
de word count ) presente em todo sistema UNIX e GNU/Linux.
Apndice I

Nmeros aleatrios

Sequncias de nmeros aleatrios so teis em inmeras aplicaes. Dada a


diculdade de obter nmeros verdadeiramente aleatrios, devemos nos conten-
tar com nmeros pseudo-aleatrios, gerados por algoritmos. Para simplicar a
linguagem, omitiremos o pseudo no que segue.

I.1 A funo rand


A funo rand (abreviatura de random), denida na biblioteca stdlib, gera
nmeros aleatrios no intervalo fechado 0..RAND_MAX. A constante RAND_MAX
est denida no arquivo-interface stdlib.h (veja Seo K.1). Cada invocao
da funo produz um nmero aleatrio.

Exerccios
I.1.1 (Roberts [15]) Qual o defeito da seguinte funo, que promete simular uma jogada
de moeda?
char *CaraCoroa (void) {
int r;
r = rand () % 2;
if (r == 1) return "cara";
else return "coroa"; }
I.1.2 O seguinte fragmento de cdigo foi extrado de um programa que pretende simular
o rolar de um dado. Qual o defeito do fragmento?
if (r < RAND_MAX * 5 / 6) return 5;
174 ALGORITMOS em linguagem C ELSEVIER

I.2 Inteiros aleatrios


A funo rand pode ser usada para produzir um nmero inteiro aleatrio num
dado intervalo a..b. O cdigo abaixo (adaptada do livro de Roberts [15]) produz
o resultado em trs passos. Primeiro, transforma o inteiro gerado por rand em
um nmero real x tal que 0 x < 1. Depois, transforma x em um nmero
inteiro i no intervalo fechado 0..b-a. Finalmente, translada i para o intervalo
a..b.
/* Esta funo devolve um inteiro aleatrio
* no intervalo fechado a..b. */
int InteiroAleatrio (int a, int b) {
double r, x, R = RAND_MAX;
int i;
r = rand ();
x = r / (R + 1);
i = x * (b - a + 1);
return a + i;
}

(A qualidade dos nmeros produzidos pela funo no boa se a diferena b a


for grande, especialmente se a diferena for maior que RAND_MAX.)

Exerccios
I.2.1 No cdigo da funo InteiroAleatrio, que acontece se escrevermos x =
r/(RAND_MAX+1) ? Que acontece se escrevermos x = r/RAND_MAX ?
I.2.2 Verique que o nmero produzido pela funo InteiroAleatrio quando a = 0
e b = RAND_MAX igual ao produzido por rand.
I.2.3 Use a funo InteiroAleatrio para simular o rolar de um dado.

I.3 Sementes
Cada vez que invocada, a funo rand calcula um novo nmero aleatrio a
partir do nmero, digamos r, que produziu em resposta invocao anterior.
O nmero r que corresponde primeira invocao de rand conhecido como
semente. Dada a semente, a sequncia de nmeros gerada pelas sucessivas
execues de rand est completamente determinada.
ELSEVIER Apndice I. Nmeros aleatrios 175

O programador pode especicar a semente por meio da funo srand, que


tem um unsigned int como argumento. (A funo est na biblioteca stdlib.)
Se o programador no especicar a semente, o sistema adota o valor 0. Para
tornar a semente mais imprevisvel, pode-se usar a funo time da biblioteca
time:
srand (time (NULL));
Apndice J

Miscelnea

Este apndice rene pequenas notas sobre alguns recursos da linguagem C.

J.1 Valor de uma expresso


Toda expresso em C tem um valor. Por exemplo, se x vale 99 ento a expresso
3 * x + 4 tem valor 301. A expresso
y = 3 * x + 4
tambm tem valor 301. O clculo do valor desta expresso tem o efeito colateral
de atribuir o valor da subexpresso 3 * x + 4 varivel y. Assim, y passa a
valer 301.
Se x vale 99 ento o valor da expresso x += 1 (que uma abreviatura
de x = x + 1) 100. O clculo do valor desta expresso tem o efeito colateral de
incrementar o valor de x, que passa a valer 100. Um exemplo mais delicado,
muito comum em C: se x vale 99 ento as expresses
x++ e ++x
valem 99 e 100 respectivamente. O clculo do valor de qualquer destas expres-
ses tem o efeito colateral de incrementar o valor de x, que passa a valer 100.
Algo anlogo vale para expresses da forma x-- e --x .
As expresses que contm o operador vrgula so usadas, em geral, mais por
seu efeito colateral que por seu valor. Por exemplo, o valor da expresso
j = 1+2, k = 99
99 e o clculo do valor da expresso tem o efeito colateral de atribuir os valores
3 e 99 a j e k respectivamente. Outro exemplo: as duas expresses abaixo tm
o mesmo efeito:
178 ALGORITMOS em linguagem C ELSEVIER

t = a, a = b, b = t;
t = a; a = b; b = t;

J.2 Valor de uma expresso booleana


Toda expresso booleana tem valor 0 ou 1. Por exemplo, se j igual a 99 e a
menor que b ento o valor da expresso
j == 99 && a < b
1. Caso contrrio, ou seja, se j = 99 ou a b, o valor da expresso 0.
O valor de toda expresso booleana calculado da esquerda para a direita.
To logo o valor da expresso ca denido, o clculo interrompido. Muitas
vezes, os ltimos termos de uma expresso longa no so sequer examinados.
Esta regra (veja short-circuit evaluation na Wikipedia [21]) usada em C para
evitar o clculo de subexpresses de valor indenido. Por exemplo, embora os
fragmentos

if (j <= 99 && v[j] < x) ...


if (v[j] < x && j <= 99) ...

paream equivalentes, o primeiro est correto enquanto o segundo pode estar


incorreto se v[j] no estiver denido quando j maior que 99.

J.3 Tipos de dados e typedef


Um tipo de dados um conjunto de valores munido de um conjunto de ope-
raes. Algumas operaes transformam valores em outros valores. Outras,
associam nmeros a valores ou conjuntos de valores. Eis alguns tipos de dados
bsicos da linguagem C:

1. O conjunto de valores do tipo int INT_MIN, . . . , 1, 0, 1, . . . , INT_MAX (veja


Apndice C). Os valores das constantes INT_MIN e INT_MAX esto denidos
no arquivo-interface limits.h (veja Seo K.5). Entre as operaes sobre
esses valores destacam-se as de comparao (<, <=, ==, >=, >) e as aritmticas
(+, -, *, /). Cada operao aritmtica recebe dois ints e devolve um int
como resultado (veja Seo C.3).
2. O conjunto de valores do tipo char (veja Apndice B) 128, 127, . . . ,
126, 127. As operaes usuais sobre esses valores so indicadas por <, <=,
==, >=, >, + e -. Os resultados dessas operaes so do tipo int.
3. O conjunto de valores do tipo double uma aproximao do conjunto dos
ELSEVIER Apndice J. Miscelnea 179

nmeros reais. Os valores mnimo e mximo esto registrados no arquivo-


interface limits.h (veja Seo K.5). As operaes so uma aproximao
das operaes aritmticas sobre nmeros reais.

O programador pode denir os seus prprios tipos de dados recorrendo ao


operador typedef. muito conveniente, por exemplo, denir o tipo de dados
string (veja Apndice G):

typedef char *string;

Para denir um tipo de dados via typedef, faa o seguinte: (1) escreva a
declarao de uma varivel do tipo desejado e (2) escreva typedef antes da
declarao. Isso transformar o nome da varivel no nome de um tipo de dados.
Por exemplo,

struct {double x, y;} ponto_no_plano;

declara uma varivel ponto_no_plano, e

typedef struct {double x, y;} ponto_no_plano;

transforma ponto_no_plano em um novo tipo de dados. Esse novo tipo pode


ser usado para declarar novas variveis:
ponto_no_plano p, q;

J.4 Include e dene


Antes de transformar um programa em cdigo executvel, o compilador C faz um
pr-processamento que cuida de todos os #include e #define. Cada #include
substitudo por uma cpia do arquivo indicado. J a diretiva #define faz com
que cada ocorrncia da constante indicada seja substituda pelo seu valor. Por
exemplo, o programa

#include <stdlib.h>
#define MAX 1000
int main (void) {
int v[MAX], i;
for (i = 0; i < MAX; i++) scanf ("%d", &v[i]);
return EXIT_SUCCESS;
}
180 ALGORITMOS em linguagem C ELSEVIER

transformado pelo pr-processador no programa

int rand (void);


void srand (unsigned int);
int atoi (char *);
int main (void) {
int v[1000], i;
for (i = 0; i < 1000; i++) scanf ("%d", &v[i]);
return 0;
}

As primeiras linhas do novo programa so uma cpia do que est no arquivo


stdlib.h. (O exemplo ctcio: o arquivo stdlib.h contm bem mais que
essas trs linhas. Veja uma amostra maior no Seo K.1.) A constante MAX
substituda por 1000 e a constante EXIT_SUCCESS substituda por 0 porque o
arquivo stdlib.h contm a linha
#define EXIT_SUCCESS 0

J.5 Precedncia entre operadores em C


H uma relao de precedncia entre os vrios operadores da linguagem C.
Durante o clculo do valor de uma expresso, alguns operadores so aplicados
antes de outros, respeitando a ordem de precedncia.

expresso interpretao expresso interpretao


&x[i] &(x[i]) a[i].b[j] ((a[i]).b)[j]
*p.dia *(p.dia) h->e->d (h->e)->d
*x++ *(x++) &h->e &(h->e)

Figura J.1: Alguns exemplos da relao de precedncia entre operadores.

Os operadores em cada linha da tabela abaixo tm precedncia sobre os


operadores das linhas seguintes. A coluna direita d a regra de associao
esquerdaparadireita ou direitaparaesquerda dos operadores da linha. Os
operadores unrios (um s operando) esto todos na segunda linha da tabela;
as demais linhas tratam de operadores binrios (dois operandos).
ELSEVIER Apndice J. Miscelnea 181

binrios () [] -> . ed
unrios - ++ -- ! & * ~ (cast) sizeof de
binrios * / % ed
binrios + - ed
binrios << >> ed
binrios < <= >= > ed
binrios == != ed
binrio & ed
binrio ^ ed
binrio | ed
binrio && ed
binrio || ed
binrios ? : de
binrios = op= de
binrio , ed

A expresso op= representa os operadores +=, -=, *= etc. A expresso (cast)


representa os operadores (int), (double) etc.
Apndice K

Arquivos-interface de bibliotecas

Programas em C tm acesso a vrias bibliotecas de funes. A implementao


dessas funes varia de computador para computador. Para isolar o usurio
das peculiaridades da implementao, o acesso a cada biblioteca se d atravs
de um arquivo-interface, que contm os prottipos das funes da biblioteca e
as denies de algumas constantes. (No meu computador, todos os arquivos-
interface esto no diretrio /usr/include/.)
Para usar uma biblioteca X, o programador deve incluir no seu programa
(veja Seo J.4) uma cpia do arquivo-interface X.h. Para usar a biblioteca
stdlib, por exemplo, diga
#include <stdlib.h>
no incio do programa. Seguem amostras muito simplicadas (e por vezes um
pouco distorcidas) dos arquivos-interface das bilbiotecas stdlib, stdio, math,
string, limits e time.

K.1 Amostra do arquivo stdlib.h


/* ALOCAO DE MEMRIA, NMEROS ALEATRIOS ETC.
********************************************************************/

/* A funo devolve 1 para dizer que terminou de maneira anormal. */

#define EXIT_FAILURE 1

/* Devolve 0 se terminou normalmente. */

#define EXIT_SUCCESS 0
184 ALGORITMOS em linguagem C ELSEVIER

/* A funo exit interrompe a execuo do programa e fecha os arquivos


* que o programa tenha porventura aberto. Se for invocada com
* argumento 0, o sistema operacional informado de que o programa
* terminou com sucesso; caso contrrio, o sistema operacional
* informado de que o programa terminou de maneira excepcional.
* Uso tpico: exit (EXIT_FAILURE). */

void exit (int status);

#define RAND_MAX (32767)

/* A funo rand devolve um nmero inteiro pseudoaleatrio no


* intervalo fechado 0..RAND_MAX. Uso tpico: i = rand (). */

int rand (void);

/* A funo srand define uma semente para a funo rand. A funo deve
* ser invocada antes do primeiro uso de rand para que a sequncia de
* nmeros devolvidos por rand no seja sempre a mesma. Uso tpico:
* srand (time (NULL)). */

void srand (unsigned int u);

/* A funo atoi recebe uma string que representa um nmero inteiro em


* notao decimal e converte essa string no int correspondente.
* Exemplo: atoi ("-1234") vale -1234. Uso tpico: i = atoi (s).
* responsabilidade do usurio garantir que o nmero representado
* pela string pertence ao intervalo fechado INT_MIN..INT_MAX. */

int atoi (char *s);

#define NULL 0

/* A funo malloc recebe um nmero natural N e aloca N bytes


* consecutivos na memria. Devolve o endereo do primeiro byte
* alocado. Se no puder alocar os N bytes, devolve NULL.
* Uso tpico: pntr = malloc (N). */

void *malloc (unsigned int N);

/* A funo free recebe o endereo pntr de um bloco de bytes


* previamente alocado por malloc e libera esse bloco.
* Uso tpico: free (pntr). */

void free (void *pntr);


ELSEVIER Apndice K. Arquivos-interface de bibliotecas 185

/* A funo qsort rearranja o vetor base[0..nmemb-1] em ordem


* crescente. Cada elemento ocupa size bytes. A comparao entre
* elementos do vetor dada pela funo compar. */

void qsort (void *base, unsigned int nmemb, unsigned int size,
int (*compar)(void *, void *));

K.2 Amostra do arquivo stdio.h


/* FUNES DE ENTRADA E SADA
********************************************************************/

#define NULL 0

/* A constante EOF um inteiro diferente de todo unsigned char. */

#define EOF (-1)

/* A funo fgetc l o prximo byte (se tal existir) do arquivo f.


* O byte interpretado como um unsigned char, convertido para int,
* e em seguida devolvido pela funo. Se f no tem um prximo byte,
* a funo devolve EOF. Uso tpico: i = fgetc (f). */

int fgetc (FILE *f);

/* A funo getc tem o mesmo comportamento que fgetc mas


* implementada como uma macro. Uso tpico: i = getc (f). */

int getc (FILE *f);

/* A funo fputc converte c em unsigned char e escreve o caractere


* resultante no arquivo f. Uso tpico: fputc (c, f). */

int fputc (int c, FILE *f);

/* A funo putc tem o mesmo comportamento que fputc, mas


* implementada como uma macro. Uso tpico: putc (c, f). */

int putc (int c, FILE *f);

/* Funes de leitura e gravao com formato. */

int printf (char *s, ...);


int scanf (char *s, ...);
int fprintf (FILE *f, char *s, ...);
186 ALGORITMOS em linguagem C ELSEVIER

int fscanf (FILE *f, char *s, ...);

/* Arquivos. (A definio da struct FILE foi impiedosamente


* simplificada...) */

typedef struct {
int _cnt;
unsigned char *_ptr;
unsigned char *_base;
unsigned char _flag;
unsigned char _file;
} FILE;

extern FILE *stdin;


extern FILE *stdout;

/* A funo fopen abre o arquivo cujo nome str. O arquivo aberto


* para leitura se modo for "r" e para gravao se modo for "w".
* Uso tpico: f = fopen ("dir/meuarquivo.txt", "r"). */

FILE *fopen (char *str, char *modo);

/* A funo fclose fecha o arquivo f. Uso tpico: fclose (f). */

int fclose (FILE *f);

K.3 Amostra do arquivo math.h


/* PARA TER ACESSO BIBLIOTECA MATH PRECISO COMPILAR O PROGRAMA
* COM AS OPES APROPRIADAS. NO CASO DO COMPILADOR gcc, DIGA
* gcc nome_do_arquivo.c -lm
********************************************************************/

/* Funes trigonomtricas. */

double sin (double x);


double cos (double x);
double tan (double x);

/* A funo exp devolve e^x, ou seja, o nmero e elevado potncia x.


* Uso tpico: y = exp (x). */

double exp (double x);


ELSEVIER Apndice K. Arquivos-interface de bibliotecas 187

/* A funo log devolve o logaritmo de x na base e. No use com x


* negativo. Uso tpico: y = log (x). */

double log (double x);

/* A funo sqrt devolve a raiz quadrada de x. No use com x negativo.


* Uso tpico: y = sqrt (x). */

double sqrt (double x);

K.4 Amostra do arquivo string.h


/* FUNES DE MANIPULAO DE STRINGS
********************************************************************/

/* A funo strlen devolve o nmero de caracteres da string x (sem


* contar o \0 final). O cdigo da funo tem o mesmo efeito que
* for (i = 0; x[i] != 0; i++) ;
* return i;
* Uso tpico: k = strlen (x). */

unsigned int strlen (char *x);

/* A funo strcmp compara lexicograficamente as strings x e y.


* Devolve um nmero negativo se x precede y, devolve 0 se x igual
* a y e devolve um nmero positivo se x sucede y. O cdigo da funo
* equivale a
* for (i = 0; x[i] == y[i]; i++)
* if (x[i] == 0) return 0;
* return (unsigned int) x[i] - (unsigned int) y[i];
* Uso tpico: if (strcmp (x, y) == 0) ... . */

int strcmp (char *x, char *y);

/* A funo strcpy copia a string x no espao alocado para a string y.


* Antes de invocar a funo, certifique-se de que o espao alocado a
* y tem pelo menos strlen (x) + 1 bytes. A funo devolve y. Exemplo:
* char y[4];
* strcpy (y, "ABC");
* O cdigo da funo equivale a
* for (i = 0; (y[i] = x[i]) != 0; i++) ;
* Uso tpico: strcpy (y, x). */

char *strcpy (char *y, char *x);


188 ALGORITMOS em linguagem C ELSEVIER

K.5 Amostra do arquivo limits.h


/* VALORES MNIMOS E MXIMOS DE VRIOS TIPOS DE DADOS
********************************************************************/

/* Valores mnimo (-2^31) e mximo (2^31-1) do tipo de dados int. */

#define INT_MIN (-2147483648)


#define INT_MAX (2147483647)

/* Valor mximo (2^32-1) do tipo de dados unsigned int. */

#define UINT_MAX (4294967295)

/* Valores mnimo e mximo do tipo de dados double. */

#define DBL_MIN (2.2250738585072014E-308)


#define DBL_MAX (1.7976931348623157E+308)

K.6 Amostra do arquivo time.h


/* MEDIDA DE TEMPO
********************************************************************/

/* A funo time devolve a leitura do relgio, em segundos. Uso


* tpico: tempo = time (NULL). */

long time (long *t);

#define CLOCKS_PER_SEC (1000000)

/* A funo clock devolve o tempo de CPU decorrido desde o incio da


* execuo do seu programa. Para converter essa quantidade de tempo
* em segundos, divida pela constante CLOCKS_PER_SEC. Exemplo:
* double start, finish, elapsed;
* start = (double) clock () / CLOCKS_PER_SEC;
* . . . [clculos] . . .
* finish = (double) clock () / CLOCKS_PER_SEC;
* elapsed = finish - start;
* Sugesto: repita o bloco [clculos] muitas vezes, digamos 100,
* e divida elapsed por esse nmero. */

long clock (void);


Apndice L

Solues de alguns exerccios

Este apndice rene solues (nem sempre completas) de alguns dos exerccios.

1.1.3 Tudo indica que o parmetro s supruo.

1.1.4 Tudo indica que a condio p <= q <= r suprua.

2.2.1 A funo devolve o valor de um elemento mximo do vetor v[0..n-1]:


int Mximo (int v[], int n) {
int j, x = v[0];
for (j = 1; j < n; j++) {
if (x < v[j]) x = v[j];
return x; }

2.2.4 O valor da expresso X(4) 13: n X(n)


1 1
2 2
3 2+31=5
4 5 + 4 2 = 13

2.2.5 A funo est errada pois nem sempre reduz uma instncia grande a uma ins-
tncia menor. Se n vale 1, por exemplo, a expresso n/3 + 1 tambm vale 1.

2.3.3 Soma recursiva. A funo recebe um vetor v e um nmero n >= 0 e devolve a


soma dos elementos positivos de v[0..n-1].
int Soma (int v[], int n) {
if (n == 0) return 0;
else {
int s = Soma (v, n - 1);
if (v[n-1] > 0) s += v[n-1];
return s; } }
190 ALGORITMOS em linguagem C ELSEVIER

2.3.6 A funo abaixo est correta mas muito ineciente, pois F(n) recalculado
muitas vezes para um mesmo n. Por exemplo, para calcular F(4), a funo calcula
F(2) duas vezes.
int F (int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return F(n - 1) + F(n - 2); }

2.3.9 Soluo recursiva:


int EuclidesR (int m, int n) {
if (n == 0) return m;
return EuclidesR (n, m % n); }

3.1.1 No incio de cada iterao, ou seja, imediatamente antes de cada comparao de


k com 0, x diferente de todos os elementos de v[k+1..n-1]. Exerccio: Prove (por
induo) que o invariante est correto. Em seguida, prove que o algoritmo est correto.

3.1.2 Esta variante to boa quanto a que est no texto.

3.1.3 Erro: o teste if (v[k] == x) d resultados imprevisveis quando k est fora do


intervalo 0..n-1.

3.1.5 Com esta deciso de projeto, faz mais sentido percorrer o vetor do comeo para
o m:
int Busca (int x, int v[], int n) {
k = 0;
while (k < n && v[k] != x) k += 1;
return k; }
Para evitar o grande nmero de comparaes de k com n, podemos postar uma sen-
tinela em v[n]:
k = 0;
v[n] = x; /* sentinela */
while (v[k] != x) k += 1;
return k;

3.1.6 (As perguntas supem, implicitamente, que o valor inicial de j 0 e no 1.)


No faz sentido trocar x = v[0] por x = 0 pois o valor de um elemento mximo
do vetor pode ser negativo. Se trocarmos x = v[0] por x = INT_MIN, a funo
continua correta mas ca deselegante, pois INT_MIN depende do computador e no da
funo em si. Se trocarmos x < v[j] por x <= v[j], a funo ca ligeiramente
menos eciente.

3.2.1 Cdigo correto mas deselegante e muito ineciente. Deveria invocar a recurso
somente depois de if (x == v[n-1]) return 1.

3.3.2 Deselegante: a atribuio v[n - 1] = 0 intil.


ELSEVIER Apndice L. Solues de alguns exerccios 191

3.3.3 Deselegante: if (k < n - 1) supruo.

3.5.1 O cdigo no mais eciente que o exerccio 3.5.2, mas pelo menos no to
torto.

3.5.2 Ineciente e deselegante. A alterao do valor de i dentro do for controlado por


i uma pssima maneira de obter o efeito desejado. (A comparao suprua i < n
dentro do if tambm deselegante e suprua.)

4.4.1 Muito deselegante. O segundo parmetro intil. O if supruo.

4.4.2 A funo Remv abaixo recebe o endereo p de uma clula qualquer de uma lista
no vazia lst. Se p = NULL, a funo supe que p->seg = NULL e remove a clula cujo
endereo p->seg. Se p = NULL, a funo remove a clula cujo endereo lst. Em
ambos os casos, a funo devolve o endereo da nova lista.
clula *Remv (clula *lst, clula *p) {
clula *lixo;
if (p == NULL) {
lixo = lst; lst = lixo->seg; }
else {
lixo = p->seg; p->seg = lixo->seg; }
free (lixo);
return lst; }
Uma soluo mais sosticada usa um ponteiroparaponteiro como parmetro. A fun-
o Remvpp abaixo recebe o endereo pp de um ponteiro que aponta para uma clula
( claro que pp deve ser diferente de NULL) e remove a clula cujo endereo *pp.
void Remvpp (clula **pp) {
clula *lixo;
lixo = *pp;
*pp = lixo->seg;
free (lixo); }
Para remover a primeira clula de uma lista lst, o usurio diz Remvpp (&lst). A funo
atualiza o valor do ponteiro lst de tal modo que ele continue apontando para a (pri-
meira clula da) nova lista. Para remover a sucessora da clula cujo endereo p basta
dizer Remvpp (&p->seg). (A expresso &p->seg interpretada como &(p->seg),
conforme o Seo J.5.)

4.5.2 No h como fazer isso.

4.5.3 Insere nova clula com contedo y em uma lista encadeada (com ou sem cabea,
no importa). O segundo parmetro da funo um ponteiroparaponteiro. Para
inserir imediatamente aps uma clula cujo endereo p, invoque a funo com segundo
argumento &p->seg (veja Seo J.5). Para inserir antes da primeira clula de uma lista
lst, invoque a funo com segundo argumento &lst (o valor de lst ser atualizado
nesse caso).
192 ALGORITMOS em linguagem C ELSEVIER

void Insere (int y, clula **pp) {


clula *nova;
nova = malloc (sizeof (clula));
nova->contedo = y;
nova->seg = *pp;
*pp = nova; }

4.7.1 Copia vetor para lista: recebe um vetor v[0..n-1] e devolve uma lista encadeada
sem cabea com o mesmo contedo do vetor.
clula *CopiaVetorParaLista (int v[], int n) {
clula *lst = NULL;
int i;
for (i = n-1; i >= 0; i--) {
clula *p;
p = malloc (sizeof (clula));
p->contedo = v[i];
p->seg = lst;
lst = p; }
return lst; }

4.7.7 Ponto mdio de lista:


clula *Meio (clula *p) {
clula *m;
m = p;
while (p != NULL && p->seg != NULL) {
m = m->seg;
p = p->seg->seg; }
return m; }

5.2.2 Seja v uma cidade no vetor f[0..t-1]. Segue dos invariantes 1 e 2 que d[v]
a distncia de o a v. Seja w uma cidade fora de f[0..t-1]. Suponha que existe um
caminho de o a w. Como o = f[0], o invariante 3 garante que o caminho passaria por
alguma cidade em f[s..t-1]. Mas isso impossvel, pois s = t.

6.3.3 Esta variante da funo InfixaParaPosfixa tira proveito dos recursos sintticos
da linguagem C. A parte inicial do cdigo foi omitida pois idntica do texto.
for (j = 0, i = 1; infix[i] != \0; i++) {
switch (infix[i]) {
case (: p[t++] = infix[i];
break;
case ): while ((x = p[--t]) != () posfix[j++] = x;
break;
case +:
case -: while ((x = p[t-1]) != () {
posfix[j++] = x;
--t; }
p[t++] = infix[i];
ELSEVIER Apndice L. Solues de alguns exerccios 193

break;
case *:
case /: while ((x = p[t-1]) != ( && x != + && x != -) {
posfix[j++] = x;
--t; }
p[t++] = infix[i];
break;
default: posfix[j++] = infix[i]; } }

7.2.2 No incio de cada iterao, imediatamente antes da comparao de j com n,


temos v[j1] < x. (Esta relao vale at mesmo no comeo da primeira iterao se
estivermos dispostos a imaginar que v[1] vale .)

7.3.1 Correto mas ligeiramente deselegante: no preciso tratar em separado dos casos
v[n-1] < x e x <= v[0].

7.3.5 As variveis e e d so sempre mpares.

7.5.3 No h como evitar o problema. Mas possvel adiar o desastre (ou seja, lidar
com vetores duas vezes maiores) se trocarmos (e+d)/2 pela expresso equivalente
e + (d-e)/2.

7.7.1 Correta (e talvez mais fcil de entender que a funo dada no texto) mas ligei-
ramente deselegante pois os dois if podem ser eliminados (veja texto).

7.8.3 Verso iterativa da busca binria simplicada:


int BuscaBin (int x, int n, int v[]) {
int e = -1, m, d = n;
while (e < d - 1) {
m = (e + d)/2;
if (v[m] == x) return m;
if (v[m] < x) e = m;
else d = m; }
return -1; }

7.8.7 Clculo de k n com no mais que 2log2 n multiplicaes:


int Potncia (int k, int n) {
if (n == 0) return 1;
else {
int m; int fator;
m = n/2;
fator = Potncia (k, m);
if (m + m == n) return fator * fator;
else return fator * fator * k; } }

8.2.4 Correto mas deselegante e um tanto ineciente.


194 ALGORITMOS em linguagem C ELSEVIER

8.2.7 O problema no tem soluo (a menos que os elementos do vetor sejam distintos
dois a dois).

8.2.9 Verso recursiva:


void InseroR (int n, int v[]) {
if (n > 1) {
int x, i;
InseroR (n - 1, v);
x = v[n-1];
for (i = n-2; i >= 0 && v[i] > x; i--) v[i+1] = v[i];
v[i+1] = x; } }

8.4.4 Suponhamos que as clulas da lista so do tipo clula:


struct cel {int valor; struct cel *seg;};
typedef struct cel clula;
Nossa lista tem clula-cabea. A funo de ordenao recebe um ponteiro para a clula
cabea e no precisa devolver nada:
void OrdenaPorInsero (clula *lst) {
clula *a, *aa, *c; *cc;
c = lst;
while (c->seg != NULL) {
a = lst;
while (a->seg->valor < c->seg->valor) a = a->seg;
if (a == c) c = c->seg;
else {
aa = a->seg; cc = c->seg;
a->seg = cc; c->seg = cc->seg;
cc->seg = aa; } } }

9.1.2 O cdigo ca incorreto. (D detalhes.)

9.1.5 No funciona. Entra em loop ou produz segmentation fault. (D detalhes.)

9.1.6 O consumo de tempo quadrtico. (D detalhes.)

10.2.4 Muito ineciente. (D detalhes.)

10.3.3 No escolhe o maior dos lhos. (D um exemplo.)

10.4.2 No. (D um contraexemplo simples.)

11.2.3 Os testes if (j < k) e if (j < r) so supruos.

11.2.6 No incio de cada passagem por A, v[p . . r] uma permutao do vetor original,
i j + 1, p j r, v[p+1 . . j] c e v[j+1 . . r] > c.

11.2.5 Invariantes: no incio de cada iterao controlada por while (1), v[p . . r] uma
permutao do vetor original, i j + 1, v[p+1 . . i1] c e v[j+1 . . r] > c.
ELSEVIER Apndice L. Solues de alguns exerccios 195

11.3.1 A execuo da funo pode no parar. (Mostre um exemplo.)

11.3.2 A execuo da funo pode no parar. (Mostre um exemplo.)

11.3.6 Funo Quicksort reescrita com while:


void Quicksrt (int p, int r, int v[]) {
while (p < r) {
int j = Separa (p, r, v);
Quicksrt (p, j - 1, v);
p = j + 1; } }

11.3.7 Resta apenas escrever a documentao correta da funo:


void Qcksrt (int p, int r, int v[]) {
int j = Separa (p, r, v);
if (p < j - 1) Qcksrt (p, j - 1, v);
if (j + 1 < r) Qcksrt (j + 1, r, v); }

11.3.11 Verso iterativa do Quicksort. Usa duas pilhas: pilhap[0..t] armazena os


extremos inferiores dos subvetores e pilhar[0..t] armazena os extremos superiores:
pilhap[0] = p; pilhar[0] = r; t = 0;
while (t >= 0) {
p = pilhap[t]; r = pilhar[t]; t--;
while (p < r) {
j = Separa (p, r, v);
t++; pilhap[t] = p; pilhar[t] = j - 1;
p = j + 1; } }

12.4.3 Enumerao das combinaes k a k de 1, 2, . . , n. A funo recebe 1 k n e


imprime, em ordem lexicogrca, todas as subsequncias de 1, 2, . . , n com exatamente
k elementos:
int *s;
void Combinacoes (int n, int k) {
s = malloc ((k + 1) * sizeof (int));
rec (0, 1, k, n); }
void rec (int j, int m, int k, int n) {
if (j == k) imprima (k);
else {
if (m <= n - k + j + 1) {
s[j+1] = m;
rec (j + 1, m + 1, k, n);
rec (j, m + 1, k, n); } } }

13.2.1 Palavra *xxxxx e texto xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. (Generalize este


exemplo.)

13.3.3 Tente a sentinela b[n+1] = b[n].


196 ALGORITMOS em linguagem C ELSEVIER

14.2.6 A funo recebe o endereo r de uma rvore binria no vazia e devolve o


endereo do primeiro n na ordem e-r-d:
n *Primeiro (rvore r) {
while (r->esq != NULL) r = r->esq;
return r; }

15.3.1 A funo abaixo recebe o endereo eer do endereo de uma rvore de busca e
o endereo de um n avulso novo. Insere novo no lugar correto da rvore e atualiza
*eer de modo que esse seja o endereo da nova rvore. (Veja o livro de Masters [12].)
void Insere (rvore *eer, n *novo) {
n *p, *r;
r = *eer; p = NULL;
while (*eer != NULL) {
p = *eer;
if (p->chave > novo->chave) eer = &(p->esq);
else eer = &(p->dir); }
*eer = novo;
if (p != NULL) *eer = r; }

15.3.2 Recebe uma rvore de busca r e n avulso novo com campos chave, esq e dir
preenchidos. Insere novo e devolve a raiz da nova rvore de busca.
rvore InsereR (rvore r, no *novo) {
if (r == NULL) return novo;
else {
if (r->chave > novo->chave) r->esq = InsereR (r->esq, novo);
else r->dir = InsereR (r->dir, novo);
return r; } }

A.1.1 Fonte tipogrca de espaamento varivel no adequada para exibir cdigo de


programas. muito melhor usar fonte de espaamento fixo.

A.3.2 Leiaute corrigido:


Em 1959 e nas dcadas seguintes nenhum programador Cobol
poderia imaginar que os programas de computador que estava
criando ainda estariam em operao no fim do sculo. Poucos se
lembram hoje de que os primeiros PCs possuam apenas 64 Kbytes
de memria. Como a quantidade de memria disponvel era
pequena, usavam-se muitos truques para economizar esse recurso.
Para representar o ano, armazenava-se (por exemplo) "85" em vez
de "1985". Com a chegada do ano 2000, essa codificao
econmica transformou-se em um erro em potencial.

A.3.4 Leiaute corrigido:


esq = 0; dir = N - 1;
i = (esq + dir)/2; /* ndice do "meio" de R[] */
while (esq <= dir && R[i] != X) {
ELSEVIER Apndice L. Solues de alguns exerccios 197

if (R[i] < X) esq = i + 1;


else dir = i - 1; /* novo ndice do "meio" de R[] */
i = (esq + dir)/2;
}

I.1.1 Os nmeros produzidos por rand no so verdadeiramente aleatrios. No se


pode garantir que eles sejam aleatoriamente pares e mpares. Dependendo da imple-
mentao, os nmeros gerados por rand podem ser alternadamente pares e mpares.

I.1.2 Dependendo do valor de RAND_MAX, expresses da forma RAND_MAX * i podem


produzir um overow aritmtico.
Bibliograa

[1] J. L. Bentley. More Programming Pearls: Confessions of a Coder. Addison-


Wesley, 1988.

[2] J. L. Bentley. Programming Pearls. ACM Press, 2a edio, 2000.

[3] R. S. Boyer and J. S. Moore. A fast string searching algorithm. Commu-


nications of the ACM, 20:762772, 1977.

[4] C. Charras and T. Lecroq. Exact String Matching Algorithms. Internet:


http://www-igm.univ-mlv.fr/~lecroq/string/index.html.

[5] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to


Algorithms. MIT Press and McGraw-Hill, 2a edio, 2001.

[6] E. W. Dijkstra. On the cruelty of really teaching computing science. Ma-


nuscript EWD1036, 1988. In: The E. W. Dijkstra Archive, http://www.cs.
utexas.edu/users/EWD/index10xx.html.

[7] D. Gries. The Science of Programming. Springer, 1981.

[8] J. Harrison. Sorting Algorithms. Internet: http://www.cs.ubc.ca/spider/


harrison/Java/sorting-demo.html.

[9] C. A. R. Hoare. Quicksort. Computer Journal, 5:1015, 1962.

[10] D. E. Knuth. Fundamental Algorithms, volume 1 of The Art of Computer


Programming. Addison-Wesley, 2a edio, 1973.

[11] D. E. Knuth and S. Levy. The CWEB System of Structured Documenta-


tion. Addison-Wesley, 1994. Internet page: http://www-cs-staff.stanford.
edu/~knuth/cweb.html.

[12] D. Masters. C: An Introduction with Advanced Applications. Prentice Hall,


1991.
200 ALGORITMOS em linguagem C ELSEVIER

[13] P. Morin. Sorting Algorithms. Internet: http://cg.scs.carleton.ca/~morin/


misc/sortalg/.

[14] Problem Set Archive. Internet: http://online-judge.uva.es/problemset/.

[15] E. S. Roberts. The Art and Science of C: a Library-Based Introduction to


Computer Science. Addison-Wesley, 1995.

[16] E. S. Roberts. Programming Abstractions in C: a Second Course in Com-


puter Science. Addison-Wesley, 1998.

[17] F. Ruskey. Combinatorial Object Server. Internet: http://theory.cs.uvic.


ca/cos.html.

[18] R. Sedgewick. Algorithms in C, Parts 14. Addison-Wesley Longman,


3a edio, 1998.

[19] S. S. Skiena and M. A. Revilla. Programming Challenges (The Pro-


gramming Contest Training Manual). Springer, 2003. Internet: http:
//www.programming-challenges.com/.

[20] R. Ueda. Dicionrio br.ispell. Internet: http://www.ime.usp.br/~ueda/br.


ispell/.

[21] Wikipedia. Internet: http://en.wikipedia.org/.

[22] J. W. J. Williams. Algorithm 232 (Heapsort). Communications of the


ACM, 7:347348, 1964.

[23] N. Ziviani. Projeto de Algoritmos (com implementaes em Pascal e C).


Thomson, 2a edio, 2004.
Termos tcnicos em ingls

address endereo
array vetor
binary search busca binria
binary tree rvore binria
break interrompa, escape
call by value invocao por valor
cell clula
character caractere
dangling pointer ponteiro solto
data structure estrutura de dados
data type tipo de dados
de-queue remover da la
delete remover, apagar
depth profundidade
derangement desarranjo
do faa
else seno
enqueue inserir na la
eld campo
le arquivo
oor piso
for para
head cell clula-cabea (de lista)
header le arquivo-interface
heap heap
if se
indentation indentao
inorder traversal varredura e-r-d
input entrada
instance instncia
layout leiaute
leaf folha (de rvore)
length comprimento
linked list lista encadeada
merge intercalar
202 ALGORITMOS em linguagem C ELSEVIER

node n
output sada
overow transbordamento
performance desempenho
pointer ponteiro, apontador
pop (from stack) desempilhar
postorder traversal varredura e-d-r
preorder traversal varredura r-e-d
push (on stack) empilhar
queue la
random aleatrio
randomized aleatorizado
record registro, struct
return devolva
root raiz
scan varrer, esquadrinhar
search tree rvore de busca
seed semente
sort ordenar
sorting ordenao
stable sort ordenao estvel
stack pilha
string string
string matching busca de palavra
string searching busca de palavra
struct registro, struct
top (of stack) topo (de pilha)
twos-complement complemento-de-dois
while enquanto
wrapper function funo-embalagem