Representando Grafos na Programação

No último artigo, conhecemos a representação chamada “grafo” da seguinte maneira:

Grafo desenhado

Como todos sabemos, seria bem difícil trabalhar uma árvore assim na programação! Por isso, existem várias maneiras de representar um grafo. Nesta série só vou usar as duas mais populares:

  • Matriz de Adjacência
  • Lista de Adjacência

Poderíamos falar também sobre a Matriz de Incidência, mas eu nunca precisei utilizá-la, então prefiro só entrar nessas duas mesmo.

Cada vértice é um número

Para representar um grafo, cada vértice sempre vai ser um número. No caso de você querer representar amizade entre duas pessoas, como no exemplo do Orkut no último artigo, você cria um vetor chamado nome[] que contém o nome de cada número…

  1. Eu
  2. João
  3. Maria
  4. José
  5. Pedro

Matriz de Adjacência

A matriz de adjacência é uma matriz de N x N (onde N é o número de vértices do grafo). Ela inicialmente é preenchida toda com 0 e quando há uma relação entre o vértice do x (número da coluna) com o do y (número da linha), matriz[x][y] é marcado um 1.

Vamos escrever este grafo aqui usando uma matriz de adjacência:

Matriz Inicial

1 2 3 4 5
1
2
3
4
5

Relações do nosso grafo

Já que o grafo não é orientado, a relação 1–2 significa 2–1 também.

  • 1–2 (2–1)
  • 1–3 (3–1)
  • 2–3 (3–2)
  • 2–4 (4–2)
  • 4–5 (5–4)

Essas são as cinco arestas do nosso grafo. Vamos representá-la na matriz de adjacência:

1 2 3 4 5
1 1 1
2 1 1 1
3 1 1
4 1 1
5 1

Simetria

Uma das características da matriz de adjacência quando o grafo não é orientado é a simetria encontrada na “diagonal”. É interessante que se lermos uma coluna de índice v ou uma linha de índice v vamos encontrar a mesma coisa.

Problemas da OBI

Alguns desses programas são complicados, mas isto não entra em questão. Apenas dê uma olhada no recebimento da entrada deles. Todos estão em C. O que eles têm em comum é a utilização de uma matriz de adjacência para guardar o grafo (geralmente nomeada g):

* – Grafo orientado
+ – Grafo ponderado (veremos no próximo artigo)
X – Não queira ver esse problema. Nunca vi solução mais feia. Já estou providenciando uma implementação mais decente… ;)

Descobrir o grau de cada vértice

Eu não disse pra vocês que era fácil conseguir emprego no Orkut? Hehehe… Vamos pensar como podemos descobrir o grau (relembrando… o número de arestas que cada vértice tem OU o número de amigos que cada pessoa tem) na matriz de adjacências. Não basta contar quantos 1s tem na sua linha correspondente? Então façamos isto.

para i \leftarrow{} 1 até N, faça
grau \leftarrow{} 0
para j \leftarrow{} 1 até N, faça
  se matriz[i][j] = 1, então
   grau \leftarrow{} grau + 1
  fim-se
fim-para
imprima “O vértice ” i ” tem grau ” grau ”.”
fim-para

O custo é Θ(n2)\Theta{}(n^{2}) até no melhor caso… Será que não há uma maneira mais simples de fazer isso? Imagina um negócio do tamanho do Orkut. Milhões de pessoas… Não seria bem mais fácil se ao invés de termos que passar por todos os vértices, só passarmos pelos amigos? Não poderíamos colocar somente seus amigos num vetor? É por isto que utilizamos a lista de adjacência.

Lista de Adjacência

A lista de adjacência consiste em criar um vetor para cada vértice. Este vetor contém cada vértice que o vértice “conhece” (tem uma aresta para). Geralmente é representada com uma matriz, porque cada vértice vai precisar de um vetor diferente, não é? Já que eu não vou ser duas vezes “amigo” de ninguém, então podemos fazer uma matriz de NxN.

1 2 3 4 5
1
2
3
4
5

A lista consiste em escrever para cada número de linha (= vértice) seus amigos, da seguinte maneira:

  1. 2, 3
  2. 1, 3, 4
  3. 1, 2
  4. 2, 5
  5. 4

