Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


Revista Programar - 10ª Edição - Setembro 2007, Notas de estudo de Informática

Artigos Tema de capa Iniciaç

Tipologia: Notas de estudo

2011

Compartilhado em 06/01/2011

samuell-ralph-de-almeida-ramos-11
samuell-ralph-de-almeida-ramos-11 🇧🇷

5

(2)

7 documentos

1 / 40

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28

Pré-visualização parcial do texto

Baixe Revista Programar - 10ª Edição - Setembro 2007 e outras Notas de estudo em PDF para Informática, somente na Docsity!

editorial

índice

notícias opinião tema de capa a programar segurança tutorial gnu/linux internet bluescreen comunidade especial

equipa PROGRAMAR

administração

Rui Maia David Pintassilgo

coordenador

Miguel Pais

coordenador adjunto

Joel Ramos

editor

Pedro Abreu

redacção

Sérgio Matias João Matos Sandro Pinto Miguel Araújo Fábio Correia Pedro Teixeira Filipe Jorge Andreia Gaita

colaboradores

David Ferreira Daniel Correia José Oliveira Sérgio Santos

contacto

revistaprogramar @portugal-a-programar.org

website

www.revista-programar.info

A (des)informação

Quando um projecto

web2.0 surge, e parte de

utilizadores individuais e

anónimos para que

sejam eles os próprios

agentes de produção de

informação, obviamente

nas áreas em que para

tal se sentem mais à

v o n t a d e , t o d o s

estranham e se inquirem

como poderão os autores do projecto ter a ilusão que tal alguma vez

resultará ou, impossibilidade absoluta, vir ainda a ser creditado como fonte

de informação crível. Foram estas e muitas outras as incertezas que

infernizaram, decerto, o começo de um dos primeiros, e agora mais bem-

sucedidos, projectos da web.

Quase impossível era a tarefa, mas como não seria de esperar, os

anónimos do mundo têm uma palavra a dizer, e tal como muitos outros

projectos sociais que agora afloram por essa net fora e chateiam por não

surpreenderem, a informação que antes seria repugnante ler, não

estivéssemos a tomar como certa uma barbaridade escrita por alguém da

cochichina, vira um dos mais importantes projectos da web, considerado

agora fonte de informação quase tão crível como muitas das melhores

enciclopédias do mercado, livremente fornecendo o que outrora muito se

pagava para adquirir.

Nesta altura, a do sucesso, alguma reflexão é necessária. Alguém ainda se

lembra quando torcia o nariz à credibilidade da informação online? Para

onde foram os argumentos que sustentavam que nada ultrapassaria uma

boa enciclopédia de centenas ou milhares de euros? Quem ainda se lembra

que afinal, e se nos bem entender, uma página da tal enciclopédia que tem

agora razão indiscutível no que diz, poderá muito bem ser editada a nosso

proveito? Não é isso que o projecto pede e incentiva? A parte da

credibilidade foi o grande problema no começo, mas é preciso manter em

mente que esse problema não se ultrapassa, é constante. Cabe-nos a nós

não tomarmos como certo o que pode estar intencionalmente errado.

Atitudes deploráveis, é certo, de quem tira proveito de tal instância

benévola para enganar tudo e todos, ou suplantar novas verdades, mas

não me cabe a mim discutir agora esses assuntos.

Deploráveis são também as atitudes daqueles que, tendo acesso livre a

informação, deixam de usar o lobo cerebral que produziria um trabalho

genuíno, e que acima de tudo revelaria bom-senso, decidindo apresentar

como deles informação que não é. Digamos que fica bem, claro... mas não

por muito tempo.

Miguel Pais

Coordenador Adjunto desde a 4ª edição, é a c t u a l m e n t e o C o o r d e n a d o r d a Revista Programar. Entrará este ano no curso de Engenharia I n f o r m á t i c a e d e Computadores.

opinião

O Preço do Código

Quando se desenvolve uma actividade por conta própria ou se está a iniciar no mercado existem sempre dificuldades em orçamentar correctamente os produtos e serviços.

Pretendo com este artigo tentar esclarecer algumas dúvidas e tentar ajudar nesta quase sempre complicada tarefa.

Uma das melhores formas de orçamentar um serviço é definir um valor hora e multiplicar pelo número de horas previstas de execução. Este valor deve ter em conta os seguintes factores de influência:

  • A dificuldade e o conhecimento que se tem da linguagem;
    • A experiência em termos de trabalhos do mesmo género;
    • O tempo disponível para o desenvolvimento.

