Universidade Federal de Minas Gerais
Instituto de Ciências Exatas
Departamento de Ciência da Computação

Algoritmos e Estruturas de Dados I

Arranjos


As linguagens de programação procuram dar suporte à implementaçao do conceito de vetores e matrizes existentes na matemática. Na linguagem Java a base deste suporte são os arranjos. Um arranjo é constituído de uma seqüência indexável de variáveis todas do mesmo tipo. É importante enfatizar que, a menos de algum vício de linguagem, o termo "arranjo"  e "variável do tipo arranjo" são conceitos bem distintos.

Podemos indexar os elementos de um arranjo de forma similar à notação da matemática. Os índices em Java sempre começam de zero e o operador de indexação consiste em abre e fecha colchetes - []. Assim a[0] denota o primeiro elemento de um arranjodenominado a. A indexação de um arranjo deve ser feita por uma expressão do tipo inteiro. Uma expressão do tipo long não pode ser usada para indexar um arranjo. Se tentarmos utilizar como índice um valor inválido, isto é, um valor menor que zero ou maior que o valor do índice do último elemento, será gerado um erro em tempo de execução:
ArrayIndexOutOfBoundsException

Na linguagem Java, além dos oito tipos primitivos - long, int, short, byte, char, float, double e boolean, existem as variáveis que podem conter endereços ou referências de memória. Em Java as variáveis do tipo arranjo são especializações de variáveis deste tipo, denominado tipo referência, ou seja as variáveis do tipo arranjo são também variáveis do tipo referência.

A linguagem Java permite que elementos de qualquer tipo possam se constituir na forma de arranjo. Assim a expressão geral para o tipo "arranjo de <tipo>" é "<tipo>[]" conforme exemplos abaixo:
int[] ai; /* variável do tipo arranjo de inteiros */
long[] al; /* variável do tipo arranjo de longos */
double[] ad; /* variável do tipo arranjo de ponto flutuante duplo */
boolean[] ab; /* variável do tipo arranjo de booleanos */

As variáveis do tipo arranjo podem ser iniciadas ou inicializadas de forma semelhante às variáveis de tipos primitivos com literais do tipo arranjo. Na verdade não existem literais do tipo arranjo. As "constantes" ou "literais" do tipo arranjo são denominados inicializadores de arranjo e não são considerados literais ou constantes. Veja um trecho de programa com exemplo de declaração de arranjo inicializado:
 

int[] numDiasMes={0, 31,28,31,30,31,30,31,31, 30, 31, 30, 31};
int totalDias=0;
for(int i=1; i<=12; i++) totalDias=totalDias+numDiasMes[i];

O trecho de programa acima declara uma variável do tipo arranjo denominada numDiasMes. Esta variável já é iniciada com uma referência para a região de memória onde existem 13 variáveis indexáveis do tipo inteiro. Cada variável inteira é iniciada com os 13 valores mostrados. Cada valor corresponde ao número de dias do mês de índice correspondente. A primeira variável de um arranjo em Java corresponde ao índice 0 (zero). Como o primeiro mês, Janeiro, é tradicionalmente denotado pelo número 1, o trecho acima opta por ignorar o primeiro elemento do arranjo - numDiasMes[0].

Os iniciadores de arranjo  não podem ser utilizados nas expressões dos comandos de atribuição! O trecho de programa abaixo não é compilável:

int[] numDiasMes;
numDiasMes={0, 31,28,31,30,31,30,31,31, 30, 31, 30, 31}; /* erro! */

A iniciação ou inicialização de uma variável de tipo arranjo envolve duas operações: (1) alocação da seqüência de elementos ou variáveis do arranjo e (2) iniciação ou inicialização de cada um dos elementos do arranjo. A alocação da seqüência de elementos do arranjo se faz, em geral, com o uso do operador new. No exemplo abaixo vemos a alocação da seqüência de elementos do arranjo correspondente à variável numDiasMes, sem a inicialização dos elementos. O operador new retorna o endereço de memória ou referência para  a memória onde foi feita a alocação do arranjo:

int[] numDiasMes;
numDiasMes=new int[13];

A linguagem Java não permite a especificação do número de elementos ou a especificação da dimensão do arranjo na declaração. Muitas linguagens de programação aceitam este estilo de delaração:

int[13] numDiasMes; /* erro!*/

A linguagem Java tem poucas operações que permitem a manipulação do arranjo como um todo. Conforme mencionado acima não existem "literais do tipo arranjo". O operador de alocação new permite formular expressões para comandos de atribuição envolvendo arranjos:

int[] numDiasMes;
numDiasMes= new int[]{0, 31,28,31,30,31,30,31,31, 30, 31, 30, 31};

O compilador não permite que o programador coloque a dimensão do arranjo e use o iniciador de arranjo:

int[] numDiasMes;
numDiasMes= new int[13]{0, 31,28,31,30,31,30,31,31, 30, 31, 30, 31}; /* erro! */

A figura abaixo mostra um modelo para o arranjo discutido acima:

O conteúdo da variável numDiasMes é uma referência para um bloco de memória contíguo. A referência para o bloco de memória é representada através de uma seta com um disco na origem e uma seta. O bloco de memória ou arranjo possui um campo ou variável denominada length. O campo length fornece quantas posições indexáveis tem o arranjo. No caso da figura o campo length de numDiasMes indica que o arranjo tem 13 elementos. O acesso a campos de blocos de memória acessados por referências é feito através do operador de acesso de campo: '. '.  Assim, por exemplo, a expressão numDiasMes.length tem valor 13. O campo length não pode receber atribuições. Em java é possível declarar variáveis que são inicializadas e posteriormente não podem receber atribuições, para isso usa-se o modificador final. Portanto o campo length do arranjo acima poderia corresponder a uma declaração do tipo:
final int length=13;
A figura mostra os 13 valores com que os elementos do arranjo foram inicializados e mostra também o índice de cada um deles, começando com 0 e terminando com 12.

