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]