A somar a estes valores estão também:

  • Deslocações (quando necessárias);
  • Comunicações (telemóvel, correio, faxes e internet);
  • Custos com o software de desenvolvimento;
  • Custos de sub-contratação;
  • Imprevistos (acreditem que acontecem mais do que seria desejável);
  • E, claro, Impostos.

Outra forma de orçamentar pode passar pela definição de um preçário por tarefas. As tarefas regem-se pelas mesmas variantes, mas podem facilitar alguns tipos de orçamentação para trabalhos mais regulares.

Imagem por =O-range

Pode aplicar-se, por exemplo, em casos como desenvolvimento de designs, instalação de CMS, entre outros.

Assim sendo, por exemplo, no caso de um trabalho que consista na criação de um backoffice para um site pode-se orçamentar das seguintes maneiras:

Desenvolvimento de Web site com backoffice para gestão de conteúdos: 40h x 15€ = 600€

ou

Desenvolvimento de Web site com backoffice para gestão de conteúdos

  • Design Personalizado: 150€
  • Criação de Backoffice: 400€

Mas nem só de preços se faz um orçamento. Por vezes existem problemas com clientes que à partida não definiram correctamente o que desejavam, criando uma situação de deficiente orçamentação, ou uma má apresentação da mesma. Assim sendo, um orçamento também deve incluir:

  • Especificação concreta e exaustiva dos serviços a prestar; no caso do software devem indicar todas as funcionalidades (funcionalidades adicionais são frequentemente um problema);
  • Indicação de que todos os serviços não indicados deverão ser orçamentados à parte;
  • Prazos de entrega, com as reservas para correcção de erros e atrasos da parte do cliente;
    • Indicação de quem tem os direitos de autor sobre o resultado;
    • Condições de Pagamento;
    • Validade do orçamento - apesar de não parecer, revela-se útil em casos em que é necessário acelerar a decisão do cliente, ou evitar juntar muitos trabalhos ao mesmo tempo.
    • Estes elementos visam evitar problemas de sub orçamentação que tornam o trabalho difícil e muitas das vezes pouco rentável, além de evitar que alguns clientes menos éticos se aproveitem de uma sub orçamentação ou uma orçamentação menos clara.
    • Devem-se evitar orçamentos inflacionados só porque se acredita que o cliente pode pagar mais, uma vez que cada cliente é uma carta fechada, e além disso pode dificultar uma boa publicidade por parte do cliente.
    • Se o leitor anda por estas andanças há pouco tempo, aconselho-o vivamente a criar um portfólio web ou em papel para poder mostrar os seus trabalhos e assim melhor justificar os valores pedidos e ter maior probabilidade de aprovação do orçamento.

Para finalizar recordo que estas dicas apenas tentam ajudar a orçamentar, e cabe a cada um o bom senso de definir o valor do seu trabalho. Aborrece-me ver pessoas que muitas vezes dificultam o trabalho de quem vive dos serviços que presta, criando uma ideia errada aos clientes de um baixo preço de alguns serviços, quando muitos sabem as dificuldades de quem legalmente desenvolve.

opinião

Tendo realizado o Curso Técnico de Informática III no Centro

de Formação Profissional de Santarém e tendo-o terminado

com a nota de 19 valores, Sérgio Matias ingressou depois

numa empresa de Aplicações Multimédia, tendo-se lançado

em 2006 como freelancer nas áreas de web design e

programação.

Website : http://www.sergiomatias.net

Sérgio Matias

foi desenvolvido para o NASM, logo recomendo que o usem. Atenção que a sintaxe pode ser diferente entre assemblers (existem 2 tipos genéricos de sintaxe: AT&T e Intel), logo um código para um determinado assembler pode não funcionar noutro.

2. Linker