Portanto, na programação seria representado da seguinte maneira:

1 2 3 4 5
1 2 3
2 1 3 4
3 1 2
4 2 5
5 4

O método da lista de adjacências tem várias vantagens: dependendo de como você implementa você não precisa inicializar a lista (zerar), as buscas são bem mais rápidas (você só passa pelos vértices que são “amigos” do atual) e geralmente você já tem o grau do vértice na ponta da língua (eu, pelo menos, sempre uso um vetor cont que contém o número de amigos de cada vértice para ficar mais fácil inserir o próximo elemento na lista – lista[cont[v]++]=w).

Como implementar

Vamos trabalhar com uma entrada de vários x, y, indicando relação entre x-y (y-x) até que x=0 e y=0. O grafo não é orientado.

Matriz de Adjacências

para i \leftarrow{} 1 até N, faça
para j \leftarrow{} 1 até N, faça
  matriz[i][j] \leftarrow{} 0
fim-para
fim-para

enquanto (recebe x, y e x \neq{} 0), faça
matriz[x][y] \leftarrow{} 1
matriz[y][x] \leftarrow{} 1
fim-enquanto

Tem vários exemplos implementados em C [aqui][14].

Lista de Adjacências

para i \leftarrow{} 1 até N, faça
grau[i] \leftarrow{} 0
fim-para

enquanto (recebe x, y e x \neq{} 0), faça
lista[x]grau[x]++] \leftarrow{} y
lista[y]grau[y]++] \leftarrow{} x
fim-enquanto

Para quem não programa em C, o variavel++ significa “incrementar variavel depois da instrução atual”.

As duas juntas

para i \leftarrow{} 1 até N, faça
para j \leftarrow{} 1 até N, faça
  matriz[i][j] \leftarrow{} 0
fim-para
grau[i] \leftarrow{} 0
fim-para

enquanto (recebe x, y e x \neq{} 0), faça
matriz[x][y] \leftarrow{} 1
matriz[y][x] \leftarrow{} 1
lista[x]grau[x]++] \leftarrow{} y
lista[y]grau[y]++] \leftarrow{} x
fim-enquanto

Qual a representação que devo utilizar?

Isso depende totalmente do problema. Em alguns casos, o mais barato é usar as duas representações juntas. As vantagens da lista de adjacências eu já escrevi [aqui][15]. A única vantagem da matriz de adjacências é você em tempo constante (não é nem linear) saber se um vértice é amigo de outro. Afinal, basta testar matriz[v][w].

Até maio do ano passado, eu não tinha aprendido isso direito e sempre usava a matriz de adjacências. Por isso muitos dos meus problemas estão resolvidos de forma pouco eficiente… e isso pode ser crucial numa prova. Por isso, saiba usar as duas formas!

Os Grafos e o Orkut

Vou neste e nos próximos artigos falar-lhes sobre a Teoria dos Grafos. É uma coisa que poderia ser complicada, então pra facilitar o entendimento eu resolvi que trabalharemos sempre com exemplos da “vida real”. Neste artigo, vamos trabalhar com o Orkut como base, partindo do princípio que todo mundo sabe o que é o Orkut.

Neste primeiro artigo, só falarei sobre a definição do grafo e sua utilidade. Apresentarei as definições de: vértice, aresta, grau, grafo orientado, grau de entrada e grau de saída. Então, vamos lá!

A definição de grafo é muito simples. Segundo o Professor Cid Carvalho de Souza: Uma forma de representar uma relação binária entre elementos de um conjunto. Bom… É simplesmente uma representação de relações (que chamamos de arestas) entre objetos, que chamamos de vértices. Vamos logo ao exemplo: a amizade no Orkut.

Um grafo de amizades

Estão vendo esta árvore? Esta é a representação que chamamos de grafo. Vamos supor que este é o grafo das amizades do Orkut e as bolinhas nele são as seguintes pessoas:

  1. Eu
  2. João
  3. Maria
  4. José
  5. Pedro

Eu (número 1) tenho dois amigos: o João (número 2) e a Maria (número 3), porque estou ligado a eles. O João (número 2) tem três amigos: eu (número 1), a Maria (número 3) e o José (número 4). E assim podemos fazer com os outros contando as linhas que saem de cada pessoa.

