• Nenhum resultado encontrado

Outros algoritmos com vetores

No documento Algoritmos e Estruturas de Dados I (páginas 164-173)

Estruturas de dados

10.1. VETORES 139 programa¸c˜ ao que implementa a no¸c˜ ao de vetores tem que encontrar uma maneira para

10.1.5 Outros algoritmos com vetores

Nesta se¸c˜ao vamos apresentar alguns problemas interessantes que podem ser resolvidos usando-se a estrutura de vetores.

Permuta¸c˜oes

Vamos apresentar um problema matem´atico conhecido comopermuta¸c˜ao, propor uma representa¸c˜ao computacional em termos de vetores, e, em seguida, estudar alguns problemas interessantes do ponto de vista de computa¸c˜ao.5

Os matem´aticos definem uma permuta¸c˜ao de algum conjunto como uma fun¸c˜ao bijetora de um conjunto nele mesmo. Em outras palavras, ´e uma maneira de reor-denar os elementos do conjunto. Por exemplo, podemos definir uma permuta¸c˜ao do conjunto {1, 2, 3, 4, 5 } assim: P(1) = 4, P(2) = 1, P(3) = 5, P(4) = 2, P(5) = 3.

Esquematicamente temos:

1 2 3 4 5 4 1 5 2 3

Outra maneira seria: P(1) = 2, P(2) = 5, P(3) = 1, P(4) = 3, P(5) = 2. Esque-maticamente:

1 2 3 4 5 2 5 1 3 2

De fato, existemn! maneiras de se reordenar os elementos e obter uma permuta¸c˜ao v´alida. Se n = 5 ent˜ao existem 120 permuta¸c˜oes.

Modelando permuta¸c˜oes

O primeiro problema interessante do ponto de vista algoritmico ´ecomo representar

4O site http://cg.scs.carleton.ca/~morin/misc/sortalg permite visualizar o comporta-mento dos principais algoritmos de ordena¸ao atrav´es de anima¸oes. Os dois algoritmos aqui ex-plicados est˜ao l´a, entre outros.

5Esta se¸ao foi inspirada em uma preparat´oria para a maratona de programa¸ao da ACM da Ural State Universisy (Internal Contest October’2000 Junior Session) encontrada na seguinte URL:

http://acm.timus.ru/problem.aspx?space=1&num=1024.

10.1. VETORES 165 uma permuta¸c˜ao. Para isto pode-se usar um vetor de n posi¸c˜oes inteiras, onde cada posi¸c˜ao ´e um valor (sem repeti¸c˜ao) entre 1 e n. Em todos os algoritmos desta se¸c˜ao consideraremos que:

const min i = 1; max i = 5;

Assim os dois vetores que representam as permuta¸c˜oes acima s˜ao, respectivamente:

1 2 3 4 5

4 1 5 2 3

1 2 3 4 5

2 5 1 3 2

Testando permuta¸c˜oes v´alidas

Uma vez resolvido o problema da representa¸c˜ao, podemos estudar o pr´oximo de-safio, que ´ecomo testar se um dado vetor (lido do teclado) ´e uma permuta¸c˜ao v´alida?

O algoritmo tem que testar se, para os ´ındices de 1 a n, seus elementos s˜ao cons-titu´ıdos por todos, e apenas, os elementos entre 1 en, em qualquer ordem. A fun¸c˜ao da figura 10.26 apresenta uma poss´ıvel solu¸c˜ao.

function testa permutacao (var v : vetor i ; n: integer) : boolean;

var i , j : integer;

eh permutacao : boolean;

begin

eh permutacao:= true;

i := 1;

while eh permutacao and ( i<= n) do begin

j:= 1; (∗ procura se i esta no vetor ∗)

while (v [ j ] <> i ) and ( j<= n) do j:= j + 1;

i f v [ j ] <> i then (∗ se nao achou nao eh permutacao ∗) eh permutacao:= false;

i := i +1;

end;

testa permutacao:= eh permutacao ; end; (∗ testa permutacao ∗)

Figura 10.26: Verifica se um vetor define uma permuta¸c˜ao.

Este algoritmo testa para saber se cada ´ındice entre i e n est´a presente no vetor.

Para isto executa no pior caso algo da ordem do quadrado do tamanho do vetor.

No primeiro semestre de 2011 um estudante6 sugeriu que basta usar um vetor

6Bruno Ricardo Sella

auxiliar, inicialmente zerado, e percorrer o vetor candidato a permuta¸c˜ao apenas uma vez. Para cada ´ındice, tentar inserir seu respectivo conte´udo no vetor auxiliar: se esti-ver com um zero, inserir, sen˜ao, n˜ao ´e permuta¸c˜ao. Se o vetor auxiliar for totalmente preenchido ent˜ao temos um vetor que representa uma permuta¸c˜ao. Este processo ´e linear e est´a ilustrado na figura 10.27.

