Sei sulla pagina 1di 22

PUC Minas – 1º Semestre 2011

Análise algoritmos Prim, Dikstra e Kruskal


Algoritmo de Prim

Pseudocódigo:

Entrada: Um grafo ponderado associado com todos os diferentes pesos.


Saída: As extremidades de uma árvore geradora mínima.

i: = 1;
N: = tamanho do gráfico de entrada, o número de vértices

while (i <N) do

escolhe a borda peso mínimo entre todos um que ainda não foram escolhidos e são incidentes alguém que já faz parte da
produção;

se a saída ainda está vazia, ou seja, estamos na primeira iteração, então escolhe a ponta para baixo peso

fim se;

se A não é um ciclo com aqueles que fazem parte da saída, então A acrescentar à saída;
i: = i + 1;

fim se;

fim while;

Neste caso, podemos observar que o algoritmo de Prim a cada iteração constrói uma árvore que cresce até completar a
árvore geradora mínima do grafo de entrada.
Algoritmo de Prim implementado em JAVA:

import java.util.Scanner;

class Prim {

private static int [][]vetor= new int[6][6];


static Scanner key= new Scanner(System.in);
static int aresta, vertice= 6;

/** Desenvolve a matriz adjacente */


private static void Leitura(){
for(int l=0; l< vertice; l++)
for(int c=0; c< l; c++){
if(l==c)
vetor[l][c]= 0; // Zerar a diagonal da matriz
else{
System.out.print("Entre com o peso da aresta entre os vertices "+ (l+1) +",
"+ (c+1) +": ");
aresta= key.nextInt();
vetor[l][c]= aresta; // Lê os valores para o triangulo superior
vetor[c][l]= aresta; // Copias os mesmos valores para o triangulo inferior,
} // visto que G[X, y)== G[Y, X] em um grafo.
}

System.out.println("\nA Matriz digitada foi:");


System.out.print(" ");
for(int ct=1; ct<= vertice; ++ct) // Perfumaria
System.out.print(" "+ ct);

for(int l=0; l< vertice; l++){


System.out.println();
System.out.print((l+1) +" ");
for(int c=0; c< vertice; c++)
System.out.print(vetor[l][c] +" "); // Imprime a matriz
}
}

/** Verifica se o valor já se encontra no vetor solução */


private static boolean Pertence(int vertices[], int valor){
for(int ct=0; ct< vertice; ct++)
if(vertices[ct]== valor)
return true;

return false;
}

private static int setPrim(){


int[] vertices= new int[6];
int[] custos= new int[5];
int custo = 0;
int total= 0;
int cont= 1;
boolean n_acabou= false;
vertices[0]= 0;

Leitura();

while(n_acabou){
int menor= 10; // Maior peso das arestas.
int aux= 0;

while(aux < cont){


for(int ct= vertices[aux]+1; ct< vertice; ct++){
if((vetor[vertices[aux]][ct] < menor)&&(!Pertence(vertices, ct))){
menor= ct;
custo= vetor[vertices[aux]][ct];
}
aux++;
}
vertices[cont]= menor;
custos[cont-1]= custo;
cont++;

if(cont == vertice)
n_acabou = false;

} // Fecha while aux


}// Fecha While n_acabou

System.out.print("\n\nCustos: [");
for(int ct=0; ct< vertice-1; ct++){
total= total+custos[ct];
System.out.print(" "+ custos[ct]);
}
System.out.print("]");
return total;
}
public static void main(String[] args){
System.out.println("\n\nCusto Total: "+ setPrim());
}
}
Algoritmo de Kruskal

Pseudocódigo:

Entrada: Um grafo ponderado associado com todos os diferentes pesos.


Saída: As extremidades de uma árvore geradora mínima.

i: = 1;
N: = tamanho do gráfico de entrada, o número de vértices

while (i <N) do

escolhe a borda peso mínimo entre todos um que ainda não foram escolhidos;
se A não é um ciclo com aqueles que fazem parte da saída, então

A acrescenta à saída;

i: = i + 1;

Fim se;

fim while;

Portanto, como observamos, o algoritmo de Kruskal irá gerar florestas diferentes, com arestas que vai selecionar e depois
são unidos para formar a árvore geradora mínima.

Algoritmo de Kruskal implementado em JAVA:


import java.io.*;
import java.util.*;

public class Kruskal {


private final int MAX_NODES = 21;
private HashSet nodes[]; // Matriz de Componentes
private TreeSet allEdges; // Prioridade da fila de arestas
private Vector allNewEdges; // Edges na arvore minima geradora

public static void main(String args[]) {


System.out.println("Running [Kruskal] - 2000");
if (args.length != 1) {
System.out.println("Usage: java Kruskal <fileName>");
System.exit(0);
}
Kruskal k = new Kruskal();
k.readInGraphData(args[0]);
k.performKruskal();
k.printFinalEdges();
}

Kruskal() {
// Constructor
nodes = new HashSet[MAX_NODES]; // Cria matriz de componentes
allEdges = new TreeSet(new Edge()); // Cria fila de prioridade vazia
allNewEdges = new Vector(MAX_NODES); // Cria vetor para as bordas da arvore minima
geradora.
}

private void readInGraphData(String fileName) {


try {
FileReader file = new FileReader(fileName);
BufferedReader buff = new BufferedReader(file);
String line = buff.readLine();
while (line != null) {
StringTokenizer tok = new StringTokenizer(line, " ");
int from = Integer.valueOf(tok.nextToken()).intValue();
int to = Integer.valueOf(tok.nextToken()).intValue();
int cost = Integer.valueOf(tok.nextToken()).intValue();

allEdges.add(new Edge(from, to, cost)); // Atualiza fila de prioridade


if (nodes[from] == null) {
// Cria um conjunto de componentes para os nós
nodes[from] = new HashSet(2*MAX_NODES);
nodes[from].add(new Integer(from));
}

if (nodes[to] == null) {
// Cria um conjunto de componentes para os nós
nodes[to] = new HashSet(2*MAX_NODES);
nodes[to].add(new Integer(to));
}

line = buff.readLine();
}
buff.close();
} catch (IOException e) {
//
}
}

private void performKruskal() {


int size = allEdges.size();
for (int i=0; i<size; i++) {
Edge curEdge = (Edge) allEdges.first();
if (allEdges.remove(curEdge)) {
// Remoção bem sucedida da fila de prioridades: todas as arestas

if (nodesAreInDifferentSets(curEdge.from, curEdge.to)) {
// System.out.println("Os nós estão em grupos diferentes...");
HashSet src, dst;
int dstHashSetIndex;

if (nodes[curEdge.from].size() > nodes[curEdge.to].size()) {


// tem que transferir todos os nós
src = nodes[curEdge.to];
dst = nodes[dstHashSetIndex = curEdge.from];
} else {
// tem que transferir todos os nós
src = nodes[curEdge.from];
dst = nodes[dstHashSetIndex = curEdge.to];
}

Object srcArray[] = src.toArray();


int transferSize = srcArray.length;
for (int j=0; j<transferSize; j++) {
// mover cada nó do conjunto
// e atualização do índice na matriz: nós
if (src.remove(srcArray[j])) {
dst.add(srcArray[j]);
nodes[((Integer) srcArray[j]).intValue()] = nodes[dstHashSetIndex];
} else {
// Este é um problema sério
System.out.println("Something wrong: set union");
System.exit(1);
}
}

allNewEdges.add(curEdge);
// Adicionar nova aresta a arvore geradora mínima
} else {
// System.out.println("Os nós estão no mesmo conjunto...não há nada a fazer
aqui");
}

} else {
// Este é um problema sério
System.out.println("TreeSet should have contained this element!!");
System.exit(1);
}
}
}
private boolean nodesAreInDifferentSets(int a, int b) {
// retorna verdadeira se os nós do grafo (a,b) forem diferentes
// components ligados, ou seja, conjunto de ‘a’ é diferente
// do que para 'b'
return(!nodes[a].equals(nodes[b]));
}

private void printFinalEdges() {


System.out.println("The minimal spanning tree generated by "+
"\nKruskal's algorithm is: ");
while (!allNewEdges.isEmpty()) {
// para cada aresta no vetor de arestas da arvore geradora mínima
Edge e = (Edge) allNewEdges.firstElement();
System.out.println("Nodes: (" + e.from + ", " + e.to +
") with cost: " + e.cost);
allNewEdges.remove(e);
}
}
class Edge implements Comparator {
// Classe interna para a borada representando arestas + pontos finais
public int from, to, cost;
public Edge() {
// Construtor padrão para a criação da árvore geradora
}
public Edge(int f, int t, int c) {
// Construtor da classe interna
from = f; to = t; cost = c;
}
public int compare(Object o1, Object o2) {
// usado para comparação ao incluir e remover operações
int cost1 = ((Edge) o1).cost;
int cost2 = ((Edge) o2).cost;
int from1 = ((Edge) o1).from;
int from2 = ((Edge) o2).from;
int to1 = ((Edge) o1).to;
int to2 = ((Edge) o2).to;

if (cost1<cost2)
return(-1);
else if (cost1==cost2 && from1==from2 && to1==to2)
return(0);
else if (cost1==cost2)
return(-1);
else if (cost1>cost2)
return(1);
else
return(0);
}
public boolean equals(Object obj) {
// usado para comparação ao incluir e remover operações
Edge e = (Edge) obj;
return (cost==e.cost && from==e.from && to==e.to);
}
}
}
Algoritmo de Dijkstra
Pseudocódigo:

custo[0..N] = INF
foi = {}

custo[INI] = 0
foi = foi+{INI} /* uma notação para: INI foi incluído no conjunto foi */
k = INI

while(FIM n 㯼/span> pertence a foi) {


para cada vizinho i de k {
se (i n 㯼/span> pertence a foi)
se (custo[i] > custo[k] + grafo[k][i]) /* relaxamento */
custo[i] = custo[k] + grafo[k][i]
}

/* escolhe o vértice não pertencente a foi, mas que possui menor custo */
k = vértice tal que custo[k] == min(custo[i]),
sendo i um vértice não/span > pertencente a foi
aux = custo[k]

foi = foi+{aux}
}

Exemplos
Observe este exemplo abaixo, mostrando passo-a-passo o Dijkstra rodando. Tente acompanhar observando o pseudo-
código:

Notas:
- o conjunto foi é simbolizado pelos vértices em vermelho;
- os custos custo[i] estão escritos dentro dos vértices;
- ini = 1 e fim = 6.

1. o grafo inicial 2. inicializando os custos

3. foi = foi+{ini} 4. relaxando os vértices vizinhos ao 1


5. 3 é o vértice de menor custo; foi =
6. relaxando os vértices vizinhos ao 3
foi+{3}

7. 2 é o vértice de menor custo; foi =


8. relaxando os vértices vizinhos ao 2
foi+{2}

9. 4 é o vértice de menor custo; foi = 10. relaxando os vértices vizinhos ao 4


foi+{4}
11. 5 é o vértice de menor custo; foi =
12. relaxando os vértices vizinhos ao 5
foi+{5}

13. 6 é o vértice de menor custo; foi = 14. fim alcançado


foi+{6} fim do algoritm
Algoritmo de Dijkstra implementado em JAVA:
import java.io.*;
import java.util.*;

public class Dijkstra {

//vertice inicial
private static String u;
//conjunto auxiliar T de vertices
private static ArrayList T = new ArrayList();
//infinito
private static final int infi = 100000000;
//conjunto V de vertices
private static ArrayList V = new ArrayList();
//Matriz de adjacencia [lin][col] , -1 senao existe aresta e > -1 = peso
private static long mAdj[][];
//distancia de u a v, L[] sincronizado com V
private static long L[];

//---------------------------------------------------------------

public static void main(String args[]) {


readFile("g1.txt");
//printMAdj();
run("a", false);
}

//---------------------------------------------------------------

private static void readFile(String fname) {

try{

RandomAccessFile raf = new RandomAccessFile(fname, "r");


byte[] b = new byte[(int)raf.length()];
raf.read(b);
raf.close();
//arquivo todo lido pra dentro de s
String s = new String(b);
//vertices A, B, C ...
String Vert = s.substring(0, s.indexOf("/")-1).trim();
//preenche V
parseV(Vert);
//arestas AB, AC, ...
String Edges = s.substring(s.indexOf("/")+1).trim();
//preenche matriz de adjacencias
parseE(Edges);

}
catch (FileNotFoundException e){
System.out.println("erro: Arquivo " + fname + " n 㯼 encontrado ");
}
catch (IOException e) {
System.out.println("erro: " + e);
}
catch (IndexOutOfBoundsException e) {
System.out.println("Arquivo " + fname + " invalido.");
}

//---------------------------------------------------------------

//recebe string de vertices e seta V


private static void parseV(String vert) {

int i=0;
String aux = new String("");
while (i vert.length()) {

if (vert.charAt(i) != ',')
aux += vert.charAt(i);
else {
V.add(aux.trim());
aux = "";
}

i++;
}

V.add(aux.trim());
}

//---------------------------------------------------------------

//recebe string de arestas e seta matriz de adj


private static void parseE(String arestas) {

//inicializa matriz de adjacencias com dimensao nxn


mAdj = new long[V.size()][V.size()];

//inicializa
for (int x=0; x V.size(); x++)
for (int y=0; y V.size(); y++)
mAdj[x][y] = infi;

int i=0;
String aux = new String("");
int r, s, p;

while (i arestas.length()) {

if (arestas.charAt(i) != '\n')
aux += arestas.charAt(i);
else{
//aux contem "v1-v2=p"
aux = aux.trim();
//posicao de v1 em V
r = V.indexOf(aux.substring(0, aux.indexOf('-')).trim());
//posicao de v2 em V
s = V.indexOf(aux.substring(aux.indexOf('-')+1, aux.indexOf('=')).trim());
//peso p de v1 para v2
p = Integer.parseInt(aux.substring(aux.indexOf('=')+1).trim());
//marca aresta (v1, v2) em mAdj
mAdj[r][s] = p;
aux = "";
}

i++;
}

aux = aux.trim();
//posicao de v1 em V
r = V.indexOf(aux.substring(0, aux.indexOf('-')).trim());
//posicao de v2 em V
s = V.indexOf(aux.substring(aux.indexOf('-')+1, aux.indexOf('=')).trim());
//peso p de v1 para v2
p = Integer.parseInt(aux.substring(aux.indexOf('=')+1).trim());
//marca aresta (v1, v2) em mAdj
mAdj[r][s] = p;

//---------------------------------------------------------------

//imprime a matriz de adj


private static void printMAdj(){
for (int i=0; i V.size(); i++) {
System.out.println('\n');
for (int j=0; j V.size(); j++)
if (mAdj[i][j] != infi)
System.out.print(" " + mAdj[i][j]);
else
System.out.print(" " + "-");
}

System.out.print("\n");
}
//---------------------------------------------------------------

//calcula o L(v)
private static boolean buildL() {

try {
//posicao de u na matriz de adj
int pos = V.indexOf(u);
//L tem dimensao n
L = new long[V.size()];

for (int i=0; i V.size(); i++)


L[i] = mAdj[pos][i];

//L(u)
L[pos] = 0;
}
catch (IndexOutOfBoundsException e) {
System.out.println(u + " nao esta em V");
return false;
}

return true;

//---------------------------------------------------------------

//imprime L(v)
private static void printL() {
System.out.print("\n");
for (int i=0; i V.size(); i++) {
if (L[i] != infi)
System.out.print(" " + L[i]);
else
System.out.print(" -");
}
}

//imprime L(v)
private static void printL2() {
System.out.print("\n");
for (int i=0; i V.size(); i++)
if (L[i] != infi)
System.out.println("L(" + V.get(i) + ")= " + L[i]);
else
System.out.println("L(" + V.get(i) + ")= infi");
}

//---------------------------------------------------------------

//executa o algo de dijkstra


private static void run(String vInicial, boolean debug) {

System.out.println("Distancia a partir de " + vInicial);


u = vInicial;

//constroi L(v)
if (!buildL()) return;

T.add(u); // T <- {u}

if (debug)
for (int k=0; k V.size(); k++)
System.out.print(" " + V.get(k));

while (!T.containsAll(V)) {
if (debug) printL();
String vLinha = findMinL();
T.add(vLinha);
updateL(vLinha);
}

if (debug)
System.out.println("\nfim");
else
printL2();

//---------------------------------------------------------------

private static void printV() {


System.out.print("\n");
for (int k=0; k V.size(); k++)
System.out.print(" " + V.get(k));

private static void printT() {


System.out.print("\n");
for (int k=0; k T.size(); k++)
System.out.print(" " + T.get(k));
}

//---------------------------------------------------------------

private static void updateL(String vLinha) {

int i;
//V2 = V
ArrayList V2 = new ArrayList(V);

//remove T de V = V2
for (i=0; i T.size(); i++)
V2.remove(V2.indexOf(T.get(i)));
for (i=0; i V2.size(); i++) {
String vaux = (String)V2.get(i);
long w = L[V.indexOf(vLinha)] + mAdj[V.indexOf(vLinha)][V.indexOf(vaux)];

if (w L[V.indexOf(vaux)])
L[V.indexOf(vaux)] = w;
}

//---------------------------------------------------------------
//retorna v nao pertencente a T tal que L(v) 頭 inimo
private static String findMinL() {

int i;
//V2 = V
ArrayList V2 = new ArrayList(V);

//remove T de V = V2
for (i=0; i T.size(); i++)
V2.remove(V2.indexOf(T.get(i)));

long pmin = infi;


String v = "";

//procuro em V2 o L(v) minimo


for (i=0; i V2.size(); i++) {

String vaux = (String)V2.get(i);

if (L[V.indexOf(vaux)] = pmin) {
v = vaux;
pmin = L[V.indexOf(vaux)];
}

return v;
}

import java.io.*;
import java.util.*;

public class Dijkstra {

//vertice inicial
private static String u;
//conjunto auxiliar T de vertices
private static ArrayList T = new ArrayList();
//infinito
private static final int infi = 100000000;
//conjunto V de vertices
private static ArrayList V = new ArrayList();
//Matriz de adjacencia [lin][col] , -1 senao existe aresta e > -1 = peso
private static long mAdj[][];
//distancia de u a v, L[] sincronizado com V
private static long L[];

//---------------------------------------------------------------

public static void main(String args[]) {


readFile("g1.txt");
//printMAdj();
run("a", false);
}

//---------------------------------------------------------------

private static void readFile(String fname) {

try{

RandomAccessFile raf = new RandomAccessFile(fname, "r");


byte[] b = new byte[(int)raf.length()];
raf.read(b);
raf.close();
//arquivo todo lido pra dentro de s
String s = new String(b);
//vertices A, B, C ...
String Vert = s.substring(0, s.indexOf("/")-1).trim();
//preenche V
parseV(Vert);
//arestas AB, AC, ...
String Edges = s.substring(s.indexOf("/")+1).trim();
//preenche matriz de adjacencias
parseE(Edges);

}
catch (FileNotFoundException e){
System.out.println("erro: Arquivo " + fname + " n 㯼 encontrado ");
}
catch (IOException e) {
System.out.println("erro: " + e);
}
catch (IndexOutOfBoundsException e) {
System.out.println("Arquivo " + fname + " invalido.");
}

//---------------------------------------------------------------

//recebe string de vertices e seta V


private static void parseV(String vert) {
int i=0;
String aux = new String("");
while (i vert.length()) {

if (vert.charAt(i) != ',')
aux += vert.charAt(i);
else {
V.add(aux.trim());
aux = "";
}

i++;
}

V.add(aux.trim());
}

//---------------------------------------------------------------

//recebe string de arestas e seta matriz de adj


private static void parseE(String arestas) {

//inicializa matriz de adjacencias com dimensao nxn


mAdj = new long[V.size()][V.size()];

//inicializa
for (int x=0; x V.size(); x++)
for (int y=0; y V.size(); y++)
mAdj[x][y] = infi;

int i=0;
String aux = new String("");
int r, s, p;

while (i arestas.length()) {

if (arestas.charAt(i) != '\n')
aux += arestas.charAt(i);
else{
//aux contem "v1-v2=p"
aux = aux.trim();
//posicao de v1 em V
r = V.indexOf(aux.substring(0, aux.indexOf('-')).trim());
//posicao de v2 em V
s = V.indexOf(aux.substring(aux.indexOf('-')+1, aux.indexOf('=')).trim());
//peso p de v1 para v2
p = Integer.parseInt(aux.substring(aux.indexOf('=')+1).trim());
//marca aresta (v1, v2) em mAdj
mAdj[r][s] = p;
aux = "";
}

i++;
}

aux = aux.trim();
//posicao de v1 em V
r = V.indexOf(aux.substring(0, aux.indexOf('-')).trim());
//posicao de v2 em V
s = V.indexOf(aux.substring(aux.indexOf('-')+1, aux.indexOf('=')).trim());
//peso p de v1 para v2
p = Integer.parseInt(aux.substring(aux.indexOf('=')+1).trim());
//marca aresta (v1, v2) em mAdj
mAdj[r][s] = p;

//---------------------------------------------------------------

//imprime a matriz de adj


private static void printMAdj(){
for (int i=0; i V.size(); i++) {
System.out.println('\n');
for (int j=0; j V.size(); j++)
if (mAdj[i][j] != infi)
System.out.print(" " + mAdj[i][j]);
else
System.out.print(" " + "-");
}

System.out.print("\n");
}

//---------------------------------------------------------------

//calcula o L(v)
private static boolean buildL() {

try {
//posicao de u na matriz de adj
int pos = V.indexOf(u);
//L tem dimensao n
L = new long[V.size()];

for (int i=0; i V.size(); i++)


L[i] = mAdj[pos][i];

//L(u)
L[pos] = 0;
}
catch (IndexOutOfBoundsException e) {
System.out.println(u + " nao esta em V");
return false;
}

return true;

//---------------------------------------------------------------
//imprime L(v)
private static void printL() {
System.out.print("\n");
for (int i=0; i V.size(); i++) {
if (L[i] != infi)
System.out.print(" " + L[i]);
else
System.out.print(" -");
}
}

//imprime L(v)
private static void printL2() {
System.out.print("\n");
for (int i=0; i V.size(); i++)
if (L[i] != infi)
System.out.println("L(" + V.get(i) + ")= " + L[i]);
else
System.out.println("L(" + V.get(i) + ")= infi");
}

//---------------------------------------------------------------

//executa o algo de dijkstra


private static void run(String vInicial, boolean debug) {

System.out.println("Distancia a partir de " + vInicial);

u = vInicial;

//constroi L(v)
if (!buildL()) return;

T.add(u); // T <- {u}

if (debug)
for (int k=0; k V.size(); k++)
System.out.print(" " + V.get(k));

while (!T.containsAll(V)) {
if (debug) printL();
String vLinha = findMinL();
T.add(vLinha);
updateL(vLinha);
}

if (debug)
System.out.println("\nfim");
else
printL2();

//---------------------------------------------------------------
private static void printV() {
System.out.print("\n");
for (int k=0; k V.size(); k++)
System.out.print(" " + V.get(k));

private static void printT() {


System.out.print("\n");
for (int k=0; k T.size(); k++)
System.out.print(" " + T.get(k));
}

//---------------------------------------------------------------

private static void updateL(String vLinha) {

int i;
//V2 = V
ArrayList V2 = new ArrayList(V);

//remove T de V = V2
for (i=0; i T.size(); i++)
V2.remove(V2.indexOf(T.get(i)));

for (i=0; i V2.size(); i++) {


String vaux = (String)V2.get(i);
long w = L[V.indexOf(vLinha)] + mAdj[V.indexOf(vLinha)][V.indexOf(vaux)];

if (w L[V.indexOf(vaux)])
L[V.indexOf(vaux)] = w;
}

//---------------------------------------------------------------
//retorna v nao pertencente a T tal que L(v) 頭 inimo
private static String findMinL() {

int i;
//V2 = V
ArrayList V2 = new ArrayList(V);

//remove T de V = V2
for (i=0; i T.size(); i++)
V2.remove(V2.indexOf(T.get(i)));

long pmin = infi;


String v = "";

//procuro em V2 o L(v) minimo


for (i=0; i V2.size(); i++) {
String vaux = (String)V2.get(i);

if (L[V.indexOf(vaux)] = pmin) {
v = vaux;
pmin = L[V.indexOf(vaux)];
}

return v;
}

REFERÊNCIAS
Algoritmos simples de Kruskal y Prim. Disponível em: < http://tracer.lsi.upc.es/kp/SimpleAlgorithmsPage.htm#Prim>
Acesso em 03 mar. 2011.

Fórum-Algoritmo de Prim. Disponível em: <http://javafree.uol.com.br/topic-880228-Algoritmo-de-Prim.html> Publicado


em: 09/12/2010 12:08:31. Acesso em 03 mar. 2011.

Algoritmos e exercícios resolvidos. Disponível em: <http://algoritmo.110mb.com/index.php?


p=artigo.php&a=Algoritmos/Grafos/dijkstra1.html>. Acesso em 03 mar. 2011.

Potrebbero piacerti anche