Uma variável do tipo arranjo, é uma variável do tipo referência. Uma referência é simplesmente um endereço de memória. O sistema de execução e o compilador Java trabalham em conjunto para que, sempre que uma variável do tipo arranjo não tenha uma referência válida o seu conteúdo seja uma referência especial denominada null. O valor null pode ser usado nos programas e consiste numa referência especial cujo significado é "nenhuma referência". No trecho de programa abaixo o comando if testa se a variável x faz referência para um arranjo:
int[] x;
...
if(x==null) System.out.println("x nao contem referencia para um arranjo");

Podemos usar o literal null nas inicializações e atribuições de variáveis do tipo arranjo:
int[] x=null;
int[] y;
y=null;

É comum encontrarmos na literatura a representação gráfica do null através do símbolo de aterramento. Considere o seguinte comando de atribuição:
numDiasMes=null;
A representação gráfica para o conteúdo da variável numDiasMes ficaria:

O operador new é que estrutura e aloca todo o bloco de memória para o arranjo fazendo todas as inicializações necessárias e retornando o endereço do bloco de memória. No caso de iniciações ou inicializações é utilizado, pelo compilador, um operador new de forma implícita. Veremos mais a frente na disciplina que um arranjo é um tipo especial de objeto e que a alocação de um objeto e o retorno de seu endereço é feito pelo operador new. Podemos fazer quantas atribuições quisermos a uma variável do tipo arranjo. Quando um bloco de memória previamente referido por uma variável do tipo arranjo fica sem nenhuma referência um mecanismo da Máquina Virtual Java denominado Coletor de Lixo irá cuidar de reciclar a memória e o bloco de memória poderá ser reutilizado. O ambiente de execução da linguagem Java não exige que o programador faça a gerência dos blocos de memória.

int[] ai1={1,2,3};
int[] ai2={4,5,6};
ai2=ai1;

No trecho de programa acima o bloco de memória correspondente ao arranjo {4,5,6} deixa de ser referido pela variável ai2 em função do comando de atribuição ai2=ai1. o arranjo {1,2,3} passa a ter duas referências. A partir deste momento o Coletor de Lixo da linguagem Java poderá atuar e reciclar o bloco de memória correspondente ao arranjo {4,5,6}.

Se tentarmos imprimir (System.out.print ou System.out.println) uma referência para um arranjo será impresso uma cadeia de caracteres que não se relaciona com o conteúdo do arranjo, mas que identifica a instância de arranjo. A única exceção é o tipo "arranjo de caracteres". System.out.print(A) e System.out.println(A) imprimem a sequência de caracteres correspondente a A quando A é um arranjo de caracteres. Exemplo

char[] c={'A', 'B', 'C'};
System.out.println(c);
imprime
ABC

A representação de uma matriz na linguagem Java pode ser feita utilizando um arranjo de arranjos. Veja abaixo o exemplo de representação de uma matriz identidade 3x3 utilizando um arranjo de arranjos de inteiros e utilizando também o mecanismo de inicialização:
int[][] matIdent={ {1,0,0}, {0,1,0}, {0,0,1}};

Dizemos que matIdent é do tipo "arranjo de arranjos de inteiros". Outra forma de declarar e inicializar o arranjo acima:
int[][] matIdent=new int[3][];
matIdent[0]=new int[]{1,0,0};
matIdent[1]=new int[]{0,1,0};
matIdent[2]=new int[]{0,0,1};

Ou ainda:
int[][] matIdent;
matIdent=new int[3][];
matIdent[0]=new int[]{1,0,0};
matIdent[1]=new int[]{0,1,0};
matIdent[2]=new int[]{0,0,1};

Uma outra opção é utilizar referências auxiliares (linha1, linha2 e linha3):
int[][] matIdent=new int[3][];
int[] linha1={1,0,0};
int[] linha2={0,1,0};
int[] linha3={0,0,1};
matIdent[0]=linha1;
matIdent[1]=linha2;
matIdent[2]=linha3;

Nos trechos acima a variável matIdent é do tipo "arranjo de arranjos de inteiros", a variável denotada por matIdent[0] é do tipo "arranjo de inteiros" e a variável denotada por matIdent[0][0] é do tipo inteiro. Mais a frente na disciplina veremos que arranjos têm relação com classes e objetos: um "arranjo de inteiros" corresponde a uma classe e int[]{1,0,0} corresponde a uma instância ou objeto desta classe.

Exercícios:

  1. Escreva um trecho de programa que calcula a soma dos dias do ano do arranjo referido por numDiasMes apresentado acima.
  2. Escreva um trecho de programa que verifica se dois vetores representados por dois arranjos de inteiros referidos por a1 e a2 são de mesmo tamanho.
  3. Escreva um trecho de programa que calcula a soma de dois vetores representados por dois arranjos de inteiros referidos por a1 e a2. O resultado deve ser armazenado em um arranjo referido por a3.
  4. Escreva um trecho de programa que testa se uma matriz representada por um arranjo de arranjos de inteiros é uma matriz quadrada.
  5. Escreva um trecho de programa que testa se uma matriz representada por um arranjo de arranjo de inteiros é uma matriz triangular envolvendo a diagonal principal e os elementos abaixo dela.
  6. Escreva um trecho de programa que, sabendo que um arranjo representa uma matriz quadrada, verifica se este arranjo representa uma matriz identidade.
Sugestão de solução

Os arranjos nos permitem expressar alguns algoritmos para ordenação dos valores.