function testa permutacao v2 (var v : vetor i ; n: integer) : boolean;

var i : integer;

testa permutacao v2:= eh permutacao ; end; (∗ testa permutacao v2 ∗)

Figura 10.27: Verifica linearmente se um vetor define uma permuta¸c˜ao.

Outros estudantes sugeriram uma conjectura, n˜ao provada em sala, de que, se todos os elementos pertencem ao intervalo 1 ≤ v[i] ≤ n e Pn

i=1v[i] = n(n+1)2 e ao mesmo tempo Qn

i=1v[i] = n!, ent˜ao o vetor representa uma permuta¸c˜ao. Tamb´em n˜ao encontramos contra-exemplo e o problema ficou em aberto.

Gerando permuta¸c˜oes v´alidas

O pr´oximo problema ´e gerar aleatoriamente uma permuta¸c˜ao. Para isto faremos uso da fun¸c˜aorandom da linguagem Pascal.

O primeiro algoritmo gera um vetor de maneira aleat´oria e depois testa se o vetor produzido pode ser uma permuta¸c˜ao usando o c´odigo da fun¸c˜aotesta permutacao j´a implementado. A tentativa ´e reaproveitar c´odigo a qualquer custo. Este racioc´ınio est´a implementado no c´odigo da figura 10.28.

Este algoritmo ´e absurdamente lento quando n cresce. Isto ocorre pois os vetores s˜ao gerados e depois testados para ver se s˜ao v´alidos, mas, conforme esperado, ´e muito prov´avel que n´umeros repetidos sejam gerados em um vetor com grande n´umero de elementos. Um dos autores deste material teve paciˆencia de esperar o c´odigo terminar apenas at´e valores de npr´oximos de 14. Para valores maiores o c´odigo ficou infernalmente demorado, levando v´arias horas de processamento.

Numa tentativa de melhorar o desempenho, o que implica em abrir m˜ao da

como-10.1. VETORES 167

procedure gerar permutacao (var v : vetor i ; n: integer) ; var i : integer;

begin

randomize ;

repeat (∗ repete ate conseguir construir uma permutacao valida ∗) for i := 1 to n do

v [ i ]:= random (n) + 1; (∗ sorteia numero entre 1 e n ∗) until testa permutacao v2 (v , n) ;

end; (∗ gera permutacao ∗)

Figura 10.28: Gerando uma permuta¸c˜ao, vers˜ao 1.

didade de se aproveitar fun¸c˜oes j´a existentes, este mesmo autor pensou que poderia gerar o vetor de modo diferente: gerar um a um os elementos e testar se eles j´a n˜ao pertencem ao conjunto j´a gerado at´e a itera¸c˜ao anterior. Isto garante que o vetor final produzido ´e v´alido. A procedure da figura 10.29 apresenta a implementa¸c˜ao desta nova ideia.

procedure gerar permutacao v2 (var v : vetor i ; n: integer) ; var i , j : integer;

begin

randomize ;

v[1]:= random (n) + 1;

for i := 2 to n do repeat

v [ i ]:= random (n) + 1; (∗ gera um numero entre 1 e n ∗) j:= 1; (∗ procura se o elemento ja existe no vetor ∗) while ( j < i ) and (v [ i ] <> v [ j ] ) do

j:= j + 1;

until j = i ; (∗ descobre que o elemento eh novo ∗) end; (∗ gera permutacao v2 ∗)

Figura 10.29: Gerando uma permuta¸c˜ao, vers˜ao 2.

Este algoritmo executa na casa de 2 segundos para vetores de tamanho pr´oximos de 1000, mas demora cerca de 30 segundos para entradas de tamanho que beiram os 30.000. Para se pensar em tamanhos maiores a chance do tempo ficar insuport´avel ´e enorme. Mas j´a ´e melhor do que o anterior.

Queremos gerar um vetor que represente uma permuta¸c˜ao, provavelmente para fins de testes. Uma maneira poss´ıvel seria a seguinte: inicializa-se um vetor de forma ordenada, depois faz-se altera¸c˜oes aleat´orias de seus elementos um n´umero tamb´em aleat´orio de vezes. Esta ideia foi implementada e ´e mostrada na figura 10.30.

Este c´odigo produz corretamente vetores que representam permuta¸c˜oes com bom grau de mistura dos n´umeros em tempo praticamente constante para entradas da ordem de um milh˜ao de elementos (usando-se o tipo longint).7

7Se algu´em souber de um modo mais eficiente de gerar uma permuta¸ao, favor avisar. N˜ao s´o