Cada pessoa é um vértice e cada linha (relação entre duas pessoas) é uma aresta. Dizemos que é uma relação binária lá em cima, porque a relação é sempre entre dois vértices.

Os números de amigos que cada pessoa tem (o número de relações que cada vértice tem) é o que chamamos de “grau” de um vértice. Grau dos vértices do exemplo acima:

  1. 2
  2. 3
  3. 2
  4. 2
  5. 1

Pra quê serve o grafo?

Ora, se você consegue contar as arestas que saem de cada vértice na programação (se você sabe fazer algo básico como calcular o grau de um vértice), você pode oferecer seus serviços ao Google e ganhar milhões, pois, como todos sabem, o Orkut não sabe fazer isso direito!

Brincadeiras a parte… Grafos são extremamente úteis porque são representações bastante simples (você teve dificuldade para entender minha árvore ali em cima?) e essa estrutura deles aparece em muitos problemas computacionais. Além disso, existem muitos algoritmos eficientes para problemas complexos que utilizam estas representações.

Definições até agora

Traduzindo os conceitos do Orkut para os grafos:

  • Vértice: Pessoa.
  • Aresta: Amizade entre uma pessoa e outra.
  • Grau de um vértice: Número de amigos de uma pessoa.

Grafos Orientados

Um grafo é orientado quando eu sou seu amigo, mas você não é meu amigo. Você sabe que um grafo é orientado através da representação quando ele tem “setinhas”, como o grafo abaixo.

Grafo orientado

Vamos supor que isso aí é um mapa do Brasil. Ele despreza as cidades que não são importantes para o país, levando apenas em consideração portanto: São Paulo, Florianópolis e Itajaí.

  • São Paulo é a bolinha vermelha.
  • Florianópolis é a bolinha amarela.
  • Itajaí é a bolinha azul.

As arestas indicam que há uma estrada para você ir de uma cidade para a outra, mas só dá pra ir no sentido da estrada, que as setas representam. Portanto, você pode ir de São Paulo a Florianópolis, de São Paulo a Itajaí e Florianópolis a Itajaí, mas não de Itajaí para qualquer outro lugar.

Grau dos Vértices

Os graus dos vértices neste segundo grafo seriam os seguintes, certo?

  • São Paulo: 2
  • Florianópolis: 2
  • Itajaí: 2

Quase… Porém, nos grafos orientados nós temos dois tipos de grau diferentes. Dizemos que:

  • grau de entrada é o número de arestas que apontam para o vértice; e
  • grau de saída é o número de arestas que saem do vértice.

Portanto, os graus de entrada do nosso grafo são:

  • São Paulo: 0
  • Florianópolis: 1
  • Itajaí: 2

E os graus de saída:

  • São Paulo: 2
  • Florianópolis: 1
  • Itajaí: 0

Onde mais posso utilizar grafos?

Existem vários casos onde você vai querer usar grafos:

  • Mapas
  • Sua árvore genealógica
  • Hierarquia de uma empresa
  • Sistema de amizades do seu sistema de comunidades virtuais
  • … e muito mais. Na OBI já apareceu até um jogo de dominó como problema de grafos!

Como veremos nos próximos artigos, tem algoritmo pra fazer “tudo” em grafos… Então representar alguma coisa em grafos é muito útil pra poder descobrir uma série de coisas.

A maioria das páginas que eu conheço sobre grafos são muito malignas porque apresentam uns 50 conceitos diferentes de grafos juntos (ex.: grafo conexo, grafo desconexo, caminho, ciclo, etc.). Nos meus artigos, devo tratar um assunto de cada vez para facilitar o entendimento. Então, vou parar por aqui hoje.

No próximo artigo: Como representar um grafo na programação? Você já pode ir pensando nisso…

KDE 3.5 e DCOP

Ontem eu baixei e compilei o QT 4.1 e o KDE 3.5 aqui no laptop. Com todo mundo falando do Konqueror e seu maravilhoso suporte aos padrões web, isso era uma coisa que já tava na minha lista há algum tempo. Ontem foi a primeira vez que usei por padrão o KDE na minha máquina desde mais ou menos um ano, quando comecei a usar Fluxbox.

