Académique Documents
Professionnel Documents
Culture Documents
Matrizes
W. Celes e J. L. Rangel J discutimos em captulos anteriores a construo de conjuntos unidimensionais atravs do uso de vetores. A linguagem C tambm permite a construo de conjuntos bi ou multidimensionais. Neste captulo, discutiremos em detalhe a manipulao de matrizes, representadas por conjuntos bidimensionais de valores numricos. As construes apresentadas aqui podem ser estendidas para conjuntos de dimenses maiores.
Podemos dizer que, nestes casos, os vetores so declarados estaticamente 1. A varivel que representa o vetor uma constante que armazena o endereo ocupado pelo primeiro elemento do vetor. Esses vetores podem ser declarados como variveis globais ou dentro do corpo de uma funo. Se declarado dentro do corpo de uma funo, o vetor existir apenas enquanto a funo estiver sendo executada, pois o espao de memria para o vetor reservado na pilha de execuo. Portanto, no podemos fazer referncia ao espao de memria de um vetor local de uma funo que j retornou. O problema de declararmos um vetor estaticamente, seja como varivel global ou local, que precisamos saber de antemo a dimenso mxima do vetor. Usando alocao dinmica, podemos determinar a dimenso do vetor em tempo de execuo:
int* v; v = (int*) malloc(n * sizeof(int));
Neste fragmento de cdigo, n representa uma varivel com a dimenso do vetor, determinada em tempo de execuo (podemos, por exemplo, capturar o valor de n fornecido pelo usurio). Aps a alocao dinmica, acessamos os elementos do vetor da mesma forma que os elementos de vetores criados estaticamente. Outra diferena importante: com alocao dinmica, declaramos uma varivel do tipo ponteiro que posteriormente recebe o valor do endereo do primeiro elemento do vetor, alocado dinamicamente. A rea de memria ocupada pelo vetor permanece vlida at que seja explicitamente liberada (atravs da funo free). Portanto, mesmo que um vetor seja
1
criado dinamicamente dentro da funo, podemos acess-lo depois da funo ser finalizada, pois a rea de memria ocupada por ele permanece vlida, isto , o vetor no est alocado na pilha de execuo. Usamos esta propriedade quando escrevemos a funo que duplica uma cadeia de caracteres (string): a funo duplica aloca um vetor de char dinamicamente, preenche seus valores e retorna o ponteiro, para que a funo que chama possa acessar a nova cadeia de caracteres. A linguagem C oferece ainda um mecanismo para re-alocarmos um vetor dinamicamente. Em tempo de execuo, podemos verificar que a dimenso inicialmente escolhida para um vetor tornou-se insuficiente (ou excessivamente grande), necessitando um redimensionamento. A funo realloc da biblioteca padro nos permite re-alocar um vetor, preservando o contedo dos elementos, que permanecem vlidos aps a re-alocao (no fragmento de cdigo abaixo, m representa a nova dimenso do vetor).
v = (int*) realloc(v, m*sizeof(int));
Vale salientar que, sempre que possvel, optamos por trabalhar com vetores criados estaticamente. Eles tendem a ser mais eficientes, j que os vetores alocados dinamicamente tm uma indireo a mais (primeiro acessa-se o valor do endereo armazenado na varivel ponteiro para ento acessar o elemento do vetor).
Esta declarao reserva um espao de memria necessrio para armazenar os 12 elementos da matriz, que so armazenados de maneira contnua, organizados linha a linha.
float m[4][3] = {{ 5.0,10.0,15.0}, {20.0,25.0,30.0}, {35.0,40.0,45.0}, {50.0,55.0,60.0}}; j i 5.0 20.0 35.0 50.0 10.0 25.0 40.0 55.0 15.0 30.0 45.0 60.0
152 60.0 55.0 50.0 45.0 40.0 35.0 30.0 25.0 20.0 15.0 10.0 5.0
104
8-2
Os elementos da matriz so acessados com indexao dupla: mat[i][j]. O primeiro ndice, i, acessa a linha e o segundo, j, acessa a coluna. Como em C a indexao comea em zero, o elemento da primeira linha e primeira coluna acessado por mat[0][0]. Aps a declarao esttica de uma matriz, a varivel que representa a matriz, mat no exemplo acima, representa um ponteiro para o primeiro vetor-linha, composto por 3 elementos. Com isto, mat[1] aponta para o primeiro elemento do segundo vetor-linha, e assim por diante. As matrizes tambm podem ser inicializadas na declarao:
float mat[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
O nmero de elementos por linha pode ser omitido numa inicializao, mas o nmero de colunas deve, obrigatoriamente, ser fornecido:
float mat[][3] = {1,2,3,4,5,6,7,8,9,10,11,12};
Passagem de matrizes para funes Conforme dissemos acima, uma matriz criada estaticamente representada por um ponteiro para um vetor-linha com o nmero de elementos da linha. Quando passamos uma matriz para uma funo, o parmetro da funo deve ser deste tipo. Infelizmente, a sintaxe para representar este tipo obscura. O prottipo de uma funo que recebe a matriz declarada acima seria:
void f (..., float (*mat)[3], ...);
Uma segunda opo declarar o parmetro como matriz, podendo omitir o nmero de linhas2:
void f (..., float mat[][3], ...);
De qualquer forma, o acesso aos elementos da matriz dentro da funo feito da forma usual, com indexao dupla. Na prxima seo, examinaremos formas de trabalhar com matrizes alocadas dinamicamente. No entanto, vale salientar que recomendamos, sempre que possvel, o uso de matrizes alocadas estaticamente. Em diversas aplicaes, as matrizes tm dimenses fixas e no justificam a criao de estratgias para trabalhar com alocao dinmica. Em aplicaes da rea de Computao Grfica, por exemplo, comum trabalharmos com matrizes de 4 por 4 para representar transformaes geomtricas e projees. Nestes casos, muito mais simples definirmos as matrizes estaticamente (float mat[4][4];), uma
2
Isto tambm vale para vetores. Um prottipo de uma funo que recebe um vetor como parmetro pode ser dado por: void f (..., float v[], ...);.
8-3
vez que sabemos de antemo as dimenses a serem usadas. Nestes casos, vale a pena definirmos um tipo prprio, pois nos livramos das construes sintticas confusas explicitadas acima. Por exemplo, podemos definir o tipo Matrix4.
typedef float Matrix4[4][4];
a b c d e f
g h I j
k = i*n+j = 1*4+2 = 6
Figura 8.2: Matriz representada por vetor simples.
Esta conta de endereamento intuitiva: se quisermos acessar elementos da terceira (i=2) linha da matriz, temos que pular duas linhas de elementos (i*n) e depois indexar o elemento da linha com j.
8-4
Com esta estratgia, a alocao da matriz recai numa alocao de vetor que tem m*n elementos, onde m e n representam as dimenses da matriz.
float *mat; /* matriz representada por um vetor */ ... mat = (float*) malloc(m*n*sizeof(float)); ...
No entanto, somos obrigados a usar uma notao desconfortvel, v[i*n+j], para acessar os elementos, o que pode deixar o cdigo pouco legvel. Matriz representada por um vetor de ponteiros Nesta segunda estratgia, faremos algo parecido com o que fizemos para tratar vetores de cadeias de caracteres, que em C so representados por conjuntos bidimensionais de caracteres. De acordo com esta estratgia, cada linha da matriz representada por um vetor independente. A matriz ento representada por um vetor de vetores, ou vetor de ponteiros, no qual cada elemento armazena o endereo do primeiro elemento de cada linha. A figura abaixo ilustra o arranjo da memria utilizada nesta estratgia.
j=2 i=1 a e I b f j c g k d h l j=2 i=1 a b c d e f I j
Figura 8.3: Matriz com vetor de ponteiros.
g h k l
A alocao da matriz agora mais elaborada. Primeiro, temos que alocar o vetor de ponteiros. Em seguida, alocamos cada uma das linhas da matriz, atribuindo seus endereos aos elementos do vetor de ponteiros criado. O fragmento de cdigo abaixo ilustra esta codificao:
int i; float **mat; /* matriz representada por um vetor de ponteiros */ ... mat = (float**) malloc(m*sizeof(float*)); for (i=0; i<m; i++) m[i] = (float*) malloc(n*sizeof(float));
A grande vantagem desta estratgia que o acesso aos elementos feito da mesma forma que quando temos uma matriz criada estaticamente, pois, se mat representa uma matriz
Estruturas de Dados PUC-Rio 8-5
alocada segundo esta estratgia, mat[i] representa o ponteiro para o primeiro elemento da linha i, e, conseqentemente, mat[i][j] acessa o elemento da coluna j da linha i. A liberao do espao de memria ocupado pela matriz tambm exige a construo de um lao, pois temos que liberar cada linha antes de liberar o vetor de ponteiros:
... for (i=0; i<m; i++) free(mat[i]); free(mat);
A funo que cria a matriz dinamicamente deve alocar a estrutura que representa a matriz e alocar o vetor dos elementos:
Matriz* cria (int m, int n) { Matriz* mat = (Matriz*) malloc(sizeof(Matriz)); mat->lin = m; mat->col = n; mat->v = (float*) malloc(m*n*sizeof(float));
Estruturas de Dados PUC-Rio 8-6
return mat; }
Poderamos ainda incluir na criao uma inicializao dos elementos da matriz, por exemplo atribuindo-lhes valores iguais a zero. A funo que libera a memria deve liberar o vetor de elementos e ento liberar a estrutura que representa a matriz:
void libera (Matriz* mat) { free(mat->v); free(mat); }
A funo de acesso e atribuio pode fazer um teste adicional para garantir que no haja invaso de memria. Se a aplicao que usa o mdulo tentar acessar um elemento fora das dimenses da matriz, podemos reportar um erro e abortar o programa. A implementao destas funes pode ser dada por:
float acessa (Matriz* mat, int i, int j) { int k; /* ndice do elemento no vetor */ if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Acesso invlido!\n); exit(1); } k = i*mat->col + j; return mat->v[k]; } void atribui (Matriz* mat, int i, int j, float v) { int k; /* ndice do elemento no vetor */ if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Atribuio invlida!\n); exit(1); } k = i*mat->col + j; mat->v[k] = v; }
Matriz com vetor de ponteiros O mdulo de implementao usando a estratgia de representar a matriz por um vetor de ponteiros apresentado a seguir. O tipo que representa a matriz, neste caso, pode ser dado por:
struct matriz { int lin; int col; float** v; };
8-7
As funes para criar uma nova matriz e para liberar uma matriz previamente criada podem ser dadas por:
Matriz* cria (int m, int n) { int i; Matriz mat = (Matriz*) malloc(sizeof(Matriz)); mat->lin = m; mat->col = n; mat->v = (float**) malloc(m*sizeof(float*)); for (i=0; i<m; i++) mat->v[i] = (float*) malloc(n*sizeof(float)); return mat; } void libera (Matriz* mat) { int i; for (i=0; i<mat->lin; i++) free(mat->v[i]); free(mat->v); free(mat); }
As funes para acessar e atribuir podem ser implementadas conforme ilustrado abaixo:
float acessa (Matriz* mat, int i, int j) { if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Acesso invlido!\n); exit(1); } return mat->v[i][j]; } void atribui (Matriz* mat, int i, int j, float v) { if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Atribuio invlida!\n); exit(1); } mat->v[i][j] = v; }
Exerccio: Escreva um programa que faa uso das operaes de matriz definidas acima. Note que a estratgia de implementao no deve alterar o uso das operaes. Exerccio: Implemente uma funo que, dada uma matriz, crie dinamicamente a matriz transposta correspondente, fazendo uso das operaes bsicas discutidas acima. Exerccio: Implemente uma funo que determine se uma matriz ou no simtrica quadrada, tambm fazendo uso das operaes bsicas.
8-8
(n 2 n) n (n + 1) = 2 2 Podemos tambm determinar s como sendo a soma de uma progresso aritmtica, pois temos que armazenar um elemento da primeira linha, dois elementos da segunda, trs da terceira, e assim por diante. s =n+
s = 1 + 2 + ... + n =
n (n + 1) 2
A implementao deste tipo abstrato tambm pode ser feita com um vetor simples ou um vetor de ponteiros. A seguir, discutimos a implementao das operaes para criar uma matriz e para acessar os elementos, agora para um tipo que representa uma matriz simtrica.
Matriz simtrica com vetor simples Usando um vetor simples para armazenar os elementos da matriz, dimensionamos o vetor com apenas s elementos. A estrutura que representa a matriz pode ser dada por:
struct matsim { int dim; float* v; }; /* matriz obrigatoriamente quadrada */
Uma funo para criar uma matriz simtrica pode ser dada por:
MatSim* cria (int n) { int s = n*(n+1)/2; MatSim* mat = (MatSim*) malloc(sizeof(MatSim)); mat->dim = n; mat->v = (float*) malloc(s*sizeof(float)); return mat; }
O acesso aos elementos da matriz deve ser feito como se estivssemos representando a matriz inteira. Se for um acesso a um elemento acima da diagonal (i<j), o valor de retorno o elemento simtrico da parte inferior, que est devidamente representado. O endereamento de um elemento da parte inferior da matriz feito saltando-se os elementos das linhas superiores. Assim, se desejarmos acessar um elemento da quinta linha (i=4),
Estruturas de Dados PUC-Rio 8-9
devemos saltar 1+2+3+4 elementos, isto , devemos saltar 1+2+...+i elementos, ou seja, i*(i+1)/2 elementos. Depois, usamos o ndice j para acessar a coluna.
float acessa (MatSim* mat, int i, int j) { int k; /* ndice do elemento no vetor */ if (i<0 || i>=mat->dim || j<0 || j>=mat->dim) { printf("Acesso invlido!\n); exit(1); } if (i>=j) k = i*(i+1)/2 + j; else k = j*(j+1)/2 + i; return mat->v[k]; }
Matriz simtrica com vetor de ponteiros A estratgia de trabalhar com vetores de ponteiros para matrizes alocadas dinamicamente muito adequada para a representao matrizes simtricas. Numa matriz simtrica, para otimizar o uso da memria, armazenamos apenas a parte triangular inferior da matriz. Isto significa que a primeira linha ser representada por um vetor de um nico elemento, a segunda linha ser representada por um vetor de dois elementos e assim por diante. Como o uso de um vetor de ponteiros trata as linhas como vetores independentes, a adaptao desta estratgia para matrizes simtricas fica simples.
Para criar a matriz, basta alocarmos um nmero varivel de elementos para cada linha. O cdigo abaixo ilustra uma possvel implementao:
MatSim* cria (int n) { int i; MatSim* mat = (MatSim*) malloc(sizeof(MatSim)); mat->dim = n; mat->v = (float**) malloc(n*sizeof(float*)); for (i=0; i<n; i++) mat->v[i] = (float*) malloc((i+1)*sizeof(float)); return mat; }
O acesso aos elementos natural, desde que tenhamos o cuidado de no acessar elementos que no estejam explicitamente alocados (isto , elementos com i<j).
8-10
float acessa (MatSim* mat, int i, int j) { if (i<0 || i>=mat->dim || j<0 || j>=mat->dim) { printf("Acesso invlido!\n); exit(1); } if (i>=j) return mat->v[i][j]; else return mat->v[j][i]; }
Finalmente, observamos que exatamente as mesmas tcnicas poderiam ser usadas para representar uma matriz triangular, isto , uma matriz cujos elementos acima (ou abaixo) da diagonal so todos nulos. Neste caso, a principal diferena seria na funo acessa, que teria como resultado o valor zero em um dos lados da diagonal, em vez acessar o valor simtrico. Exerccio: Escreva um cdigo para representar uma matriz triangular inferior. Exerccio: Escreva um cdigo para representar uma matriz triangular superior.
8-11