procedure gerar permutacao v3 (var v : vetor i ; n: integer) ; var i , j , k , aux, max: integer;

begin

for i := 1 to n do v [ i ] := i ; randomize ;

max:= random (1000) ; (∗ vai trocar dois elementos de 0 a 999 vezes ∗) for i := 1 to maxdo

begin

j:= random (n) + 1;

k:= random (n) + 1;

aux:= v [ j ] ; v [ j ]:= v [ k ] ; v [ k]:= aux ; end;

end; (∗ gera permutacao v3 ∗)

Figura 10.30: Gerando uma permuta¸c˜ao, vers˜ao 3.

Em uma aula do segundo semestre de 2010 surgiu uma nova ideia para se gerar um vetor de permuta¸c˜ao.8

A sugest˜ao ´e modificar o algoritmo 10.29, fazendo com que um vetor auxiliar contenha os n´umeros ainda n˜ao colocados no vetor permuta¸c˜ao. O sorteio deixa de ser sobre o elemento a ser inserido, mas agora sobre o ´ındice do vetor auxiliar, cujo tamanho decresce na medida em que os n´umeros v˜ao sendo sorteados. O c´odigo da figura 10.31 ilustra estas ideias. Ele foi implementado durante a aula e possibilitou gerar vetores de tamanhos incrivelmente grandes em tempo extremamente curto.9

Determinando a ordem de uma permuta¸c˜ao

Antes de apresentarmos o pr´oximo problema do ponto de vista algoritmico a ser tratado precisamos introduzi-lo do ponto de vista matem´atico.

Observem que, uma vez que a fun¸c˜ao que define a permuta¸c˜ao ´e sobre o pr´oprio conjunto, ocorre que, se P(n) ´e uma permuta¸c˜ao, ent˜ao P(P(n)) tamb´em ´e. Logo,

´e poss´ıvel calcular o valor de express˜oes tais como P(P(1)). De fato, consideremos novamente a permuta¸c˜ao:

1 2 3 4 5 4 1 5 2 3

Ent˜ao pode-se calcular:

• P(P(1)) =P(4) = 2.

daremos os cr´editos necess´arios como tamb´em mostraremos os resultados aqui neste material.

8Cr´editos para o Felipe Z. do Nascimento.

9odigo e testes feitos na aula por Renan Vedovato Traba, a partir da ideia do Felipe.

10.1. VETORES 169

procedure gerar permutacao v4 (var v : vetor i ; n: longint ) ; var i , j , tam: longint ;

end; (∗ gera permutacao v4 ∗)

Figura 10.31: Gerando uma permuta¸c˜ao, vers˜ao 4.

• P(P(2)) =P(1) = 4.

• P(P(3)) =P(5) = 3.

• P(P(4)) =P(2) = 1.

• P(P(5)) =P(3) = 5.

Desta maneira, definimos P2(n) = P(P(n)). Em termos gerais, podemos definir o seguinte:

P1(n) =P(n);

Pk(n) =P(Pk−1(n)) k ≥2.

Dentre todas as permuta¸c˜oes, existe uma especial:

ID=

1 2 3 4 5 1 2 3 4 5

Isto ´e,P(i) =i,∀i. Esta permuta¸c˜ao recebe um nome especial ID. ´E poss´ıvel de-monstrar que, para quaisquerken,IDk(n) =ID(n). Tamb´em ´e poss´ıvel demonstrar que a senten¸ca seguinte tamb´em ´e v´alida:

Seja P(n) uma permuta¸c˜ao sobre um conjunto de n elementos. Ent˜ao existe um n´umero natural k tal que Pk = ID. Este n´umero natural ´e chamado da ordem da permuta¸c˜ao.

Vamos considerar como exemplo a permuta¸c˜ao acima. Podemos calcular para valores pequenos de k como ´e Pk:

P =

1 2 3 4 5 4 1 5 2 3

P2 =

Chegamos no ponto de apresentarmos o pr´oximo problema10. Dada uma per-muta¸c˜ao, encontrar sua ordem. Simular a sequˆencia de opera¸c˜oes e testar quando a identidade for encontrada, contando quantas opera¸c˜oes foram feitas ´e muito caro.

Tem que haver uma maneira melhor.

A fun¸c˜ao da figura 10.32 implementa um algoritmo que recebe como entrada uma permuta¸c˜ao (v´alida) e retorna sua ordem.