No que toca a linkers, não existem tantas opções como na categoria dos assemblers. O linker que vai ser usado é o ld, que vem com o pacote binutils do projecto GNU (http://www.gnu.org/software/binutils/). Outra alternativa é o alink (http://alink.sourceforge.net/).

3. Editor

Podem usar qualquer editor de texto. As escolhas mais populares em ambientes GNU/Linux são o vi/vim, emacs, e pico/ed/nano. Caso não se sintam à vontade a editar o código na consola (shell), também podem usar um editor de texto com interface gráfica, como o gedit, Geany, etc.

Caso o vosso sistema não tenha os pacotes instalados, procurem na documentação da vossa distribuição como o fazer.

Arquitectura do computador

Antes de começarmos a programar em Assembly, temos de aprender os conceitos básicos do funcionamento interno de um computador.

A arquitectura dos computadores modernos é baseada na arquitectura Von Neumann, seguindo o nome do seu criador. Esta arquitectura divide o computador em duas partes principais: o processador (CPU - Central Processing Unit) e a memória. Esta arquitectura é usada em todos os computadores modernos, incluindo os computadores pessoais, super computadores, mainframes, consolas de jogos e até mesmo telemóveis.

Estrutura da memória do computador

A memória do computador é o espaço onde estão armazenados todos os dados do computador. Este espaço tem um tamanho fixo e os dados podem ser acedidos através de endereços. Por exemplo, imaginem que têm 128MB de RAM no computador. Isto corresponde a 131072 kilobytes, ou 134217728 bytes. Neste caso, estão disponíveis 134217728 posições de armazenamento diferentes do tamanho de um byte. Não esquecer que o computador começa a contar no 0, logo os endereços de memória disponíveis neste caso começam no 0 e acabam em

Processador

O processador é o componente do computador que interpreta e executa as instruções dos programas e processa os dados. Este é constituído por vários sub-sistemas, dos quais se destacam a ALU (Arithmetic Logic Unit) - responsável por todas as operações aritméticas (ex. adição e subtracção) e lógicas (ex. AND, XOR, OR); FPU (Floating Point Unit) - equivalente ao ALU mas para números decimais; os registos - zona de armazenamento ultra-rápida, utilizada pelo processador para acelerar a execução dos programas permitindo acesso aos valores utilizados mais frequentemente.

Existe um número limitado de operações, sendo o conjunto de todas essas operações e das suas variações designado por ISA (Instruction Set Architecture). Existem diferentes conjuntos de instruções mas consideram-se duas categorias: RISC (Reduced Instruction Set Architecture - ex. arquitectura MIPS) e CISC (Complex Instruction Set Architecture - ex. arquitectura x86).

Este tutorial vai abordar o conjunto de instruções base x (este surgiu pela primeira vez em 1978 no processador Intel 8086). Ao longo dos anos têm sido feitas extensões a este conjunto de instruções, tais como o MMX, 3DNow!, SSE, SSE2 e SSE3.

Todos os processadores com base na arquitectura Von Neumann funcionam com base num ciclo constituído por 3 passos essenciais: fetch, decode, execute.

tema de capa

No primeiro passo o processador obtém a próxima instrução a executar a partir da posição contida no registo PC, que armazena a posição actual da memória do programa; no segundo passo, o processador divide a instrução (em código máquina) em secções: uma com o opcode da operação a executar (Operation Code) e as outras com dados complementares para realizar a operação; no terceiro passo a operação é executada.

Outro componente do processador são os registos. Os registos são como variáveis ultra-rápidas embutidas no processador. É nos registos que são armazenados todos os dados necessários para efectuar os cálculos ou outras operações realizadas pelo processador.

O problema da família x86 de processadores é que existem poucos registos disponíveis, o que leva a que o programador tenha de gerir bem os registos necessários para a sua aplicação.

Os registos são normalmente de 32bits, mas também existem registos de 16bits e de 8bits. Por exemplo, tomando como base o registo AX de 16bits, podemos considerar o registo EAX (Extended AX) de 32bits, e os registos AH (AX Higher) e AL (AX Lower) que correspondem a dois registos de 8bits.

Registos gerais: Tal como o nome indica estes são os registos usados a maior parte do tempo. A maioria das instruções tem como base estes registos.

  • EAX, AX, AH, AL: “Accumulator register” Normalmente usado para acesso I/O, aritmética, chamadas de sistema, etc...
  • EBX, BX, BH, BL: “Base register” É usado como um ponteiro base para o acesso à memória.

•ECX, CX, CH, CL: “Counter register” É usado como contador de loops e para shifts.

  • EDX, DX, DH, DL: “Data register” Semelhante ao registo EAX.

Registos de segmento

  • CS: “Code segment” Armazena o segmento de código do programa.
  • DS: “Data segment” Armazena o segmento de dados do programa.
  • ES FS GS: Registos de segmentos adicionais para armazenamento de segmentos.
    • SS: “Stack segment” Armazena o segmento da stack do programa

Index e ponteiros

  • EDI: “Destination index register” Usado para cópia de strings e arrays de memória e para endereçamento de ponteiros em conjunto com o ESI.
  • ESI: “Source index register” Usado para cópia de strings e arrays de memória.
  • EBP: “Stack Base pointer register” Armazena o endereço da base da stack.
  • ESP: “Stack pointer register” Armazena o endereço do topo da stack.
  • EIP: “Index Pointer” Armazena o offset para a próxima instrução.
  • Indicador EFLAGS: Armazena o estado do processador

Modos de endereçamento de memória

1. Endereçamento por valor imediato (immediate address mode) 2. Endereçamento de registo (register address mode) 3. Endereçamento directo (direct addressing mode) 4. Endereçamento por index (indexed addressing mode) 5. Endereçamento indirecto (indirect adressing mode) 6. Endereçamento por ponteiro base (base pointer addressing mode)

No primeiro caso, atribuímos o valor directamente. Por exemplo, se quisermos inicializar um registo para 0, introduzimos directamente o valor 0, em vez de darmos um endereço para o processador ler o valor 0.

No modo de endereçamento de registo, a instrução contém o registo de onde deve obter o valor, em vez de uma localização na memória.

No modo de endereçamento directo, a instrução contém o endereço da memória que contém o valor. Por exemplo, podemos pedir ao processador para copiar um valor num determinado endereço da memória para o registo do processador.

No modo de endereçamento por index, a instrução contém um endereço de memória para aceder e um index, que funciona como um offset. Por exemplo, se utilizarmos o endereço 1390 e um index de 10, o valor lido vai ser o da

tema de capa

Neste caso, a primeira instrução a ser realizada é a mov eax,

  1. O que esta execução faz é mover o valor 1 para o registo eax do processador. Em todas as operações da sintaxe Intel, o primeiro operando corresponde ao local de destino, e o segundo ao valor inicial.

Nota: Na sintaxe AT&T, a ordem dos operandos é inversa.

Nas linhas seguintes movemos o valor 0 para o registo ebx do processador, e fazemos uma chamada ao sistema com a instrução int 0x80 (abreviatura de interrupt).

Como estamos a chamar o serviço exit do sistema (valor 1 no registo eax), o programa retorna à consola com o valor no registo ebx. Se experimentarem alterar este valor no código fonte, e voltarem a correr o programa, podem ver que o valor que o programa retorna para a consola é diferente. Nota: Não utilizar valores superiores a 255 (o valor máximo de um unsigned byte) ou podem ocorrer problemas de overflow. Ao ultrapassar o valor máximo que o byte permite, o comportamento do sistema pode ser inesperado. No meu caso, ao utilizar 266, o valor retornado foi de 10 (266-255).

De seguida o clássico Hello World:

tema de capa

João Matos é um estudante de 18 anos e programador

autodidacta interessado por tudo o que esteja relacionado

com a área de informática e das novas tecnologias. Entrará

este ano no curso de Engenharia Informática e de

Computadores. Foi finalista nacional das Olimpíadas

Nacionais de Informática 2007.

João Matos

Primeiro declaramos a string Hello World usando ‘db’ (declare byte). No fim da string usamos o caracter de representação hexadecimal 0x0a, mais conhecido por \n ou mudança de linha.

Na linha seguinte atribuímos a ‘len’ o tamanho da string msg, usando para isso o ‘equ’. Para obter o valor do tamanho da string subtraímos a posição actual $ ao endereço inicial de msg.

No resto do programa usamos a chamada ao sistema write, para escrever a mensagem para a consola, e depois usamos o código do exemplo anterior para retornar do programa.

Esta foi a primeira parte deste artigo, saiu mais teórica do que o previsto. Assim ficam com as bases para aprender a maior parte dos conceitos mais complexos desta fabulosa linguagem. Estejam atentos às próximas edições para o próximo artigo que vai contar com muitos mais exemplos de código.

; write mov ebx, 1 ; ficheiro de saida

- stdin mov ecx, msg ; apontador para o buffer mov edx, len ; tamanho do buffer mov eax, 4 ; chamada write ao sistema int 0x

; exit mov eax, 1 ; move o valor 1 para o registo eax mov ebx, 0 ; move o valor 0 para o registo ebx int 0x80 ; chamada de sistema para a kernel

section .data msg db "Hello World!",0x0a ; string hello world len equ $-msg ; calcula o tamanho da string msg

section .text ; inicio da seccao de texto global _start ; onde deve comecar a execucao

_start: ; label start - a execucao comeca aqui

Programação em

Ambiente Gráfico -

CircularGT

2ª Parte

Introdução

Bem, tal com referi na edição anterior, para este número da revista ficou reservada a parte mais prática da questão, isto é, enquanto outrora me preocupei em explicar como funcionava cada bloco de programação, agora tenciono analisar alguns programas exemplo, bem como explicar como se processa a comunicação entre o compilador e o robô, de forma a consolidar e fortificar os conhecimentos que o leitor já adquiriu.

Programas exemplo

Na verdade, antes de partirmos, efectivamente, para os exemplos práticos seria interessante explicar as regras e estrutura dos programas. Com efeito, a primeira ideia a clarear é que o ciclo do programa será sempre de cima para baixo, isto é, começa pelo bloco imediatamente abaixo do bloco de configuração das entradas e saídas do microcontrolador (“PROGRAM START”), e caso não exista nenhuma comparação o bloco seguinte a ser executado é o bloco logo abaixo.

Todavia, caso exista uma comparação, existem duas hipóteses: se se verificar a condição imposta no bloco de comparação, o próximo bloco a ser executado é o bloco logo abaixo deste; se não se verificar a condição, neste caso, o bloco a ser executado é o bloco imediatamente à direita da comparação, que poderá ser uma nova comparação ou simplesmente não existir. No caso de não existir nenhum bloco à direita, é considerado como ciclo terminado e é iniciado um novo.

Explicada, basicamente, a sintaxe dos programas vamos, portanto, analisar o primeiro exemplo:

Este é, sem dúvida, dos programas mais simples desta linguagem. Como podemos observar, utilizamos um bloco PWM de regulação da velocidade dos motores seguido de um bloco temporizador.

Assim, lendo o programa de cima para baixo, vemos que o robô irá andar para a frente (conforme se pode ver na figura da direita), contudo como por baixo temos um

bloco temporizador ele não o irá fazer eternamente, mas apenas 0,5 segundos (0,5 porque como vimos no bloco temporizador o tempo encontra-se escalado em períodos de 0,25 – 0,25x2=0,5). É importante referir que há logo a passagem do bloco de PWM para o bloco de temporização pois não existe nenhum bloco de comparação, e, como tal, não se tem de verificar se uma determinada condição é verdadeira ou falsa. Passados os dois segundos, visto que não há mais nenhum bloco de instrução, o ciclo do programa é dado como finalizado, mas logo de seguida iniciar-se-á um novo, ou seja, os programas nesta linguagem estão em “loop” constante.

Visto o primeiro exemplo, está na altura de compreender algo um pouco mais elaborado:

Antes de mais, convém referir que este é um bom exemplo para se demonstrar como se podem utilizar os blocos de comparação simples e com AND lógico, para além de se reforçar a compreensão dos blocos de movimentos pré- definidos e de regulação de velocidade. Feito o aparte, começarei então a explicar a estrutura do programa. Como podemos ver, o primeiro bloco imediatamente abaixo do

a programar

para trás (ambas os motores a 0) durante 0,75 segundos. Ora, agora em vez de um incremento, no final da coluna temos um bloco de atribuição de valores. Como o próprio nome indica, vamos atribuir um valor a variável V1, que sendo 10 fará com que se volte ao início do programa e se execute, novamente, todas as tarefas anteriormente mencionadas.

Em suma, este programa, através de incrementos (que também poderiam ser decrementos) e atribuição de valores às variáveis, faz com que o dispositivo realize uma sequência de movimentos, que bem elaborada, trabalhada e “alongada”, poderá habilitar o robô a representar uma coreografia (falo por experiência própria!).

Comunicação compilador/robô

Neste momento, depois do estudo de todos os blocos e de alguns exemplos concretos, o básico e intermédio da programação já está deveras consolidado. Visto isso, torna- se agora importante perceber como é que é feita a comunicação entre o compilador e o robô, isto é, como se descarregam os programas na PIC do dispositivo.

Assim, para descarregar o programa devemos, em primeiro lugar, efectuar a sua compilação, ou seja, depois dele estar produzido devemos carregar no botão de compilação existente na Toolbar, conforme se pode observar na figura abaixo.

Depois de efectuada a compilação é, então, automaticamente aberta uma aplicação chamada “CD2M”. Agora, torna-se importante a existência de um cabo RS232, essencial para estabelecer a comunicação entre a entrada série do PC e do robô.

Ligado o terminal macho ao robô e o terminal fêmea ao computador, na aplicação CD2M devemos especificar a porta série que estamos a utilizar “Com1”, “Com2”, “Com3”

ou “Com4” no campo Commport e seleccionar a velocidade de comunicação de 9600. De seguida, utilizando-se a opção Browse abre-se o ficheiro Prog.hex que se encontra na directoria onde foi instalado o software de programação. Finalmente, com o robô desligado (interruptor geral “OFF”) liga-se o interruptor de Programação (põe-se “ON”), e seguidamente é que se liga o interruptor geral (“ON”). Carregamos em START e vemos então o programa a ser enviado para o microcontrolador. Quando aparecer a mensagem “Code has been downloaded” podemos desligar o interrutor geral e de programação (por esta ordem). Retira-se o cabo série, e accionando o interruptor geral vemos o dispositivo electrónico a fazer o que definimos na programação.

Conclusão

Agora que chegamos ao final do artigo, considero que os leitores estão já com um certo à-vontade em relação a esta linguagem em ambiente gráfico. Contudo, agora cabe a cada um aprofundar e “trabalhar” a informação adquirida de forma a tornar-se um “expert” na programação deste robô.

a programar

Apesar de apresentar especial paixão pelo futebol, Sandro

Pinto é um declarado amante da informática, particularmente

da componente electrónica e de hardware. Tendo concluído o

curso tecnológico de electrotecnia e electrónica com média de

19,1, ingressará na universidade este ano na área de

Engenharia Electrónica e de Computadores.

Sandro Pinto

Grafos

1ª Parte

Esta é a primeira parte de um artigo que pretende fazer com que o leitor compreenda e use grafos sem quaisquer problemas.

Contudo, dada a extensão do tema, fui forçado a dividi-lo em duas partes. Uma bastante mais cansativa e outra bastante mais interessante. Fui forçado a tal facto uma vez que a teoria é tão importante como a prática.

Deixo-vos contudo um pequeno aperitivo da segunda: Flood Fill. Na segunda parte vamos concentrar os nossos esforços em algoritmos cada vez mais complexos, de modo a cobrir o máximo possível deste mundo.

Introdução - O que é um grafo

O leitor certamente que já ouviu falar em grafos. São amplamente usados em matemática, mas sobretudo em programação. Formalmente, um grafo é uma colecção de vértices (V) e uma colecção de arcos (E) constituídos por pares de vértices. É uma estrutura usada para representar um modelo em que existem relações entre os objectos de uma certa colecção. Pense nos vértices como “locais”. O conjunto dos vértices é o conjunto de todos os locais possíveis. Nesta analogia, os arcos (ou arestas) representam caminhos entre estes locais. O conjunto E (vou usar o termo mais comum - “E” do inglês “edges”) contém todas as ligações entre os locais.

Utilizar grafos é de grande utilidade na representação de problemas da vida real.Podem ser cidades, e uma rede de estradas. Redes de computadores. Até mesmo os movimentos de um cavalo num tabuleiro de xadrez podem ser representados através de um grafo.

E depois de representá-los correctamente, o que podemos descobrir? O caminho mais curto entre duas cidades num mapa; dadas as coordenadas de n cidades, que estradas construir de modo que o número de quilómetros de estrada seja mínimo mas fiquem todas conectadas; dado um mapa de uma casa (em que paredes e chão são representados com caracteres diferentes) saber qual a divisão com maior área; etc.

As possibilidades são imensas, e ficarão admirados com a facilidade com que estes problemas são resolvidos.

Graficamente, um grafo é normalmente representado da seguinte forma:

Vértices são pontos ou círculos; Arcos são linhas entre eles.

Usando o primeiro exemplo, V={1, 2, 3, 4, 5, 6} e E = {(1,3), (1,6), (2,5), (3,4), (3,6)}. Cada vértice (também chamado “nó”) é um membro do conjunto V. Cada arco é um membro do conjunto E.

Terminologia

Como é de esperar, tendo aplicações tão variadas, o grafo adapta-se às nossas necessidades. Assim, existem vários tipos de grafos. Aliados a isso, existem termos comummente usados para descrever um grafo, ou parte dele. Vou listar alguns (entre eles os mais comuns):

- Vértice isolado - Um vértice é considerado isolado se não possuir nenhuma ligação a outro vértice - Grafo trivial (ou ponto) - Grafo sem arestas e um único nó. - Laço (ou loop/self-loop) - Um arco é um laço se em ambas as extremidades estiver o mesmo vértice. (nenhum dos grafos apresentados possui laços) - Grafo simples - Um grafo é simples se não contiver laços nem arcos repetidos em E. - Vértices adjacentes - Dois vértices (u e v) são adjacentes se existir um arco que possui uma extremidade em u e outra em v. Os vizinhos de um vértice são todos os vértices adjacentes a ele.

a programar

Sem grandes esforços, podemos chegar a um algoritmo simples:

  • Precisamos de um vector que nos diga, num dado instante, se o elemento i foi ou não visitado (inicialmente todos os elementos começam a 0 (não visitado).
  • Começando no primeiro elemento, marcar todos os elementos que lhe estão ligados como visitados. E todos os que estão ligados a esses (que ainda não foram marcados, obviamente) também. E por aí fora.
  • Se quando chegarmos ao fim ainda houver elementos por marcar, então estamos perante a existência um novo centro urbano.

Um flood fill pode ser feito basicamente de 2 maneiras: em profundidade (dfs - depth-first search) ou em largura (bfs - breadth-first search) (simplificando, pois existem mais alternativas como o “dfs with iterative deepening” ou “breadth-first scanning”).

- Breadth-first search Podemos ver a pesquisa em largura como uma torneira aberta num chão de tijoleira: a primeira a ser molhada é a que se encontra no local onde está a torneira. Todas as que estão à volta serão as próximas, e por aí em diante, numa expansão a partir de um dado centro. Este algoritmo normalmente não levanta muitos problemas a implementar, quer iterativa como recursivamente: temos o nosso vector de elementos visitados (e por visitar) e uma lista dos elementos que acabaram de ser visitados (recentemente). Para cada um destes últimos, adicionamos a uma nova lista os seus vizinhos ainda não visitados. Depois de termos feito isto com todos os elementos, passamos para a nova lista (e podemos esquecer a antiga). - Depth-first search O algoritmo de pesquisa em profundidade normalmente são mais complicados de compreender, porque dão trabalho a implementar iterativamente e muita gente não está familiarizada com a recursividade. Contudo, após esse problema estar ultrapassado, é ainda mais fácil de implementar do que o bfs.

Vamos ver o dfs como um rato à procura de um queijo perdido num labirinto: ele escolhe um caminho, e mal encontra um beco sem saída volta para trás e vira pelo primeiro caminho ainda não pesquisado. É exactamente este funcionamento que vamos implementar, desta vez em pseudo-código.

Partindo do vértice inicial, V.

função dfs(v) marcar v como visitado para todos os vértices i adjacentes a v se i não tiver sido visitado dfs(i)

- Problemas modelo Street Race [ International Olympiads in Informatics ‘95 ] Dados: um grafo direccionado, um ponto inicial e um ponto final. Encontrar todos os pontos “p” que um caminho do ponto inicial para o ponto final deve atravessar obrigatoriamente.

Análise: O algoritmo mais simples é remover cada ponto e verificar se o ponto final ainda é alcançado a partir do ponto inicial.

The Castle [ International Olympiads in Informatics ‘94 ] Dados: um mapa de um castelo (uma matriz) onde “#” representa uma parede e “.” uma casa em branco. Descobrir qual o tamanho da maior sala do castelo, após deitar abaixo uma das paredes.

Análise: Ao ser fornecida a matriz temos uma representação implícita do grafo, cada casa sendo um vértice. Não precisamos de a transformar numa matriz/lista de adjacência, podemos usá-la para saber quais os vértices adjacentes. Mais uma vez, deitamos uma parede abaixo e verificamos o tamanho da maior sala.

Fontes : Wikipedia e USACO Training Program (http://train.usaco.org)

a programar

O especial interesse pela algoritmia nasceu enquanto

frequentava no secundário o curso Científico-Tecnológico de

Informática, que terminou recentemente com média de 19,

valores. Também recentemente representou Portugal nas

Olimpíadas Internacionais de Informática, na Croácia, em

2007, após vencer as Olimpíadas Nacionais, e em Mérida, no

México, em 2006.

Miguel Araújo

Vamos agora ver uma outra forma de serialização, embora use o mesmo mecanismo que o exemplo anterior. Neste exemplo os objectos serão serializados para um ByteArray. Esta técnica pode ser bastante útil para envio de grandes quantidades de objectos pela rede.

Para isso vamos usar novamente a classe Exemplo1 e uma nova classe de teste semelhante à classe Teste1 usada anteriormente.

a programar

public class Teste1 {

public static void main( String args []) { Exemplo1 e1 = new Exemplo1(001,"White"); Exemplo1 e2 = new Exemplo1(002,"Magician");

System .out.println(e1.toString());

System .out.println(e2.toString());

ObjectOutputStream out; ObjectInputStream in;

try { out = new ObjectOutputStream ( new FileOutputStream ( System .getProperty("user .dir")+ File .separator+"Exemplo1.bin")); out.writeObject(e1); out.writeObject(e2); out.flush(); out.close(); } catch ( Exception e) { e.printStackTrace(); }

Exemplo1 e3; Exemplo1 e4;

try { in = new ObjectInputStream ( new FileInputStream ( System .getProperty("user. dir")+ File .separator+"Exemplo1.bin")); e3 = (Exemplo1) in.readObject(); e4 = (Exemplo1) in.readObject();

in.close();

System .out.println(e3.toString());

System .out.println(e4.toString()); } catch ( Exception e) { e.printStackTrace(); } } }

[Teste1.java]

Esta classe é constituída apenas pelo método main visto que tem por objectivo demonstrar a serialização de objectos para um ficheiro binário. Começamos por criar alguns objectos, neste caso dois e o conteúdo desses objectos será impresso no ecrã com o auxílio do método toString implementado na classe Exemplo1. Inicializamos um ObjectOutputStream que irá criar um ficheiro binário de nome Exemplo1.bin na directoria actual. Este stream irá permitir serializar os objectos no ficheiro. Agora basta serializar os objectos, serialização essa realizada pelo método writeObject(Object object), que irá guardar o objecto dado como argumento no ficheiro de destino sob a forma binária.

Agora vamos fazer o processo inverso, passar os objectos serializados no ficheiro binário para objectos Java válidos. Este processo é tão simples como o seu inverso. Para isso iremos começar por criar dois novos objectos da classe Exemplo1 sem os inicializar. Em seguida inicializamos um ObjectInputStream que irá ler o ficheiro Exemplo1.bin criado anteriormente. Este stream contém o método readObject() que lê um objecto serializado num ficheiro. Como podemos ver, os objectos retornados pelo método readObject() são guardados nas variáveis e3 e e4 criadas anteriormente. Podemos ver também que nestas mesmas linhas é feito um cast para (Exemplo1). O cast deve ser sempre feito porque, embora os objectos serializados sejam do tipo Exemplo1 o método readObject() retorna o tipo genérico Object, que depois deve ser convertido para o tipo original desse objecto.

Por fim vamos imprimir os objectos e3 e e4 tal como fizemos anteriormente para o e1 e e2 e, se todo o processo correr normalmente, os valores de e1 serão iguais aos de e3 e o mesmo acontece com e2 e e4, o output. Neste caso será algo como o que podemos ver em seguida.

Numero = 1 | Nome = White Numero = 2 | Nome = Magician Numero = 1 | Nome = White Numero = 2 | Nome = Magician

import java.io. ByteArrayInputStream ; import java.io. ByteArrayOutputStream ; import java.io. ObjectInputStream ; import java.io. ObjectOutputStream ;

À semelhança do exemplo anterior nesta classe são criados dois objectos da classe Exemplo1 e em seguida os seus valores são impressos.

As diferenças principais começam agora. Criamos um ByteArrayOutputStream que será usado como buffer para guardar os objectos serializados, e à semelhança do exemplo anterior, inicializamos o ObjectOutputStream, mas ao contrário do primeiro exemplo, em que damos como argumento um FileOutputStream usado para escrever no ficheiro, neste caso damos como argumento o ByteArray criado. Assim cada objecto serializado será guardado no ByteArray e não num ficheiro. Depois, são serializados dois objectos Exemplo1 para o ByteArray que será usado mais tarde.

O ByteArrayOutputStream que criamos pode ser usado para várias coisas recorrendo ao método toByteArray() que retorna uma array de bytes com todos os dados do nosso ByteArrayOutputStream. As utilidades deste array são inúmeras, mas neste caso vamos apenas usá-lo para construir um ObjectInputStream, e assim iremos conseguir recuperar os nossos objectos serializados.

Por fim vamos imprimir os valores dos nossos novos objectos e iremos ver que os valores coincidem tal como no exemplo anterior.

Para terminar este artigo sobre serialização de objectos em Java iremos fazer o processo usando XML ao invés do ficheiro binário como vimos anteriormente. Comecemos por criar uma classe Exemplo2.java.

a programar

Numero = 1 | Nome = White Numero = 2 | Nome = Magician Numero = 1 | Nome = White Numero = 2 | Nome = Magician

public class Exemplo2 { private int numero; private String nome;

public Exemplo2 () { }

public Exemplo2(int numero, String nome) { this .numero = numero; this .nome = nome; }

public void setNome( String nome) { this .nome = nome; }

public void setNumero(int numero) {

public class Teste2 { public static void main( String args []) { Exemplo1 e1 = new Exemplo1(001,"White");

Exemplo1 e2 = new Exemplo1(002,"Magician");

System .out.println(e1.toString()); System .out.println(e2.toString());

ByteArrayOutputStream buffer = new ByteArrayOutputStream (); ObjectOutputStream out; ObjectInputStream in;

try { out = new ObjectOutputStream (buffer); out.writeObject(e1); out.writeObject(e2); out.flush(); out.close(); } catch ( Exception e) { e.printStackTrace(); }

Exemplo1 e3; Exemplo1 e4;

try { in = new ObjectInputStream ( new ByteArrayInputStream (buffer.toByteArray() )); e3 = (Exemplo1) in.readObject(); e4 = (Exemplo1) in.readObject();

in.close();

System .out.println(e3.toString()); System .out.println(e4.toString());

} catch ( Exception e) { e.printStackTrace(); } } }

[Teste.java]