O KDE está muito legal. Muitas melhorias, menos bugs e um design bem mais “chique”. Os desenvolvedores estão fazendo um trabalho muito bom e não consigo deixar de pensar como será o KDE 4.

Aí eu acabei (espero) meus dois freelas que estavam em desenvolvimento e resolvi fazer uma aplicação Ajax para qualquer pessoa poder controlar meu amaroK. Óbvio que eu não quero que as pessoas controlem meu amaroK, mas depois de ter lido um pouco sobre dcop eu precisava fazer algo assim.

E, hoje de manhã, em menos de uma hora, a aplicação ficou pronta. Ela com certeza não é segura (só pra vocês terem noção do drama, eu permiti sudo ao usuário do Apache, hehehe) e ainda nem tá bonita nem boa o suficiente, mas foi só pra brincar um pouco.

Vou estudar um pouco mais sobre isso e depois quando eu tiver um código bem bonito, eu publico aqui. :D


Ontem resolvi um problema do USACO Training Gateway (não sei por que só um… Deu vontade na hora e depois eu cansei… hehehe) e já publiquei aqui: namenum.c. O nome é Name that Number e ele é sobre permutações. Se quer ler o enunciado, entre lá no USACO Training Gateway. :) Ah, o motivo de eu só ter resolvido ele agora (porque ele é de uma das primeiras seções lá) é que eles só colocaram esse problema agora. Quando eu tinha passado pela seção dele antes, ele ainda não existia.

Vejam só que boa notícia: GoogleTalk se integra à rede Jabber! Finalmente, né? Não sei porque o GoogleTalk já não começou assim…

No mais, nada mais. Devo publicar mais um artigo da série Algoritmos em breve (talvez hoje mesmo), já tive umas idéias… ;)

Saber Programar

Este artigo pode ter ficado meio confuso… Acho que acabei me perdendo no meio… :S Deixe um comentário se quiser discutir. ;)


Lógica da Programação

Estava me perguntando hoje:
“O que é a lógica da programação?”
E não consegui obter nenhuma resposta
com exatidão.
Afinal, o que é esse negócio
que “todo programador tem que saber”
e que até cursos existem
para o profissional aprender?

Desculpem… Não pude resistir… Hehehe :D Escrevi algo parecido e coube direitinho, aí dei uma modificada pra ficar em forma de “poesia”. Mas vamos ao artigo…

Talvez minhas idéias sejam diferentes das de quem já fez curso e aprendeu formalmente esse conceito, mas hoje estava pensando e cheguei a conclusão de que a lógica da programação é a lógica da vida, nua e crua. É simplesmente a lógica matemática/filosófica que já conhecemos há tempo e que aprendemos com experiências na vida. Então pra que estudá-la como sendo algo novo?

Um exemplo de lógica da programação, no meu ponto de vista, poderia ser o seguinte:

Eu não gosto de quem mexe nas minhas coisas. E O meu irmão mexeu nas minhas coisas. Disso, deduzimos que eu não gosto do meu irmão. Não importa se é verdadeiro, é lógico. É assim que o computador pensa, não é mesmo? A lógica portanto, para mim, é uma relação entre duas ou mais coisas.

Lógica Matemática

Aí lembrei-me que da lógica matemática, algo que não aprendi formalmente, mas, bom… O Professor Vavá me mencionou no meio desse ano, num dos nossos encontros matemáticos. Ele, decepcionado, mencionou que estava ensinando “lógica” para uma turma de sexta série e passou uma prova com a seguinte questão: As duas afirmações a seguir são corretas: ‘Todo professor de matemática é bonito.’ e ‘O Fabiano é professor de matemática.’ Sabendo disso, o que podemos deduzir? E aí tinham opções como: O Fabiano é feio., O Fabiano é bonito, entre várias outras coisas. Bom… Como é possível alguém errar uma questão dessas?* E, é claro, um monte de gente errou!

Condições Compostas

Cheguei a conclusão de que isto é a lógica da programação. Que é a mesma coisa que a lógica matemática. Aí… Putz… Lembrei-me da semana passada, quando o Leandro Matos (nada contra ele, até tá certo postar artigos assim porque tem gente que consegue ter dificuldade numa coisa dessas!) postou em seu blog um artigo sobre condições compostas – conjunções. Eu, olhando um título como esses, pensei: “Puxa, que esplêndido! Do que será este artigo se trata?” e, antes até de tentar interpretar o título, inocentemente abri uma página para “aprender” sobre o uso do e em condicionais na programação. Realmente decepcionante…

