CES-11 ALGORITMOS E
ESTRUTURAS DE DADOS
Capítulo I
Introdução
Capítulo I - Introdução
1.1 – Revisão sobre tipos, comandos e
subprogramação
1.2 – Tipos abstratos de dados
1.3 – Noções de complexidade de algoritmos
1.1 – Revisão sobre Tipos, Comandos
e Subprogramação
1.1.1 – Componentes de um sistema de informações
Um grande sistema de informações pode ser decomposto
em um conjunto de subsistemas menores interligados
Cada um desses subsistemas por sua vez pode ser
decomposto da mesma maneira
E assim por diante, até que se obtenha um conjunto de
informações indivisíveis interligadas
Exemplo: cadastro dos funcionários de uma empresa
Nome, setor de trabalho: strings
Salário: número real
Sexo: um caractere (M ou F)
Data de nascimento: vetor de 3 inteiros
Endereço: logradouro, número, bairro, cidade e estado
Número
inteiro
Strings
Informações escalares: compostas de um único elemento;
indivisíveis; exemplos: Sexo, Salário e Número do Endereço
Informações estruturadas: podem ser subdivididas
(exemplos: Nome, Endereço, Data de Nascimento, Setor de
Trabalho)
Endereço: subdividido em outras informações estruturadas
(Logradouro, Bairro, Cidade e Estado)
1.1.2 – Tipos escalares primitivos
Tipo primitivo: é um tipo escalar cujas variáveis e constantes
são declaradas por meio de palavras reservadas
a) Tipo Inteiro: (int, short, long)
Domínio: números inteiros entre - e +
Operações: + - * / %
= < >
Exemplos: 7 / 3 = 2
7 % 3=1
b) Tipo Real: (float, double)
Domínio: números reais entre - e +
Operações: + - * /
= < >
c) Tipo Caractere: (char)
Domínio:
Dígitos decimais: ‘0’, ‘1’, ... , ‘9’
Letras: ‘A’, ‘B’, ... , ‘Z’, ‘a’, ‘b’, ... , ‘z’
Sinais especiais: ‘.’ , ‘,’ , ‘;’ , ‘+’ , ‘*’ , ‘/’, ‘(’ , ‘)’,‘[’, ‘]’, ...
Caracteres de controle: ‘\n’, ‘\t’, ‘\b’, ‘\r’, ...
Operações: = ( < > + - * / %)
d) Tipo Lógico: (logic – não há em C)
Domínio: Verdade e Falso
Operações: =, ≠, and, or, not, nand, nor e xor
Resultados de comparações são valores lógicos
1.1.3 – Comandos básicos de uma linguagem
Os algoritmos apresentados nesta disciplina serão expressos
num código correspondente a um aplainamento da
Linguagem C
Quase tudo na mesma forma da Linguagem C
Comandos de entrada e saída, comandos de seleção e
outros terão forma mais simples que a da Linguagem C,
para maior clareza
Comando composto:
{ Comando Comando . . . . . Comando }
Bloco:
{ Declarações Comando Comando . . . . . Comando }
Comando de atribuição:
Operadores: =, ++, --, +=, -=, *=, /=, %=
Comandos de entrada e saída:
read (Variável , Variável , . . . . . , Variável);
write (Elemento , Elemento , . . . . . , Elemento)
Elemento pode ser uma expressão ou um string
Comando vazio:
;
Comandos condicionais:
if ( Expressão ) Comando
if ( Expressão ) Comando else Comando
Expressão condicional:
Expr1 ? Expr2 : Expr3
Comandos repetitivos:
while ( Expressão ) Comando
do Comando while Expressão ;
for ( Inicializações ; Expressão ; Atualizações )
Comando
Comando de seleção:
switch ( Expressão inteira ) {
V11, V12, . . ., V1m : Lista de comandos;
V21, V22, . . ., V2n : Lista de comandos;
........
Vi1, Vi2, . . ., Vip : Lista de comandos;
default: Lista de comandos;
}
Para maior clareza, nos algoritmos não serão usados case’s
nem break’s
Comandos de desvio:
break ;
goto Rótulo ;
1.1.4 – Tipos estruturados de uma linguagem
a) Vetores:
TipoPrimitivo V[30], W[50], X[200];
ou
typedef TipoPrimitivo vetor[30];
vetor V1, V2, V3;
b) Matrizes:
TipoPrimitivo M1[10][10][10], M2[8][20];
ou
typedef TipoPrimitivo matriz[10][10];
matriz M1, M2, M3;
Há matrizes especiais com poucos elementos não-nulos
Por simplicidade, serão abordadas apenas as matrizes
bidimensionais quadradas
Matriz diagonal:
Para armazená-la:
•Um vetor com os elementos da
diagonal principal
•Um inteiro com sua dimensão
MatDiag
5
n
Elem
5
6
2
8
1
0
3
4
1
2
Matriz tridiagonal:
Para armazená-la:
•Um vetor para armazenar os
elementos das 3 diagonais
relevantes
•Um inteiro com o número de
elementos dessas 3 diagonais
n
19
5 3 7 6 2 4 2 8 5 8 3 1
Elem
1 4 4 8 3 9 1
0
12
1
2
3
4
5
6
7
8
9
10
11
13
14
15
16
17
18
MatTriDiag
Algoritmos especiais são necessários para manipular matrizes
tridiagonais armazenadas em vetores
Matriz triangular:
Para armazená-la:
•Um vetor para
armazenar os elementos
da área não-nula
•Um inteiro com a
dimensão da matriz
MatTriang
5 4 1 3 1 6 2 7 2 2 4 3 8 9 1
Dimensão: 5
•Um flag dizendo se é
superior ou inferior
Tipo: superior
Algoritmos especiais são necessários para manipular matrizes
triangulares armazenadas em vetores
Matriz faixa:
Para armazená-la:
•Um vetor para armazenar os
elementos da área não-nula
•Um inteiro com a dimensão
da matriz
•Dois inteiros com a largura
da faixa acima e abaixo da
diagonal principal
Algoritmos especiais são necessários para manipular matrizes
faixas armazenadas em vetores
Matriz esparsa:
Para armazená-la:
•Matriz bidimensional com 3 linhas:
•Uma com os elementos não-nulos
•Outra com os números das linhas
desses elementos
•Outra com os números de suas colunas
•Um inteiro com o número de elementos
não-nulos
9
4
3
2
8
5
0
2
3
4
5
6
4
1
4
0
5
1
6 elementos
c) Strings
Declarações:
typedef char string[15];
string s1, s2;
Operações:
strcat (s1, s2);
c = strlen (s1);
strcpy (s1, s2);
strcmp (s1, s2);
strcpy (s1, “Cadeia de teste”);
d) Estruturas
Estruturas simples:
typedef struct funcionario funcionario;
struct funcionario {
char Nome[30], Endereço[30], Setor[15];
char Sexo, Estcivil; int Idade, Anosfirma ;
};
funcionario F1, F2, F3, Empregados [200];
.
.
.
.
.
Empregados[1] = F1; F2.Sexo = ‘M’;
strcpy (Empregados[3].Nome, “José da Silva”);
Estruturas de campos alternativos:
Tipo union: define um conjunto alternativo de campos que
podem ser armazenados numa mesma posição de memória.
Exemplo:
typedef union ttt ttt;
union ttt {
char c1; int c2; double c3;
char A[10];
};
ttt t1, t2, t3;
O espaço reservado
para t1 é o do maior
de seus campos
t1
c1 / c2 / c3 / A
Exemplo: Seja o cadastro dos habitantes de uma cidade
A seguir, informações pertinentes sobre um habitante genérico
A seguir, declarações para
implementar o cadastro
desses habitantes
Habitante
Hab [1000000];
typedef struct Habitante Habitante;
struct Habitante {
char nome [30], ender [30], cid_natal [20];
Data DataNasc: int esc;
Formacao form;
};
typedef union Formacao Formacao;
union Formacao {
PrimGrau pg; SegGrau sg; Superior
};
sp;
typedef struct PrimGrau PrimGrau;
struct PrimGrau {
int ano_term;
};
typedef struct SegGrau SegGrau;
struct Seggrau {
int ano_term; char tipo;
};
typedef struct Superior Superior;
struct Superior {
int ano_term;
char nome_escola[30], tipo[4];
};
typedef struct Data Data;
struct Data {
int dia, mes, ano;
};
e) Tipos enumerativos:
typedef enum Cor Cor;
enum Cor {Branca, Amarela, Verde, Azul, Preta};
typedef enum Diasemana Diasemana;
enum Diasemana {Dom, Seg, Ter, Qua, Qui, Sex, Sab};
Diasemana Ontem, Hoje, Amanha;
Cor c1, c2, c3;
1.1.5 – Subprogramação
Chamada de função:
NomeFunção (ListaArgumentos)
ListaArgumentos pode ser ou não vazia
Uma chamada de função pode aparecer em expressões
maiores
Quando aparecer isolada, seguida de ‘;’, torna-se um
comando de uma lista de comandos
Declaração de funções:
Tipo NomeFunção (ListaParâmetros) {
CorpoFunção
Funções que não retornam
}
valores serão do tipo void
ListaParâmetros pode ser ou não vazia
Parâmetros sempre são alocados quando a função é
chamada para execução
Parâmetros recebem os argumentos da chamada da função
Duas formas de passagem de argumentos: por valor ou por
referência (ou por endereço)
Passagem por valor:
void ff (int a) {
a += 1;
write ("Durante ff: a = ", a);
}
void main ( ) {
int a = 5;
write ("Antes de ff: a = ", a);
ff (a);
write ("Depois de ff: a = ", a);
}
Saída:
Antes de ff: a = 5
Durante ff: a = 6
Depois de ff: a = 5
a
56
a 5
São 2 variáveis
diferentes, de
mesmo nome: a
Passagem por referência ou endereço:
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
Seja sua execução:
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
3
i
8
j
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
3
i
8
j
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
p
q
3
i
8
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
p
q
3
i
8
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
3
p
q
3
i
8
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
3
p
q
3
i
8
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
3
p
q
8
i
8
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
3
p
q
8
i
8
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
aux
3
p
q
8
i
3
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
Desalocação das variáveis de Trocar:
aux
3
p
q
8
i
3
j
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
Antes de trocar, i = 3; j = 8
Depois de trocar, i = 8; j = 3
Resultado
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
8
i
3
j
void main ( ) {
int i = 3, j = 8;
write ("Antes de trocar, i = ", i, "; j = ", j);
trocar(&i, &j);
write ("Depois de trocar, i = ", i, "; j = ", j);
}
Passagem de variáveis indexadas:
O nome de uma variável indexada é o endereço de seu 1º
elemento
Se um dos parâmetros de uma função for uma variável
indexada, na realidade ele será um ponteiro
Caso o argumento correspondente seja o nome de uma
variável indexada:
O endereço correspondente ao seu nome é passado ao
parâmetro
Os elementos da variável-argumento não são copiados
para a função
Então essa passagem de argumento é por referência ou por
endereço
Toda alteração nos elementos da variável-parâmetro terá
efeito sobre aqueles da variável-argumento
Outros possíveis argumentos: ponteiros e endereços
A seguir um exemplo ilustrativo
void Alterar (int B[]) {
B[1] = B[3] = 7;
}
O parâmetro B de
Alterar é um ponteiro
void main () {
int i, j, A[10] = {0};
Não é necessário
write ("Vetor inicial: ");
colocar a dimensão
for (i = 0; i <= 9; i++)
write (A[i]);
Poderia ser int *B
Alterar (A);
write ("Vetor intermediario: ");
for (i = 0; i <= 9; i++) write (A[i]);
Alterar (&A[4]);
write ("Vetor final: ");
for (i = 0; i <= 9; i++) write (A[i]);
}
B
void Alterar (int B[]) {
B[1] = B[3] = 7;
}
A
0
07
0
70
0
70
0
70
0
0
A[0] A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8] A[9]
void main () {
int i, j, A[10] = {0};
B[0] B[1] B[2] B[3] B[0]
B[4] B[5]
B[1] B[2]
B[6] B[7]
B[3] B[4]
B[8] B[9]
B[5]
write ("Vetor inicial: ");
for (i = 0; i <= 9; i++)
write (A[i]);
Alterar (A);
write ("Vetor intermediario: ");
for (i = 0; i <= 9; i++) write (A[i]);
Alterar (&A[4]);
write ("Vetor final: ");
for (i = 0; i <= 9; i++) write (A[i]);
}
Vetor inicial
void Alterar (int B[]) {
B[1] = B[3] = 7;
}
:
0
0
0
0
0
0
0
0
0
0
Vetor intermediario:
0
7
0
7
0
0
0
0
0
0
Vetor final
0
7
0
7
0
7
0
7
0
0
:
void main () {
int i, j, A[10] = {0};
write ("Vetor inicial: ");
for (i = 0; i <= 9; i++)
write (A[i]);
Alterar (A);
write ("Vetor intermediario: ");
for (i = 0; i <= 9; i++) write (A[i]);
Alterar (&A[4]);
write ("Vetor final: ");
for (i = 0; i <= 9; i++) write (A[i]);
}
Resultado
Passagem de estruturas:
A passagem de uma estrutura como argumento pode ser por
valor ou por referência (endereço)
A passagem de uma grande estrutura por valor provoca a
movimentação de considerável massa de dados
Então uma grande estrutura só deve ser passada por valor,
se o subprograma alterar esse valor e se o argumento não
puder sofrer essa alteração
Caso o subprograma não altere o parâmetro correspondente, é
recomendável que tal estrutura seja passada por endereço:
Obtém-se economia de memória e melhor desempenho
1.1.6 – Ponteiros
a) Conceitos, declarações e atribuições
Ponteiros são endereços
Existem constantes e variáveis do tipo ponteiro
O endereço de uma variável a declarada num bloco qualquer
permanece o mesmo durante a execução do bloco
Então &a é uma constante do tipo ponteiro
Variável do tipo ponteiro armazena um endereço que pode
ser alterado durante a execução do programa
Exemplo: seja trecho de programa:
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
write ("Endereco(p) = ",
write ("p = ", p);
write ("Endereco(a) = ",
write ("Endereco(q) = ",
write ("q = ", q);
write ("Endereco(b) = ",
write ("Endereco(c) = ",
write ("a = ", a);
write ("b = ", b);
write ("c = ", c);
&p);
&a);
&q);
&b);
&c);
Resultado
Endereco(p)
p
Endereco(a)
Endereco(q)
q
Endereco(b)
Endereco(c)
a
b
c
=
=
=
=
=
=
=
=
=
=
1638204
1638208
1638208
1638216
1638220
1638220
1638224
13.5
15
237
Com este resultado,
pode-se desenhar o
mapa da memória a
seguir
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
1638208
p
13.5
a
1638220
q
15
b
237
c
1638208
Endereco(p)
p
Endereco(a)
Endereco(q)
q
Endereco(b)
Endereco(c)
a
b
c
Resultado
=
=
=
=
=
=
=
=
=
=
1638204
1638208
1638208
1638216
1638220
1638220
1638224
13.5
15
237
1638216
1638220
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
1638208
p
13.5
a
1638220
q
15
b
237
c
1638208
&a é uma constante-ponteiro
para double
&b é uma constante-ponteiro
para int
1638216
1638220
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
1638208
p
13.5
a
1638220
q
15
b
237
c
1638208
q é um ponteiro para o tipo int
p é um ponteiro para o tipo
double
p recebe o endereço de a
q recebe o endereço de b
1638216
p aponta para a
q aponta para b
1638220
a é apontada por p
b é apontada por q
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
1638208
p
13.5
a
1638220
q
15
b
237
c
1638208
Duas formas de representação
gráfica:
p
13.5
a
1638216
q
15
b
ou simplesmente:
p
q
1638220
13.5
a
15
b
1638224
Considerando-se as declarações:
float a; int c; float *p; int *q;
são possíveis as seguintes atribuições:
p = &a; q = &c;
p = NULL;
p
q
a
c
p
p = (float*) 1776;
p
1776
Acesso ao local apontado por um ponteiro:
Seja a declaração:
int a, b, *p;
p
a
*p
O local apontado por p é referenciado por *p
b
*p
Executando-se p = &a; então *p passa a coincidir com a
Depois, executando-se p = &b;
coincidir com b
então *p passa a
Exemplo: Seja o seguinte trecho de programa:
int a, b = 2, *p;
p = &a;
*p = 1;
b = *p;
a
1
Inicialmente
Prosseguindo
b
21
p
Exemplo: Seja a seguinte declaração:
int a = 2, b = 5, *p = &a, *q = &b;
Inicialmente:
Voltando
Fazendo
pà =
*p
situação
=q;*q; inicial:
a
52
p
b
5
q
Apesar de *p e *q serem iguais, p é diferente de q
As principais utilidades dos ponteiros são:
Alocação dinâmica de variáveis indexadas
Passagem de parâmetros por referência
Encadeamento de estruturas
O encadeamento de estruturas foi a principal razão para a
criação de ponteiros
Ele é usado intensamente em CES-11 Algoritmos e
Estruturas de Dados
b) Relação entre Ponteiros e Variáveis Indexadas:
O nome de uma variável indexada é o endereço do
primeiro de seus elementos
É uma constante-ponteiro apontando para seu elemento
zero
Não tem um local exclusivo na memória
Seja a declaração int A[8];
Representação gráfica de A:
O nome de uma
variável indexada
pode ser atribuído a
um ponteiro de
mesmo tipo
Exemplo: seja o seguinte trecho de programa:
int i, A[8], B[5], *p;
p = A;
p = B;
Inicialmente:
Após
p = B;
A;
A
Proibidos:
p tem localAexclusivo
= p;
B = p;
A e B não têm
A = B;
B = A;
A = &i;
B = &i;
A[0] A[1] A[2] A[3] A[4] A[5] A[6] A[7]
p ?
B
p tornou-se equivalente a B:
A:
B[0] B[1] B[2] B[3] B[4]
p[0] B[0],
A[0], p[1] A[1],
B[1], ... ,
p[7] B[4]
p[4]
A[7]
*A A[0] e *B B[0]
p se tornou variável indexada
Ponteiros podem ter subscritos e variáveis indexadas
admitem o operador unário ‘*’
Exemplo: supondo as declarações:
int i, A[50], *p;
A[i]
*(p+i)
é equivalente a
é equivalente a
*(A+i)
p[i]
Sabe-se que A contém o endereço de A[0], logo
p=A
equivale a
p = &A[0]
p = A+i
equivale a
p = &A[i]
a[1] tem sempre o mesmo endereço
p[1] pode variar de endereço
c) Alocação dinâmica de memória
Muitas vezes é útil reservar espaço para uma variável
indexada, em tempo de execução
Sabendo-se o seu número de elementos, reserva-se espaço
necessário e suficiente para essa variável
Em primeiro lugar, tal variável deve ser declarada como
ponteiro
Esse tipo de reserva em
tempo de execução
denomina-se alocação
dinâmica
Há uma região da memória
ocupada pelo programa
denominada heap, destinada
a essas alocações dinâmicas
A alocação pode ser feita
pela função malloc
malloc recebe como
argumento o número de
bytes a ser alocado
malloc então reserva na heap
esse número de bytes de
forma contígua
O valor retornado por malloc
é o endereço do primeiro
desses bytes (um ponteiro)
Esses bytes ficam
indisponíveis para novas
alocações
Exemplo: para alocar um vetor de 7 elementos do tipo int:
int *V;
V = (int *) malloc (7 * sizeof (int));
28 bytes
V
int
4 bytes
V pode ser usada
como vetor
heap
Tamanho dos vetores:
typedef int *vetor;
void main () {
Vetor A: 28 39 84 27
int m, i; vetor A, B, C;
Vetor B: 94 27 68 17
write ("Tamanho dos vetores: ");
Vetor C:
94
39
read (m);
A = (int *) malloc (m*sizeof(int));
B = (int *) malloc (m*sizeof(int));
C = (int *) malloc (m*sizeof(int));
write (“\nVetor A: ");
for (i = 0; i < m; i++) read (A[i]);
write (“\nVetor B: ");
for (i = 0; i < m; i++) read (B[i]);
write (“\nVetor C: ");
for (i = 0; i < m; i++)
C[i] = (A[i] > B[i])? A[i]: B[i];
for (i = 0; i < m; i++) write (C[i]);
}
7
Exemplo: seja
o programa à
72 39
esquerda
82 49 10
83
84
27
83
72
39
No vídeo
Quando um espaço assim alocado não for mais usado, ele
pode ser novamente disponibilizado para outras alocações,
mediante o comando
free (A);
Quando um programa faz muitas alocações dinâmicas, ele
pode esgotar a capacidade dessa área especial
Se, durante a execução, algumas dessas alocações perderem
sua utilidade, é bom que elas sejam novamente
disponibilizadas
Isso pode evitar tal esgotamento
d) Alocação dinâmica de matrizes:
Pode feita por
vetores de
ponteiros
Seja A uma
matriz (m x n):
typedef int *vetor;
Exemplo: leitura
typedef vetor *matriz;
uma matriz
void main () {
int m, n, i, j; matriz A;
write ("Dimensoes de uma matriz: "); read (m, n);
A = (vetor *) malloc (m * sizeof(vetor));
for (i = 0; i < m; i++)
A[i] = (int *) malloc (n * sizeof(int));
A
?
?
?
de
5
m
4
n
Dimensões
da matriz
?
?
?
A partir daqui, o ponteiro A
pode ser usado como matriz
typedef int *vetor;
Exemplo: leitura
typedef vetor *matriz;
uma matriz
void main () {
int m, n, i, j; matriz A;
write ("Dimensoes de uma matriz: "); read (m, n);
A = (vetor *) malloc (m * sizeof(vetor));
for (i = 0; i < m; i++)
A[i] = (int *) malloc (n * sizeof(int));
write ("Elementos da matriz:");
for (i = 0; i < m; i++) {
write ("Linha ", i);
for (j = 0; j < n; j++) read (A[i][j]);
}
write ("Confirmacao:");
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) write (A[i][j]);
write ("\n");
}
}
de
e) Alocação dinâmica e encadeamento de estruturas
Seja o seguinte código:
typedef struct st st;
struct st {int a; float b;};
st *p;
p = (st *) malloc (sizeof(st));
p
a
5
b
17.3
Atribuindo:
(*p).a = 5;
(*p).b = 17.3;
Outra forma de referenciar os campos de uma estrutura
apontada:
p->a = 5;
p->b = 17.3;
equivalem a (*p).a = 5;
(*p).b = 17.3;
Exemplo: sejam as seguintes declarações:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
Um dos campos da
estrutura st é um ponteiro
para a própria st
e os seguintes comandos:
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
a
5
prox
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
a
2
p
prox
a
3
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
a
5
prox
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
a
2
p
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
a
2
p
q
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
3
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
q
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
3
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
3
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
Vídeo
2
3
5
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
for (q = p; q != NULL; q = q->prox)
write (q->a);
prox
q
q == NULL:
Fim!!!
Vídeo
2
3
5
Seja a execução dos comandos:
typedef struct st st;
struct st {int a; st *prox;};
st *p, *q;
p
a
2
prox
a
3
prox
a
5
prox
q
p = (st *) malloc (sizeof(st));
p->a = 2;
p->prox = (st *) malloc (sizeof(st));
p->prox->a = 3;
p->prox->prox = (st *) malloc (sizeof(st));
p->prox->prox->a = 5;
p->prox->prox->prox = NULL;
A constante NULL
for (q = p; q != NULL; q = q->prox)
é muito usada para
write (q->a);
teste de final de
percurso
p
a
2
prox
a
3
prox
a
5
prox
q
Duas formas
de referenciar
este local
p->prox->prox->a
(*(*(*p).prox).prox).a
Qual delas é mais cômoda???
Encadeamento de estruturas é a principal razão para
incorporação de variáveis-ponteiros nas linguagens de
programação
Nesta disciplina será vista grande variedade de alternativas de
armazenamento de informações oferecida por encadeamentos
desse gênero
Modelos como listas lineares, árvores, grafos, estruturas
multi-ligadas muito se beneficiam da inclusão de um ou mais
ponteiros em variáveis do tipo struct
1.1.7 – Recursividade
Formulas recursivas escalares simples:
Fatorial:
n! =
Potenciação:
-1, para n < 0
1, para 0 ≤ n ≤ 1
n * (n-1)!, para n > 1
1, para n = 0
An =
A * An-1, para n > 0
Máximo divisor comum:
mdc (m, n) =
∞, p/ m = 0 e n = 0
m, p/ m > 0 e n = 0
n, p/ m = 0 e n > 0
mdc (n, m%n), p/ m e n > 0
int fat (int
int f;
if (n < 0)
else if (n
else f = n
return f;
}
n) {
f = -1;
<= 1) f = 1;
* fat(n-1);
void main() {
char c; int n;
write ("Calculo do fatorial de n");
write ("Digite n: "); read (n);
write ("Fat(", n, ") = ", fat(n));
}
Exemplo: execução de
um programa com
uma função recursiva
para o cálculo de
fatorial
int fat (int
int f;
if (n < 0)
else if (n
else f = n
return f;
}
n) {
f = -1;
<= 1) f = 1;
* fat(n-1);
void main() {
char c; int n;
write ("Calculo do fatorial de n");
write ("Digite n: "); read (n);
write ("Fat(", n, ") = ", fat(n));
}
5
n
Valor digitado: 5
int fat (int
int f;
if (n < 0)
else if (n
else f = n
return f;
}
n) {
f = -1;
<= 1) f = 1;
* fat(n-1);
fat – v1
fat – v2
fat – v3
n
n
4
n
3
f
24
f
6
5
f 120
f = 5*fat(4)
void main() {
char c; int n;
write ("Calculo do fatorial de n");
write ("Digite n: "); read (n);
write ("Fat(", n, ") = ", fat(n));
}
f = 4*fat(3)
f = 3*fat(2)
fat – v5
fat – v4
n
1
n
2
f
1
f
2
f =1
f = 2*fat(1)
5
n
Valor digitado: 5
Recursividade com variáveis indexadas
Exemplo: formulação recursiva para a procura binária
Vet
-5
-1
4
7
10
14
15
17
21
23
24
30
32
38
45
50
53
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inf
x
med
sup
ProcBinaria (x, inf, sup, Vet) =
FALSO se (x < Vet[inf] ou x > Vet[sup])
VERDADE se (x == Vet[med])
ProcBinaria (x, inf, med-1, Vet) se (x < Vet[med])
ProcBinaria (x, med+1, sup, Vet) se (x > Vet[med])
- - - - typedef int *vetor;
- - - - logic ProcBinaria (int, int, int, vetor);
A
A[0]
void main () {
int - - - -, n, num; vetor A;
logic estah; - - - - Alocacao, leitura e ordenacao do vetor A
estah = ProcBinaria (num, 0, n-1, A);
- - - - }
logic ProcBinaria (int x, int inf, int sup, vetor Vet) {
int med; logic r;
if (x < Vet[inf] || x > Vet[sup]) r = FALSE;
Vet
else {
med = (inf + sup) / 2;
if (x == Vet[med]) r = TRUE;
else if (x < Vet[med]) r = ProcBinaria (x, inf, med-1, Vet);
else r = ProcBinaria (x, med+1, sup, Vet);
}
return r;
Os ponteiros Vet de todas as chamadas
}
recursivas apontarão para A[0]