Este algoritmo parte da ideia de que cada elemento P(i) = x do conjunto re-torna `a posi¸c˜ao i ciclicamente, de cont em cont permuta¸c˜oes. Ou seja, Pcont(i) = x, P2×cont(i) = x, . . .. O mesmo ocorre para todos elementos do conjunto, mas cada um possui um ciclo (valor decont) pr´oprio.

function ordem permutacao (var v : vetor i ; n: integer) : int64 ; var mmc, cont : int64 ;

Figura 10.32: Calcula a ordem de uma permuta¸c˜ao.

10Este ´e o problema da Maratona da ACM.

10.1. VETORES 171 Para exemplificar, tomemos a permuta¸c˜ao acima. Para o ´ındice 1, temos que P3(1) = 1. Isto quer dizer que para todo m´ultiplo de 3 (a cada 3 itera¸c˜oes) ´e verdade queP3k(1) = 1. Isto tamb´em ocorre para os ´ındices 2 e 4. Mas para os ´ındices 3 e 5, o n´umero de itera¸c˜oes para que ocorra uma repeti¸c˜ao ´e de duas itera¸c˜oes. Logo, pode-se concluir que a permuta¸c˜ao ID ocorrer´a exatamente na itera¸c˜ao que ´e o m´ınimo m´ultiplo comum (MMC) entre o n´umero que provoca repeti¸c˜ao entre todos os ´ındices.

Observamos que:

M M C(x1, x2, . . . , xn) =M M C(x1, M M C(x2, . . . , xn).

Infelizmente, n˜ao existe algoritmo eficiente para c´alculo do MMC. Mas existe para o c´alculo do MDC (m´aximo divisor comum). De fato, implementamos o algoritmo de Euclides (figura 6.16, se¸c˜ao 6.4) e mostramos que ele ´e muito eficiente. Felizmente, a seguinte propriedade ´e verdadeira:

M DC(a, b) = a×b M M C(a, b)

O programa acima explora este fato e torna o c´odigo muito eficiente para calcular a ordem de permuta¸c˜oes para grandes valores de n. O estudante ´e encorajado aqui a gerar uma permuta¸c˜ao com os algoritmos estudados nesta se¸c˜ao e rodar o programa para valores de n compat´ıveis com os tipos de dados definidos (integer).

Polinˆomios

Nesta se¸c˜ao vamos mostrar como representar e fazer c´alculos com polinˆomios repre-sentados como vetores.

Para uma sucess˜ao de termos a0, ..., an ∈ R, podemos para este curso definir um polinˆomio de grau n como sendo uma fun¸c˜ao que possui a seguinte forma:

P(x) =anxn+an−1xn−1+. . .+a1x+a0

Vamos considerar ao longo desta se¸c˜ao que ∀k > n, ent˜ao ak = 0. Tamb´em consideramos que an 6= 0 para um polinˆomio de grau n.

Do ponto de vista computacional, uma poss´ıvel representa¸c˜ao para um polinˆomio

´

e um vetor den+ 1 elementos cujos conte´udos s˜ao os coeficientes reais dos respectivos monˆomios. Consideremos ent˜ao o tipo abaixo, podemos exemplificar alguns casos:

type polinomio =array [ 0 . .max] of real;

• P(x) = 5−2x+x2:

0 1 2

5 -2 1

• P(x) = 7−2x2+ 8x3−2x7

0 1 2 3 4 5 6 7 7 0 -2 8 0 0 0 -2

No restante desta se¸c˜ao iremos mostrar algoritmos que realizam opera¸c˜oes costu-meiras sobre polinˆomios. A primeira ´e calcular o valor de um polinˆomio em um dado ponto x∈R. Por exemplo, se P(x) = 5−2x+x2 ent˜ao, P(1) = 5−2×1 + 12 = 4.

A fun¸c˜ao em Pascal que implementa este c´alculo est´a apresentado na figura 10.33.

function valor no ponto (var p: polinomio ; graup : integer; x : real) : real; var soma, potx : real;

i : integer; begin

potx:= 1;

soma:= 0;

for i := 0 to graup do begin

soma:= soma + p[ i ]∗potx ; potx:= potx x ;

end;

valor no ponto:= soma;

end;

Figura 10.33: Calcula o valor de P(x) para um dado x∈R.

O pr´oximo algoritmo interessante ´e o c´alculo do polinˆomio derivada de um po-linˆomio P. Seja P0(x) a derivada de P(x) assim definido:

P0(x) =nanxn−1+ (n−1)an−1xn−2+. . .+ 2a2x+a1 O programa que implementa este c´alculo est´a na figura 10.34.

procedure derivar (var p: polinomio ; graup : integer; var d: polinomio ; var graud : integer) ; var i : integer;

begin

i f graup = 0 then begin

graud:= 0;

d[0]:= 0;

end else begin

graud:= graup 1;

for i := 0 to graud do d[ i ]:= ( i+1) p[ i +1];

end;

end;

Figura 10.34: Calcula o polinˆomio derivada de P(x).

10.1. VETORES 173

No documento Algoritmos e Estruturas de Dados I (páginas 164-173)