Quer dizer… Poxa! Como é que é possível alguém não entender uma frase como:

n>4 e n<6

… só vai ser verdadeira se 4>n E n<6? Pô, não consigo entender. Já não tá tudo escrito? Não tem nem o que interpretar: basta ler! Então, cheguei a conclusão de que o errado é o que muitas pessoas aprendem como programação. Muitas pessoas não sabem programar. Desculpa se você é um dos caras que tem dificuldade com uma coisa como “condições compostas” (nome bonito, hein?), mas sinceramente, você não vai conseguir programar nada dessa maneira. Você não consegue transpôr um conceito básico da sua vida e das suas experiências pessoais para a programação. Cheguei a conclusão de que as pessoas não entendem o que significa o “verdadeiro” e o “falso” de um condicional, não entendem um ! (“não” no C e linguagens “derivadas”), não entendem que basta você “ler”/“interpretar” a programação. Pô… Olha só a tabela que o Leandro postou no site dele! Hehehe…

É por isso que a gente chama C, PHP e outras siglas de “linguagens”, porque elas não passam de uma maneira diferente de comunicar uma coisa, geralmente muito parecida com o nosso inglês… Aliás, muito parecida com tudo que a gente vê. Quem não entende é porque não tá acostumado com o que a gente vê todos os dias ou faz tanto parte da rotina que nem nota e nem aprende.

Nerds

Então, afinal, quem é um nerd? O programador? Ora… O programador é um ser pensante, ele conhece A Lógica, porque ele tem experiências de vida. Quem não programa direito (não conhece “A Lógica”) é que é nerd, que não tem muitas experiências na vida e não consegue absorver uma mensagem e utilizá-la em outro local. E aí eu fui pensar bem e tentei lembrar-me do que eu considero “nerd”. Sempre me vem imagens de um cara que passa a noite num quarto escuro estourando sua placa aceleradora num jogo viciante e com um fone de ouvido. Nunca me vem a mente um laboratório arrumado, claro, com um programador escrevendo linhas de código.

O exemplo do “Tio Marcos”

E aí, pra completar esse laço gigantesco, lembrei-me do meu tio Marcos. É um cara bem power, o presidente do Conselho Nacional de Psicologia. A gente sempre comenta que o cara sabe “de tudo”. Tipo assim, eu comento sobre software livre, ele responde citando o Linux e com Mozilla. Se um marinheiro vai falar com ele sobre nós de marinheiro, ele sabe um monte sobre isso também! Meu irmão fala com ele sobre música erudita e ele sabe identificar os momentos históricos e qualidades de vários compositores. E a gente sempre chega a conclusão de que ele sabe tudo isso porque ele cria relações entre as coisas, relações adquiridas através de experiências pessoais. Ele já morou na Europa duas vezes (na França e na Espanha), fala essas duas línguas (francês e espanhol), além do português do inglês e vive viajando pelo mundo. Fala com gente de todos os estilos, freqüenta uns locais bem alternativos e dessa maneira é um cara que realmente sabe de “lógica”. Ele tem uma facilidade imensa para relacionar dois conteúdos e tenho certeza que ele não teria dificuldade nenhuma para programar.

Conclusão

E cheguei a conclusão portanto de que lógica é a capacidade que a pessoa tem de relacionar dois eventos. E a lógica é um dos valores mais importantes do homem. Não serve só para a programação, mas para a matemática, para a filosofia e para qualquer coisa que você vá fazer. A lógica não passa do seu raciocínio e do seu pensamento. Aliás, acho que a “inteligência” é uma função que recebe a “lógica” como argumento… ;) Para adquirir lógica, não adianta você ler um monte de coisa; saia do computador e vá conhecer pessoas e adquirir experiências! :D

Concorda? Não concorda? Comente!

* Importante observar que o Professor Vavá nunca elogiaria o seu “Professor Rival”… Na verdade, ele colocou um aviso bem grande na questão: “Deduções lógicas podem às vezes estar erradas, como acontece, por exemplo, nesse caso.” :)

© 2005–2020 Tiago Madeira