Universidade Católica de Pelotas Centro Politécnico Ciência da Computação Estruturas de Dados em C por Prof. Dr. Paulo Roberto Gomes Luzzardi [email protected] [email protected] http://infovis.ucpel.tche.br/luzzardi http://graphs.ucpel.tche.br/luzzardi http://gcg.ucpel.tche.br Versão 2.02 Referências Bibliográficas CORMEN, et al. Algoritmos - Teoria e Prática. Rio de Janeiro: Campus, 2002. VELOSO, Paulo e SANTOS, Clésio - Estruturas de Dados - Editora Campus, 4 ed., Rio de Janeiro, 1986. WIRTH, Niklaus. Algoritmos e Estruturas de Dados. Rio de Janeiro: Prentice-Hall do Brasil, 1989. PINTO, Wilson - Introdução ao Desenvolvimento de Algoritmos e Estrutura de Dados, Editora Érica, 1994. Pelotas, 25 de fevereiro de 2009 1 Sumário 1. Tipos de Dados 1.1 Conceitos Básicos 1.2 Tipos Primitivos 1.3 Construção de Tipos (Estruturados ou Complexos) 1.3.1 Strings 1.3.2 Vetor (Agregados Homogêneos) 1.3.3 Struct (Estrutura) 1.3.4 Ponteiros (Apontadores) 1.4 Operadores (Aritméticos, Relacionais e Lógicos) 1.4.1 Aritméticos 1.4.2 Relacionais 1.4.3 Lógicos 2. Vetores e Matrizes 2.1 Conceitos Básicos 3. Listas Lineares 3.1 Listas Genéricas 3.2 Tipos de Representações 3.2.1 Lista Representada por Contigüidade Física 3.2.2 Lista Representada por Encadeamento 3.2.3 Lista Encadeada com Descritor 3.2.4 Lista Duplamente Encadeada 3.2.5 Listas com disciplinas de Acesso 3.2.5.1 Filas 3.2.5.1.1 Fila com Vetor 3.2.5.1.2 Fila Circular 3.2.5.1.3 Fila com Alocação Dinâmica 3.2.5.2 Pilhas 3.2.5.2.1 Pilha com Vetor 3.2.5.2.2 Pilha com Alocação Dinâmica 3.2.5.2.3 Analisador de Expressões usando Pilha 3.2.5.3 Deques 3.3 Representação por Contigüidade Física 3.4 Representação por Encadeamento 2 4. Arquivos 4.1 Sistema de Arquivo Bufferizado 4.2 Argumentos argc e argv 5. Pesquisa de Dados 5.1 Pesquisa Seqüencial 5.2 Pesquisa Binária 5.3 Cálculo de Endereço (Hashing) 6. Classificação de Dados (Ordenação) 6.1 6.2 6.3 6.4 Classificação por Força Bruta Vetor Indireto de Ordenação (Tabela de Índices) Classificação por Encadeamento Métodos de Classificação Interna 6.4.1 Método por Inserção Direta 6.4.2 Método por Troca 6.4.2.1 Método da Bolha (Bubble Sort) 6.4.3 Método por Seleção 6.4.3.1 Método por Seleção Direta 7. Árvores 7.1 Conceitos Básicos 7.2 Árvores Binárias 7.3 Representações 7.3.1 Representação por Contigüidade Física 7.3.2 Representação por Encadeamento 7.4 Caminhamentos em Árvores 7.4.1 Caminhamento Pré-Fixado (Pré-Ordem) 7.4.2 Caminhamento In-Fixado (Central) 7.4.3 Caminhamento Pós-Fixado 7.4.4 Algoritmos recursivos para percorrer Árvores Binárias 7.4.4.1 Caminhamento Pré-Fixado (Pré-Ordem) 7.4.4.2 Caminhamento In-Fixado (Central) 7.4.4.3 Caminhamento Pós-Fixado 7.5 Árvore de Busca Binária 7.6 Árvores AVL 7.6.1 Inserção em uma árvore AVL 7.6.2 Remoção em uma árvore AVL 3 8. Grafos 8.1 Conceitos 8.2 Representação por Lista e Matriz de Adjacências 8.2.1 Lista de Adjacências 8.2.2 Matriz de Adjacências 8.3 Percurso em Amplitude e Percurso em Profundidade 8.4 Determinação do Caminho Mínimo 4 1. Tipos de Dados 1.1 Conceitos Básicos Estruturas de Dados Estuda as principais técnicas de representação e manipulação de dados na memória principal (Memória de Acesso Randômico, RAM – Random Access Memory). Organização de Arquivos Estuda as principais técnicas de representação e manipulação de dados na memória secundária (Disco). Conceito Dados São as informações a serem representadas, armazenadas ou manipuladas. Tipos de Dados É o conjunto de valores que uma constante, ou variável, ou expressão pode assumir, ou então a um conjunto de valores que possam ser gerados por uma função. Na definição de uma variável, constante, expressão ou função deve-se definir o Tipo de Dado, por algumas razões: 1) Representar um tipo abstrato de dado (Realidade); 2) Delimitar a faixa de abrangência (Limites); 3) Definir a quantidade de bytes para armazenamento; 5 4) E as operações que podem ser efetuadas. Os tipos de dados podem ser: Primitivos ou Estruturados, sendo que os estruturados, são chamados de Complexos. 1.2 Tipos Primitivos São os tipos de dados que, além de depender das características do sistema, dependem do processador e do co-processador. Tipos primitivos da Linguagem de Programação C: CARACTER ( char ch; ) INTEIRO ( int i; ) REAL ( float f; ou double d;) Tipo Bytes char 1 Int 2 float 4 double 8 void 0 Bits 8 16 32 64 0 Faixa de valores -128 à 127 -32768 à 32767 -3.4E-38 à 3.4E+38 -1.7E-308 à 1.7E+308 Sem tipo 1.3 Construção de Tipos (Estruturados ou Complexos) Tipos obtidos através de tipos primitivos, podem ser: STRING (Cadeia de Caracteres) ( char *s; ou char s[11]; ) VETOR (Agregados Homogêneos) ( int v[10]; ) ESTRUTURA (Agregados Heterogêneos) ( struct ) PONTEIRO (Apontadores) ( int *p; ) 1.3.1 String (Cadeia de Caracteres) Tipo de dado que permite que uma variável possua vários caracteres. Exemplo: char nome[30]=”UCPel”; ou char *s=”UCPel\n”; 6 1.3.2 Vetor (Agregados Homogêneos) Tipo de dado que permite que uma variável possua vários elementos, todos do mesmo tipo. Exemplo: #define MAX 10 float vetor[MAX]; ............. vetor[0] até vetor[9] 1.3.3 Struct (Estrutura) Tipo de dado que permite que uma variável possua vários campos. Os campos podem ser de tipos de dados distintos. Exemplos: #define MAX 50 struct ALUNO { int matricula; char nome[30]; char endereco[40]; } struct ALUNO turma[MAX]; struct DATA { int dia; int mes; int ano; } data; Acesso aos elementos: data.dia, data.mes ou data.ano struct TEMPO { int horas; int minutos; int segundos; } *tempo; 7 Acesso aos elementos tempo->horas, tempo->minutos ou tempo->segundos ou (*tempo).horas, (*tempo).minutos ou (*tempo).segundos 1.3.4 Ponteiros (Apontadores) É um tipo de dado, onde a variável contém o endereço de outra variável, ou um endereço de memória. Permite ainda, alocação dinâmica de Memória, ou seja, alocação de memória em tempo de execução do programa. Exemplo: int *p; Exemplos: #include <stdio.h> int main(void) { int *p; int n; n = 65; p = &n; printf(“Conteúdo: %d\n”,*p); getchar(); } &n ....... Endereço da variável “n” na memória principal (RAM), sendo que o endereço é formado de Segmento:OffSet (segmento e deslocamento). #include <stdio.h> #include <stdlib.h> 8 int main(void) { int *p; int i,n; printf("Quantos valores: "); scanf("%d",&n); p = (int *) malloc(sizeof(int)*n); if (p == NULL) printf("ERRO FATAL: Falta de Memória"); else { for (i = 0;i < n;i++) p[i] = i; for (i = 0;i < n;i++) printf("Conteúdo: %d\n",p[i]); free(p); } getchar(); } Exemplo: #include <stdio.h> #include <stdlib.h> int main(void) { int *p; int i,n; printf("Quantos valores: "); scanf("%d",&n); p = (int *) malloc(sizeof(int)*n); if (p == NULL) printf("ERRO FATAL: Falta de Memória"); else { for (i = 1;i <= n;i++) { *p = i; p++; } for (i = 1;i <= n;i++) { p--; printf("Conteúdo: %d\n",*p); } free(p); } getchar(); 9 } Definições malloc(p) ....................... Aloca dinamicamente memória para o ponteiro "p" free(p) .......................... Desaloca memória ocupada pela variável "p" NULL ............................. Palavra reservada para ponteiro NULO p ................................... Endereço da memória RAM *p ................................. Conteúdo do ponteiro &p ................................. Endereço do ponteiro 1.4 Operadores (Aritméticos, Relacionais e Lógicos) 1.4.1 Aritméticos + * / % ++ -- Adição Subtração Multiplicação Divisão Resto Inteiro da Divisão Incremento Decremento 1.4.2 Relacionais > < >= <= == != Maior Menor Maior ou Igual Menor ou Igual Igual Diferente 1.4.3 Lógicos && || ! e ou não 10 2. Vetores e Matrizes Permitem armazenamento de vários dados na memória RAM ao mesmo instante de tempo e com contigüidade física, ou seja, uma variável com possui vários elementos, igualmente distanciados, ou seja, um ao lado do outro. 2.1 Conceitos Básicos Vetor: (É uma matriz unidimensional) #define MAX 7 int vetor[MAX]; Matriz: (Possui mais de uma dimensão) #define M 3 #define N 4 float matriz[M][N]; Índice: Constante numérica inteira que referencia cada elemento Exemplos: Dada a definição acima: vetor[0]...................... primeiro elemento vetor[MAX-1].............. último elemento m[0][0]...................... primeiro elemento m[M-1][N-1]............... último elemento Entrada de um Vetor for (i = 0;i < MAX;i++) scanf(“%d”, &vetor[i]); Entrada de uma Matriz Bidimensional for (i = 0;i < M;i++) for (j = 0;j < N;j++) scanf(“%f”, &matriz[i][j]); 11 Exercícios: 1) Escreva um programa em C que lê uma matriz A (6x6) e cria 2 vetores SL e SC de 6 elementos que contenham respectivamente a soma das linhas (SL) e a soma das colunas (SC). Imprimir os vetores SL e SC. 2) Escreva um programa em C que lê uma matriz A (12x13) e divide todos os elementos de cada uma das 12 linhas de A pelo valor do maior elemento daquela linha. Imprimir a matriz A modificada. Observação: Considere que a matriz armazena apenas elementos inteiros 3) Escreva um programa em C que insere números inteiros (máximo 10 elementos) em um vetor mantendo-o ordenado. Quando o usuário digitar um ZERO o programa deve imprimir na tela o vetor ordenado. Operações sobre os Dados • • • • • Criação dos Dados Manutenção dos Dados • Inserção de um Componente • Remoção de um Componente • Alteração de um Componente Consulta aos Dados Destruição dos Dados Pesquisa e Classificação Alocação de Memória (RAM - Random Access Memory) Alocação Estática de Memória É a forma mais simples de alocação, na qual cada dado tem sua área reservada, não variando em tamanho ou localização ao longo da execução do programa. float f; // a variável “f” ocupa 4 bytes durante toda a execução do programa 12 Alocação Dinâmica de Memória Nesta forma de alocação, são feitas requisições e liberações de porções da memória ao longo da execução do programa. Para isto, são usadas variáveis do tipo Ponteiro. int *p; // a variável “p” poderá ocupar “n” bytes a qualquer momento Célula, Nodo ou Nó Espaço reservado (alocado) na memória RAM para uma variável (tipo primitivo ou complexo), ou seja, número de bytes “gastos” para o armazenamento de um dado. Campo É uma subdivisão de uma célula, ou seja, cada elemento de uma estrutura (struct). No exemplo abaixo, tempo é uma célula e horas, minutos e segundos são os campos. struct TEMPO { int horas; int minutos; int segundos; } *tempo; 3. Listas Lineares 3.1 Listas Genéricas Conceito Conjunto de dados que mantém a relação de ordem Linear entre os componentes. É composta de elementos (componentes ou nós), os quais podem conter um dado primitivo ou estruturado. Lista Linear É uma estrutura que permite representar um conjunto de dados de forma a preservar a relação de ordem entre eles. 13 Uma lista linear X é um conjunto de nodos (nós) X1, X2, ... Xn, Tais que: 1) Existem “n” nodos na lista (n >= 0) 2) X1 é o primeiro nodo da lista 3) Xn é o último nodo da lista 4) Para todo i,j entre 1 e n, se i<j, então o elemento Xi antecede o elemento Xj 5) Caso i = j-1, Xi é o antecessor de Xj e Xj é o sucessor de Xi Observação: Quando n=0, diz-se que a Lista é Vazia Exemplos de Listas • • • Lista de clientes de um Banco Lista de Chamada Fichário Operações sobre Listas 1) Percurso Permite utilizar cada um dos elementos de uma lista, de tal forma que: • • • 0 primeiro nodo utilizado é o primeiro da lista; Para utilizar o nodo Xj, todos os nodos de X1 até X(j-1) já foram utilizados; último nodo utilizado é o último nodo da lista. 14 2) Busca Procura um nodo específico da lista linear, de tal forma que: • • nodo é identificado por sua posição na lista; nodo é identificado pelo seu conteúdo. 3) Inserção Acrescenta um nodo X a uma lista linear, de tal forma que: • • • nodo X terá um sucessor e/ou um antecessor; Após inserir o nodo X na posição i (i >= 1 e i <= n+1), ele passará a ser i-ésimo nodo da lista; número de elementos (n) é acrescido de uma unidade. 4) Retirada (Exclusão) Retira um nodo X da lista, de tal forma que: • • Se Xi é o elemento retirado, o seu sucessor passa a ser o sucessor de seu antecessor. X(i+1) passa a ser o sucessor de X(i-1). Se Xi é o primeiro nodo, o seu sucessor passa a ser o primeiro, se Xi é o último, o seu antecessor passa a ser o último; número de elementos (n) é decrescido de uma unidade. Operações Válidas sobre Listas • • • • • • • • Acessar um elemento qualquer da lista; Inserir um novo elemento à lista; Concatenar duas listas; Determinar o número de elementos da lista; Localizar um elemento da lista com um determinado valor; Excluir um elemento da lista; Alterar um elemento da lista; Criar uma lista; 15 • Destruir a lista. 3.2 Tipos de Representações 3.2.1 Lista Representada por Contigüidade Física Os nodos são armazenados em endereços contíguos, ou igualmente distanciados um do outro. Os elementos são armazenados na memória um ao lado do outro, levando-se em consideração o tipo de dado, ou seja, a quantidade de bytes. Se o endereço do nodo Xi é conhecido, então o endereço do nodo X(i+1) pode ser determinado. • • Os relacionamentos são representados pela disposição física dos componentes na memória; A posição na estrutura lógica determina a posição na estrutura física. Observação: Uma lista pode ser implementada através de um vetor de “m” elementos. Atenção: Se “n’ = “m” a Lista é chamada Cheia Observação: Como o número de nodos armazenados na lista pode ser modificado durante a execução do programa, deve-se representar como parte de um vetor de “m” elementos com “n <= m”. 16 Representação: A lista X está representada por um vetor V de “m” elementos. Componentes de uma Lista • • • Número de nodos da lista (n); Vetor de nodos (v); Tamanho total da lista (m). #define m 7 typedef float TDADOS; typedef struct { int n; TDADOS v[m]; } TLISTA; TLISTA l; 17 Observação: Considera-se que o primeiro armazenado na primeira posição do vetor. Exemplo: typedef int TDADOS; #define SUCESSO 1 #define LISTA_CHEIA 2 // ------------------------------------ Cria_Lista void Cria_Lista(TLISTA *x) { x->n = 0; } // ------------------------------------ Inclui_Fim int Inclui_Fim(TLISTA *x, TDADOS no) { if (x->n == m) return(LISTA_CHEIA); else { x->v[x->n] = no; x->n = x->n + 1; return(SUCESSO); } } // ------------------------------------ Inclui_Inicio int Inclui_Inicio(TLISTA *x, TDADOS no) { int i; if (x->n == m) return(LISTA_CHEIA); else { for (i = x->n-1;i >= 0;i--) x->v[i+1] = x->v[i]; x->v[0] = no; x->n = x->n + 1; return(SUCESSO); } } 18 nodo da lista será 4) Incluir dados em uma lista de números inteiros (máximo 10 elementos), mantendo-a ordenada. Solução do problema proposto (4): // Inserir Lista mantendo Ordenada #include <stdio.h> // ---------------------------------------------- Definições #define m 5 #define SUCESSO 0 #define LISTA_CHEIA 1 #define LISTA_VAZIA 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int n; TDADOS v[m]; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *x); int Inclui_Fim(TLISTA *x, TDADOS dado); int Inclui_Inicio(TLISTA *x, TDADOS dado); int Inclui_Posicao(TLISTA *x, TDADOS dado, int pos); int Verifica_Posicao(TLISTA *x, TDADOS dado); void Exibe_Lista(TLISTA x); void Imprime_Erro(int erro); // ------------------------------------ Programa Principal int main(void) { TLISTA l; TDADOS valor; int erro; Cria_Lista(&l); printf("Valor: "); scanf("%d", &valor); if (valor != 0) { Inclui_Fim(&l,valor); 19 do { Exibe_Lista(l); printf("\nValor: "); scanf("%d",&valor); if (valor != 0) { erro = Verifica_Posicao(&l,valor); if (erro) Imprime_Erro(erro); } } while (valor != 0 && erro != LISTA_CHEIA); } Exibe_Lista(l); getchar(); } // ------------------------------------ Cria_Lista void Cria_Lista(TLISTA *x) { x->n = 0; } // ------------------------------------ Inclui_Fim int Inclui_Fim(TLISTA *x, TDADOS dado) { if (x->n == m) return(LISTA_CHEIA); else { x->v[x->n] = dado; x->n = x->n + 1; return(SUCESSO); } } // ------------------------------------ Inclui_Inicio int Inclui_Inicio(TLISTA *x, TDADOS dado) { int i; if (x->n == m) return(LISTA_CHEIA); else { for (i = x->n-1;i >= 0;i--) x->v[i+1] = x->v[i]; x->v[0] = dado; x->n = x->n + 1; 20 return(SUCESSO); } } // ------------------------------------ Inclui_Posicao int Inclui_Posicao(TLISTA *x, TDADOS dado, int pos) { int i; if (x->n == m) return(LISTA_CHEIA); else { for (i = x->n-1;i >= pos;i--) x->v[i+1] = x->v[i]; x->v[pos] = dado; x->n = x->n + 1; return(SUCESSO); } } // ------------------------------------ Verifica_Posicao int Verifica_Posicao(TLISTA *x, TDADOS dado) { int i=0; do { if (dado < x->v[i]) return(Inclui_Posicao(x,dado,i)); i++; } while (i < x->n); return(Inclui_Fim(x,dado)); } // ------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case LISTA_CHEIA: printf("ERRO: Lista Cheia\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; } } // ------------------------------------ Exibe_Lista 21 void Exibe_Lista(TLISTA x) { int i; printf("Lista Ordenada: "); for (i = 0;i < x.n;i++) printf("%02d ",x.v[i]); } 5) Incluir dados em uma lista linear de números inteiros (máximo 50) sem repetição. O programa termina quando o dado lido for zero, então o programa deve imprimir a lista na tela sem repetição. Solução do problema proposto (5): // Inserir Lista sem Repeticao #include <stdio.h> // ---------------------------------------------- Definições #define m 5 #define TRUE !0 #define FALSE 0 #define #define #define #define SUCESSO 0 LISTA_CHEIA 1 LISTA_VAZIA 2 DADO_REPETIDO 3 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int n; TDADOS v[m]; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *x); int Inclui_Fim(TLISTA *x, TDADOS dado); int Inclui_Inicio(TLISTA *x, TDADOS dado); int Insere_Sem_Repeticao(TLISTA *x, TDADOS dado); void Imprime_Erro(int erro); void Exibe_Lista(TLISTA x); // ------------------------------------ Programa Principal 22 int main(void) { TLISTA l; TDADOS valor; int erro; Cria_Lista(&l); printf("Valor: "); scanf("%d", &valor); if (valor != 0) { Inclui_Fim(&l,valor); do { Exibe_Lista(l); printf("\nValor: "); scanf("%d",&valor); if (valor != 0) { erro = Insere_Sem_Repeticao(&l,valor); if (erro) Imprime_Erro(erro); } } while (valor != 0 && erro != LISTA_CHEIA); } Exibe_Lista(l); getchar(); } // ------------------------------------ Cria_Lista void Cria_Lista(TLISTA *x) { x->n = 0; } // ------------------------------------ Inclui_Fim int Inclui_Fim(TLISTA *x, TDADOS dado) { if (x->n == m) return(LISTA_CHEIA); else { x->v[x->n] = dado; x->n = x->n + 1; return(SUCESSO); } } // ------------------------------------ Inclui_Inicio 23 int Inclui_Inicio(TLISTA *x, int dado) { int i; if (x->n == m) return(LISTA_CHEIA); else { for (i = x->n-1;i >= 0;i--) x->v[i+1] = x->v[i]; x->v[0] = dado; x->n = x->n + 1; return(SUCESSO); } } // ------------------------------------ Insere_Sem_Repeticao int Insere_Sem_Repeticao(TLISTA *x, TDADOS dado) { int i,achei = FALSE; if (x->n == m) return(LISTA_CHEIA); else { for (i = 0;i < x->n;i++) if (x->v[i] == dado) { achei = TRUE; return(DADO_REPETIDO); } if (!achei) return(Inclui_Fim(x,dado)); } return(SUCESSO); } // ------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case LISTA_CHEIA: printf("ERRO: Lista Cheia\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; case DADO_REPETIDO: printf("ERRO: Dado Repetido\n"); break; 24 } } // ------------------------------------ Exibe_Lista void Exibe_Lista(TLISTA x) { int i; printf("Lista sem Repetição: "); if (x.n != 0) for (i = 0;i < x.n;i++) printf("%02d ",x.v[i]); } Contigüidade Física Uma alternativa para representação por contigüidade física é não iniciar no início do vetor, isto facilita as inserções. Observação: As operações de inclusão e exclusão de nodos podem optar pela extremidade da lista que irá diminuir (no caso de exclusão) ou aumentar (no caso de inserção) de comprimento. “A escolha deverá considerar o caso que produz menor movimentação de elementos”. typedef float TDADOS; typedef struct { int inicio; int fim; TDADOS v[m]; } TLISTA; TLISTA l; Lista vazia 25 início = -1 fim = -1 Lista cheia início = 0 fim = m-1 6) Escreva um programa em C que permite a inclusão de números inteiros no início ou no fim da lista linear (máximo 7 elementos). Solução do problema proposto (6): // Lista Linear #include <stdio.h> #include <string.h> #include <ctype.h> // ---------------------------------------------- Definições #define m 7 #define SUCESSO 0 #define LISTA_CHEIA 1 #define LISTA_VAZIA 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int inicio; int fim; TDADOS v[m]; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *x); int Inclui_Inicio(TLISTA *x, TDADOS dado); int Inclui_Fim(TLISTA *x, TDADOS dado); void Exibe_Lista(TLISTA x); void Imprime_Erro(int erro); // ---------------------------------------------- Programa Principal int main(void) 26 { TLISTA l; TDADOS valor; int erro; char ch; Cria_Lista(&l); do { Exibe_Lista(l); printf("\nValor: "); scanf("%d",&valor); if (valor != 0) { printf("[I]nício ou [F]im ?"); do { ch = toupper(getchar()); } while (!strchr("IF",ch)); switch (ch) { case 'I': erro = Inclui_Inicio(&l,valor); break; case 'F': erro = Inclui_Fim(&l,valor); break; } if (erro) Imprime_Erro(erro); } } while (valor != 0 && erro != LISTA_CHEIA); Exibe_Lista(l); getchar(); } // ---------------------------------------------- Cria_Lista void Cria_Lista(TLISTA *x) { x->inicio = -1; x->fim = -1; } // ---------------------------------------------- Inclui_Inicio int Inclui_Inicio(TLISTA *x, TDADOS dado) { if (x->inicio == -1) { x->inicio = m / 2; x->fim = x->inicio; x->v[x->inicio] = dado; return(SUCESSO); } 27 else if (x->inicio == 0) return(LISTA_CHEIA); else { x->inicio = x->inicio - 1; x->v[x->inicio] = dado; return(SUCESSO); } } // ---------------------------------------------- Inclui_Fim int Inclui_Fim(TLISTA *x, TDADOS dado) { if (x->fim == -1) { x->inicio = m / 2; x->fim = x->inicio; x->v[x->fim] = dado; return(SUCESSO); } else if (x->fim == m-1) return(LISTA_CHEIA); else { x->fim = x->fim + 1; x->v[x->fim] = dado; return(SUCESSO); } } // ---------------------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case LISTA_VAZIA: printf("\nERRO: Lista Vazia"); break; case LISTA_CHEIA: printf("\nERRO: Lista Cheia"); break; } } // ---------------------------------------------- Exibe_Lista void Exibe_Lista(TLISTA x) { int i; 28 printf("\nLista: "); if (x.inicio != -1) for (i = x.inicio;i <= x.fim;i++) printf("%02d ",x.v[i]); else printf("VAZIA"); } 7) Escreva um programa em C que permite a inclusão de números inteiros no início ou no fim da lista linear (máximo 7 elementos) avisando qual lado está cheio. Observação: Note que a lista pode estar cheia num lado e não estar cheia no outro lado. A próxima solução (7) avisa ao usuário qual lado da lista linear está cheio. Solução do problema proposto (7): // Lista Linear no meio do Vetor #include <stdio.h> #include <string.h> #include <ctype.h> // ---------------------------------------------- Definições #define m 7 #define SUCESSO 0 #define LISTA_CHEIA_ESQUERDA 1 #define LISTA_CHEIA_DIREITA 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int inicio; int fim; TDADOS v[m]; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *x); int Inclui_Inicio(TLISTA *x, TDADOS dado); int Inclui_Fim(TLISTA *x, TDADOS dado); void Imprime_Erro(int erro); 29 void Exibe_Lista(TLISTA x); // ---------------------------------------------- Programa Principal int main(void) { TLISTA l; TDADOS valor; int erro; char ch; Cria_Lista(&l); do { Exibe_Lista(l); printf("\nValor: "); scanf("%d",&valor); if (valor != 0) { printf("[I]nício ou [F]im ?"); do { ch = toupper(getchar()); } while (!strchr("IF",ch)); switch (ch) { case 'I': erro = Inclui_Inicio(&l,valor); break; case 'F': erro = Inclui_Fim(&l,valor); break; } if (erro) Imprime_Erro(erro); } } while (valor != 0); Exibe_Lista(l); getchar(); } // ---------------------------------------------- Cria_Lista void Cria_Lista(TLISTA *x) { x->inicio = -1; x->fim = -1; } // ---------------------------------------------- Inclui_Inicio int Inclui_Inicio(TLISTA *x, TDADOS dado) { if (x->inicio == -1) { 30 x->inicio = m / 2; x->fim = x->inicio; x->v[x->inicio] = dado; return(SUCESSO); } else if (x->inicio == 0) return(LISTA_CHEIA_ESQUERDA); else { x->inicio = x->inicio - 1; x->v[x->inicio] = dado; return(SUCESSO); } } // ---------------------------------------------- Inclui_Fim int Inclui_Fim (TLISTA *x, TDADOS dado) { if (x->fim == -1) { x->inicio = m / 2; x->fim = x->inicio; x->v[x->fim] = dado; return(SUCESSO); } else if (x->fim == m-1) return(LISTA_CHEIA_DIREITA); else { x->fim = x->fim + 1; x->v[x->fim] = dado; return(SUCESSO); } } // ---------------------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case LISTA_CHEIA_ESQUERDA: printf("\nERRO: Lista Cheia na ESQUERDA"); break; case LISTA_CHEIA_DIREITA: printf("\nERRO: Lista Cheia na DIREITA"); break; } } 31 // ---------------------------------------------- Exibe_Lista void Exibe_Lista(TLISTA x) { int i; printf("\nLista: "); if (x.inicio == -1) printf("VAZIA"); else for (i = x.inicio;i <= x.fim;i++) printf("%02d ",x.v[i]); } 8) Escreva um programa em C que permite a inclusão de números inteiros em uma lista linear no início, fim e na posição escolhida pelo usuário Solução do problema proposto (8): // Lista Linear meio do vetor #include <stdio.h> #include <string.h> #include <ctype.h> // ---------------------------------------------- Definições #define m 7 #define SUCESSO 0 #define LISTA_CHEIA 1 #define LISTA_VAZIA 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int inicio; int fim; TDADOS v[m]; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *x); int Inclui_Inicio(TLISTA *x, TDADOS dado); int Inclui_Fim(TLISTA *x, TDADOS dado); 32 int Inclui_Posicao(TLISTA *x, TDADOS dado, int pos); void Exibe_Lista(TLISTA x); void Imprime_Erro(int erro); // ------------------------------------ Programa Principal int main(void) { TLISTA l; TDADOS valor; int erro,pos,inic,fim; char ch; Cria_Lista(&l); do { Exibe_Lista(l); printf("\nValor: "); scanf("%d",&valor); if (valor != 0) { printf("[I]nício, [P]osição ou [F]im ?"); do { ch = toupper(getchar()); } while (!strchr("IFP",ch)); switch (ch) { case 'I': erro = Inclui_Inicio(&l,valor); break; case 'F': erro = Inclui_Fim(&l,valor); break; case 'P': if (l.inicio == -1) inic = 1; else if (l.inicio == 0) inic = 1; else inic = l.inicio +1; if (l.fim == -1) fim = m; else fim = l.fim + 1; printf("\n"); do { printf("Posição [%d..%d]: ",inic,fim); scanf("%d",&pos); } while (!(pos >= inic && pos <= fim)); erro = Inclui_Posicao(&l,valor,pos-1); break; } } if (erro) 33 Imprime_Erro(erro); } while (valor != 0); Exibe_Lista(l); getchar(); } // ------------------------------------ Cria_Lista void Cria_Lista(TLISTA *x) { x->inicio = -1; x->fim = -1; } // ------------------------------------ Inclui_Inicio int Inclui_Inicio(TLISTA *x, TDADOS dado) { if (x->inicio == -1) { x->inicio = m / 2; x->fim = x->inicio; x->v[x->inicio] = dado; return(SUCESSO); } else if (x->inicio == 0) return(LISTA_CHEIA); else { x->inicio = x->inicio - 1; x->v[x->inicio] = dado; return(SUCESSO); } } // ------------------------------------ Inclui_Fim int Inclui_Fim(TLISTA *x, TDADOS dado) { if (x->fim == -1) { x->fim = m / 2; x->inicio = x->fim; x->v[x->fim] = dado; return(SUCESSO); } else if (x->fim == m-1) return(LISTA_CHEIA); else 34 { x->fim = x->fim + 1; x->v[x->fim] = dado; return(SUCESSO); } } // ----------------------------------- Inclui_Posicao int Inclui_Posicao(TLISTA *x, TDADOS dado, int pos) { int i,erro; if (x->inicio == -1) { x->inicio = pos; x->fim = pos; x->v[x->inicio] = dado; return(SUCESSO); } else if (x->inicio == 0 && x->fim == m-1) return(LISTA_CHEIA); else if (pos == x->inicio-1) { erro = Inclui_Inicio(x,dado); return(erro); } else if (pos == x->fim+1) { erro = Inclui_Fim(x,dado); return(erro); } else { for (i = x->fim;i >= pos;i--) x->v[i+1] = x->v[i]; x->v[pos] = dado; x->fim = x->fim + 1; return(SUCESSO); } } // ----------------------------------- Exibe_Lista void Exibe_Lista(TLISTA x) { int i; 35 printf("\nLista: "); if (x.inicio == -1) printf("VAZIA"); else for (i = x.inicio;i <= x.fim;i++) printf("%02d ",x.v[i]); } // ----------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case LISTA_CHEIA: printf("ERRO: Lista Cheia\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; } } Vantagens e Desvantagens da Representação por Contigüidade Física Vantagens • • • A consulta pode ser calculada (acesso randômico aos dados); Facilita a transferência de dados (área de memória contígua); Adequada para o armazenamento de estruturas simples. Desvantagens • • • • • O tamanho máximo da lista precisa ser conhecido e alocado antecipadamente, pois a lista é alocada estaticamente na memória; Inserções e remoções podem exigir considerável movimentação de dados; Inadequada para o armazenamento de estruturas complexas; Mantém um espaço de memória ocioso (não ocupado); Como a lista é limitada, devem ser testados os limites. 3.2.2 Lista representada por Encadeamento Permite Alocação Dinâmica de Memória, ou seja, a lista cresce com a execução do programa. Operações como inserção e remoção são mais simples. Isto é feito através de variáveis do tipo ponteiro, ou seja, 36 um elemento aponta (possui o endereço, posição de memória do próximo elemento) para o próximo. 9) Escreva um programa em C que permite incluir números inteiros em uma lista encadeada. Solução do problema proposto (9): // Lista Encadeada #include <stdio.h> #include <stdlib.h> // ------------------------------------ Definições #define SUCESSO #define FALTA_DE_MEMORIA #define LISTA_VAZIA 0 1 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct nodo { TDADOS dado; struct nodo *elo; } TNODO; typedef struct { TNODO *primeiro; } TLISTA; // ------------------------------------ Prototypes void Cria_Lista (TLISTA *l); int Inclui_Lista (TLISTA *l, TDADOS d); int Remove_Primeiro(TLISTA *l); int Conta_Elementos_Lista(TLISTA l); int Remove_Ultimo(TLISTA *l); void Imprime_Erro(int erro); 37 void destroi_Lista(TLISTA *l); // ------------------------------------ Programa Principal int main(void) { TLISTA l; TDADOS d; int n,erro; Cria_Lista(&l); do { printf("Valor: "); scanf("%d",&d); if (d != 0) { erro = Inclui_Lista(&l,d); if (erro) { Imprime_Erro(erro); break; } } } while (d != 0); n = Conta_Elementos_Lista(l); printf("Número de Elementos: %d\n",n); Destroi_Lista(&l); getchar(); } // ------------------------------------ Cria_Lista void Cria_Lista (TLISTA *l) { l->primeiro = NULL; } // ------------------------------------ Inclui_Lista int Inclui_Lista (TLISTA *l, TDADOS d) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { if (l->primeiro == NULL) { l->primeiro = p; 38 p->dado = d; p->elo = NULL; } else { p->dado = d; p->elo = l->primeiro; l->primeiro = p; } return(SUCESSO); } } // ------------------------------------ Remove_Primeiro int Remove_Primeiro(TLISTA *l) { TNODO *p; if (l->primeiro == NULL) return(LISTA_VAZIA); else { p = l->primeiro; l->primeiro = p->elo; free(p); return(SUCESSO); } } // ------------------------------------ Conta_elementos int Conta_Elementos_Lista(TLISTA l) { TNODO *p; int n = 0; if (l.primeiro == NULL) return(n); else { p = l.primeiro; while (p != NULL) { n++; p = p->elo; } return(n); } } 39 // ------------------------------------ Remove_Ultimo int Remove_Ultimo(TLISTA *l) { TNODO *p,*q; if (l->primeiro == NULL) return(LISTA_VAZIA); else { q = l->primeiro; p = l->primeiro; while (p->elo != NULL) { q = p; p = p->elo; } if (l->primeiro == q) l->primeiro = NULL; else q->elo = NULL; free(p); return(SUCESSO); } } // ------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: Falta de Memória\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; } getchar(); } // ------------------------------------------ Destroi_Lista void Destroi_Lista(TLISTA *l) { TNODO *p; if (l->primeiro != NULL) { p = l->primeiro; while (p != NULL) { 40 l->primeiro = p->elo; free(p); p = l->primeiro; } } } 10) Escrever um programa em C que insere dados em uma lista encadeada, permitindo obter o conteúdo do último elemento, imprimindo também, toda a lista. Solução do problema proposto (10): // Lista Encadeada #include <stdio.h> #include <stdlib.h> // ------------------------------------ Definições #define SUCESSO #define FALTA_DE_MEMORIA #define LISTA_VAZIA 0 1 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct nodo { TDADOS dado; struct nodo *elo; } TNODO; typedef struct { TNODO *primeiro; } TLISTA; // ------------------------------------ Prototypes void Cria_Lista (TLISTA *l); int Inclui_Lista (TLISTA *l, TDADOS dado); int Consulta_Ultimo(TLISTA l,TDADOS *dado); int Imprime_Lista(TLISTA l); void Imprime_Erro(int erro); // ------------------------------------ Programa Principal int main(void) { 41 TLISTA l; TDADOS valor; int erro; Cria_Lista(&l); do { printf("Valor: "); scanf("%d",&valor); if (valor != 0) { erro = Inclui_Lista(&l,valor); if (erro) { Imprime_Erro(erro); break; } } } while (valor != 0); erro = Consulta_Ultimo(l,&valor); if (erro == SUCESSO) { printf("Último Elemento: %d\n",valor); Imprime_Lista(l); } else Imprime_Erro(erro); getchar(); } // ------------------------------------ Cria_Lista void Cria_Lista (TLISTA *l) { l->primeiro = NULL; } // ------------------------------------ Inclui_Lista int Inclui_Lista (TLISTA *l, TDADOS d) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { if (l->primeiro == NULL) { l->primeiro = p; p->dado = d; 42 p->elo = NULL; } else { p->dado = d; p->elo = l->primeiro; l->primeiro = p; } return(SUCESSO); } } // ------------------------------------ Consulta_Ultimo int Consulta_Ultimo(TLISTA l,TDADOS *dado) { TNODO *p; if (l.primeiro == NULL) return(LISTA_VAZIA); else { p = l.ultimo; *dado = p->dado; return(SUCESSO); } } // ------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: Falta de Memória\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; } } // ------------------------------------ Imprime_Lista int Imprime_Lista(TLISTA l) { TNODO *p; if (l.primeiro == NULL) return(LISTA_VAZIA); else { 43 printf("Lista Encadeada: "); p = l.primeiro; while (p != NULL) { printf("%02d ",p->dado); p = p->elo; } return(SUCESSO); } } 11) Escrever um programa em C que permite incluir, excluir e consultar (no início ou fim) dados inteiros em uma lista encadeada. Em cada operação imprimir a lista. Solução do problema proposto (11): // Lista Encadeada #include #include #include #include <stdio.h> <string.h> <ctype.h> <stdlib.h> // ---------------------------------------------- Definições #define SUCESSO 0 #define FALTA_DE_MEMORIA 1 #define LISTA_VAZIA 2 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct nodo { TDADOS dado; struct nodo *elo; } TNODO; typedef struct { TNODO *primeiro; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *l); int Inclui_Lista(TLISTA *l, TDADOS valor); int Exclui_Lista(TLISTA *l); int Consulta_Lista(TLISTA l, TDADOS *valor); 44 void void void void Imprime_Erro(int erro); Destroi_Lista(TLISTA *l); Exibe_Primeiro(TLISTA l); Exibe_Lista(TLISTA l); // ------------------------------- Programa Principal int main(void) { TLISTA l; TDADOS valor; int erro; char tecla; Cria_Lista(&l); do { Exibe_Primeiro(l); Exibe_Lista(l); printf("[I]ncluir, [E]xcluir, [C]onsultar ou [F]inalizar: "); do { tecla = toupper(getchar()); } while (!strchr("IECF",tecla)); switch (tecla) { case 'I': printf("Valor: "); scanf("%d",&valor); erro = Inclui_Lista(&l,valor); break; case 'E': erro = Exclui_Lista(&l); if (!erro) { printf("Ok, Elemento Excluído"); printf(", tecle <enter> para continuar"); } break; case 'C': erro = Consulta_Lista(l,&valor); if (!erro) printf("Valor Consultado: %d",valor); break; } if (erro && tecla != 'F') Imprime_Erro(erro); } while (tecla != 'F'); Destroi_Lista(&l); } // ------------------------------- Cria_Lista void Cria_Lista(TLISTA *l) { l->primeiro = NULL; 45 } // ------------------------------- Inclui_Lista int Inclui_Lista(TLISTA *l, TDADOS dado) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->dado = dado; if (l->primeiro == NULL) { l->primeiro = p; p->elo = NULL; } else { p->elo = l->primeiro; l->primeiro = p; } return(SUCESSO); } } // ------------------------------- Exclui_Lista int Exclui_Lista(TLISTA *l) { TNODO *p; if (l->primeiro == NULL) return(LISTA_VAZIA); else { p = l->primeiro; l->primeiro = p->elo; free(p); return(SUCESSO); } } // ------------------------------- Consulta_Lista int Consulta_Lista(TLISTA l, TDADOS *dado) { TNODO *p; 46 if (l.primeiro == NULL) return(LISTA_VAZIA); else { p = l.primeiro; *dado = p->dado; return(SUCESSO); } } // ------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: FALTA DE MEMÓRIA"); break; case LISTA_VAZIA: printf("ERRO: LISTA VAZIA"); break; } printf(", tecle <enter> para continuar"); } // ----------------------------------------- Destroi_Lista void Destroi_Lista(TLISTA *l) { TNODO *p,*q; if (l->primeiro != NULL) { p = l->primeiro; while (p != NULL) { q = p->elo; free(p); p = q; } } Cria_Lista(l); } // ----------------------------------------- Exibe_Primeiro void Exibe_Primeiro(TLISTA l) { if (l.primeiro != NULL) printf("Primeiro: | %p |",l.primeiro); else printf("Primeiro: | NULL |"); 47 } // ----------------------------------------- Exibe_Lista void Exibe_Lista(TLISTA l) { TNODO *p; printf("Lista Encadeada: "); if (l.primeiro == NULL) printf("LISTA VAZIA"); else { p = l.primeiro; while (p != NULL) { printf("| %02d ",p->dado); p = p->elo; } } } Vantagens das Listas representadas por Encadeamento • • Lista cresce indeterminadamente, enquanto houver memória livre (Alocação Dinâmica de Memória); As operações de insersão e remoção de elementos não exige a movimentação dos demais elementos. Desvantagens das Listas representadas por Encadeamento • • • Determinar o número de elementos da lista, pois para tanto, deve-se percorrer toda a lista; Acessar diretamente um determinado elemento pela sua posição, pois só é conhecido o primeiro elemento da lista; Acessar o último elemento da lista, pois para acessá-lo, deve-se “visitar” todos os intermediários. 3.2.3 Lista Encadeada com Descritor Como foi visto anteriormente, as dificuldades da lista encadeada, é descobrir o número de elementos e ainda, acessar o último elemento. Estas dificuldades podem ser resolvidas utilizando-se um descritor, da seguinte forma: 48 12) Escrever o mesmo programa em C que insere dados em uma lista encadeada com descritor, permitindo obter o conteúdo do último elemento diretamente, imprimindo também, toda a lista. Solução do problema proposto (12): // Lista Encadeada com Descritor #include <stdio.h> #include <stdlib.h> // ------------------------------------ Definições #define SUCESSO #define FALTA_DE_MEMORIA #define LISTA_VAZIA 0 1 2 // ------------------------------------ Tipos de Dados typedef int TDADOS; typedef struct nodo { TDADOS dado; struct nodo *elo; } TNODO; typedef struct { TNODO *primeiro; int n; TNODO *ultimo; } TDESCRITOR; 49 // ------------------------------------ Prototypes void Cria_Lista (TDESCRITOR *d); int Inclui_Esquerda (TDESCRITOR *d, TDADOS dado); int Inclui_Direita (TDESCRITOR *d, TDADOS dado); int Consulta_Ultimo(TDESCRITOR d,TDADOS *dado); void Imprime_Lista(TDESCRITOR d); void Imprime_Erro(int erro); // ------------------------------------ Programa Principal int main(void) { TDESCRITOR d; TDADOS dado; char tecla; int erro; Cria_Lista(&d); do { Imprime_Lista(d); printf("\nValor: "); scanf("%d",&dado); if (dado != 0) { printf("[E]squerda ou [D]ireita: "); do { tecla = toupper(getchar()); } while (!strchr("ED",tecla)); switch (tecla) { case 'E': erro = Inclui_Esquerda(&d,dado); break; case 'D': erro = Inclui_Direita(&d,dado); break; } if (erro) { Imprime_Erro(erro); break; } } } while (dado != 0); erro = Consulta_Ultimo(d,&dado); if (erro == SUCESSO) printf("Último Elemento: %d\n",dado); else Imprime_Erro(erro); getchar(); } 50 // ------------------------------------ Cria_Lista void Cria_Lista (TDESCRITOR *l) { l->primeiro = NULL; l->n = 0; l->ultimo = NULL; } // ------------------------------------ Inclui_Esquerda int Inclui_Esquerda (TDESCRITOR *d, TDADOS dado) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->dado = dado; p->elo = d->primeiro; d->primeiro = p; if (d->n == 0) d->ultimo = p; (d->n)++; return(SUCESSO); } } // ------------------------------------ Inclui_Direita int Inclui_Direita (TDESCRITOR *d, TDADOS dado) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->dado = dado; p->elo = NULL; if (d->n == 0) d->primeiro = p; else d->ultimo->elo = p; d->ultimo = p; (d->n)++; return(SUCESSO); } 51 } // ------------------------------------ Consulta_Ultimo int Consulta_Ultimo(TDESCRITOR d,TDADOS *dado) { TNODO *p; if (d.primeiro == NULL) return(LISTA_VAZIA); else { p = d.primeiro; while (p->elo != NULL) p = p->elo; *dado = p->dado; return(SUCESSO); } } // ------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: Falta de Memória\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; } } // ------------------------------------ Imprime_Lista void Imprime_Lista(TDESCRITOR l) { TNODO *p; printf("\nLista Encadeada: "); if (l.primeiro == NULL) printf("VAZIA"); else { p = l.primeiro; while (p != NULL) { printf("%02d ",p->dado); p = p->elo; } } 52 } 13) Escrever um programa em C que permite incluir, excluir e consultar (no início ou fim) dados inteiros em uma lista encadeada. Em cada operação imprimir a lista. Solução do problema proposto (13): // Lista Encadeada com Descritor #include #include #include #include <stdio.h> <string.h> <ctype.h> <stdlib.h> // ----------------------------------------- Definições #define SUCESSO 0 #define FALTA_DE_MEMORIA 1 #define LISTA_VAZIA 2 // ----------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct nodo { TDADOS dado; struct nodo *elo; } TNODO; typedef struct { TNODO *primeiro; int n; TNODO *ultimo; } TLISTA; // ---------------------------------------------- Prototypes void Cria_Lista(TLISTA *l); int Inclui_Inicio(TLISTA *l, TDADOS valor); int Inclui_Fim(TLISTA *l, TDADOS valor); int Exclui_Inicio(TLISTA *l); int Exclui_Fim(TLISTA *l); int Consulta_Inicio(TLISTA l, TDADOS *valor); int Consulta_Fim(TLISTA l, TDADOS *valor); void Imprime_Erro(int erro); void Destroi_Lista(TLISTA *l); void Exibe_Descritor(TLISTA l); void Exibe_Lista(TLISTA l); 53 // ------------------------------- Programa Principal int main(void) { TLISTA l; TDADOS valor; int erro; char tecla1,tecla2; Cria_Lista(&l); do { Exibe_Descritor(l); Exibe_Lista(l); printf("[I]ncluir, [E]xcluir, [C]onsultar ou [F]inalizar: "); do { tecla1 = toupper(getchar()); } while (!strchr("IECF",tecla1)); if (tecla1 != 'F') { printf("[I]nicio ou [F]im: "); do { tecla2 = toupper(getchar()); } while (!strchr("IF",tecla2)); switch (tecla1) { case 'I': printf("Valor: "); scanf("%d",&valor); if (tecla2 == 'I') erro = Inclui_Inicio(&l,valor); else erro = Inclui_Fim(&l,valor); break; case 'E': if (tecla2 == 'I') erro = Exclui_Inicio(&l); else erro = Exclui_Fim(&l); if (!erro) printf("Ok, Elemento Excluído"); break; case 'C': if (tecla2 == 'I') erro = Consulta_Inicio(l,&valor); else erro = Consulta_Fim(l,&valor); if (!erro) printf("Valor Consultado: %d",valor); break; } if (erro) Imprime_Erro(erro); } 54 } while (tecla1 != 'F'); Destroi_Lista(&l); } // ------------------------------- Cria_Lista void Cria_Lista(TLISTA *l) { l->primeiro = NULL; l->n = 0; l->ultimo = NULL; } // ------------------------------- Inclui_Inicio int Inclui_Inicio(TLISTA *l, TDADOS dado) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->dado = dado; if (l->n == 0) { l->primeiro = p; l->n = 1; l->ultimo = p; p->elo = NULL; } else { p->elo = l->primeiro; l->primeiro = p; l->n = l->n + 1; } return(SUCESSO); } } // ------------------------------- Inclui_Fim int Inclui_Fim (TLISTA *l, TDADOS dado) { TNODO *p,*q; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); 55 else { p->dado = dado; p->elo = NULL; if (l->n == 0) { l->primeiro = p; l->n = 1; l->ultimo = p; } else { q = l->ultimo; l->ultimo = p; q->elo = p; (l->n)++; } return(SUCESSO); } } // ------------------------------- Exclui_Inicio int Exclui_Inicio(TLISTA *l) { TNODO *p; if (l->n == 0) return(LISTA_VAZIA); else { p = l->primeiro; l->primeiro = p->elo; l->n = l->n - 1; if (l->n == 0) { l->primeiro = NULL; l->ultimo = NULL; } free(p); return(SUCESSO); } } // ------------------------------- Exclui_Fim int Exclui_Fim(TLISTA *l) { TNODO *p,*q; 56 if (l->n == 0) return(LISTA_VAZIA); else { p = l->primeiro; while (p->elo != NULL) { q = p; p = p->elo; } l->ultimo = q; q->elo = NULL; l->n = l->n - 1; if (l->n == 0) { l->primeiro = NULL; l->ultimo = NULL; } free(p); return(SUCESSO); } } // ------------------------------- Consulta_Inicio int Consulta_Inicio(TLISTA l, TDADOS *dado) { TNODO *p; if (l.n == 0) return(LISTA_VAZIA); else { p = l.primeiro; *dado = p->dado; return(SUCESSO); } } // ------------------------------- Consulta_Fim int Consulta_Fim(TLISTA l, TDADOS *dado) { TNODO *p; if (l.n == 0) return(LISTA_VAZIA); else { p = l.ultimo; *dado = p->dado; 57 return(SUCESSO); } } // ------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: FALTA DE MEMÓRIA"); break; case LISTA_VAZIA: printf("ERRO: LISTA VAZIA"); break; } } // ----------------------------------------- Destroi_Lista void Destroi_Lista(TLISTA *l) { TNODO *p,*q; if (l->n > 0) { p = l->primeiro; while (p != NULL) { q = p->elo; free(p); p = q; } } Cria_Lista(l); } // ----------------------------------------- Exibe_Descritor void Exibe_Descritor(TLISTA l) { if (l.primeiro != NULL && l.ultimo != NULL) printf("Descritor: | %p | %d | %p |",l.primeiro,l.n,l.ultimo); else printf("Descritor: | NULL | 0 | NULL |"); } // ----------------------------------------- Exibe_Lista void Exibe_Lista(TLISTA l) { TNODO *p; 58 printf("Lista Encadeada: "); if (l.n == 0) printf("LISTA VAZIA"); else { p = l.primeiro; while (p != NULL) { printf("| %02d ",p->dado); p = p->elo; } } } 3.2.4 Lista Duplamente Encadeada Na lista duplamente encadeada, cada elemento possui um elo para o anterior e o posterior, sendo que a lista pode ter ou não descritor. 14) Escrever um programa em C que insere dados em uma lista duplamente encadeada com descritor. Solução do problema proposto (14): // Lista Duplamente Encadeada com Descritor #include #include #include #include <stdio.h> <string.h> <ctype.h> <stdlib.h> // --------------------------------------- Definições #define #define #define #define SUCESSO FALTA_DE_MEMORIA LISTA_VAZIA POSICAO_INVALIDA 0 1 2 3 // --------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct nodo { struct nodo *anterior; TDADOS dado; struct nodo *posterior; } TNODO; 59 typedef struct { TNODO *primeiro; int n; TNODO *ultimo; } TDESCRITOR; // --------------------------------------------- Prototypes void Inicializa_Descritor(TDESCRITOR *d); int Insere_Direita(TDESCRITOR *d, TDADOS dado); int Insere_Esquerda(TDESCRITOR *d, TDADOS dado); void Imprime_Lista_Esquerda(TDESCRITOR d); void Imprime_Lista_Direita(TDESCRITOR d); void Imprime_Erro(int erro); void Exibe_Descritor(TDESCRITOR d); int Insere_Posicao(TDESCRITOR *d, int pos, TDADOS dado); // ------------------------------- Programa Principal int main(void) { TDESCRITOR d; TDADOS valor; int erro,pos; char tecla; Inicializa_Descritor(&d); do { Exibe_Descritor(d); Imprime_Lista_Esquerda(d); Imprime_Lista_Direita(d); printf("\nValor: "); scanf("%d",&valor); if (valor != 0) { printf("Inserir na [E]squerda, [D]ireita ou [P]osição: "); do { tecla = toupper(getchar()); } while (!strchr("EDP",tecla)); switch (tecla) { case 'E': erro = Insere_Esquerda(&d,valor); break; case 'D': erro = Insere_Direita(&d,valor); break; case 'P': printf("\nPosição: "); scanf("%d",&pos); erro = Insere_Posicao(&d,pos,valor); break; } 60 if (erro) Imprime_Erro(erro); } } while (valor != 0); } // ------------------------------- Inicializa_Descritor void Inicializa_Descritor(TDESCRITOR *d) { d->primeiro = NULL; d->n = 0; d->ultimo = NULL; } // ------------------------------- Insere_Direita int Insere_Direita (TDESCRITOR *d, TDADOS dado) { TNODO *p,*q; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->dado = dado; p->posterior = NULL; if (d->n == 0) { d->primeiro = p; d->n = 1; d->ultimo = p; p->anterior = NULL; } else { q = d->ultimo; d->ultimo = p; q->posterior = p; p->anterior = q; (d->n)++; } return(SUCESSO); } } // ------------------------------- Insere_Esquerda int Insere_Esquerda(TDESCRITOR *d, TDADOS dado) { 61 TNODO *p,*q; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->dado = dado; p->anterior = NULL; if (d->n == 0) { d->primeiro = p; d->n = 1; d->ultimo = p; p->posterior = NULL; } else { q = d->primeiro; d->primeiro = p; q->anterior = p; p->posterior = q; (d->n)++; } return(SUCESSO); } } // ------------------------------- Imprime_Lista_Direita void Imprime_Lista_Direita(TDESCRITOR d) { TNODO *p; printf("\nLista pela Direita: "); p = d.ultimo; while (p != NULL) { printf("%02d ",p->dado); p = p->anterior; } } // ------------------------------- Imprime_Lista_Esquerda void Imprime_Lista_Esquerda(TDESCRITOR d) { TNODO *p; printf("\nLista pela Esquerda: "); 62 p = d.primeiro; while (p != NULL) { printf("%02d ",p->dado); p = p->posterior; } } // ------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: Falta de Memória\n"); break; case LISTA_VAZIA: printf("ERRO: Lista Vazia\n"); break; case POSICAO_INVALIDA: printf("ERRO: Posição Inválida\n"); break; } } // ------------------------------- Exibe_Descritor void Exibe_Descritor(TDESCRITOR d) { printf("\nDescritor: %p | %d | %p |",d.primeiro,d.n,d.ultimo); } // ------------------------------- Insere_Posicao int Insere_Posicao(TDESCRITOR *d, int pos, TDADOS dado) { TNODO *p,*q; int i; if (pos < 1 || pos > d->n+1) return(POSICAO_INVALIDA); else if (pos == 1) return(Insere_Esquerda(d,dado)); else if (pos == d->n+1) return(Insere_Direita(d,dado)); else { p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else 63 { p->dado = dado; q = d->primeiro; i = 1; while (i < pos-1) { q = q->posterior; i++; } p->anterior = q; p->posterior = q->posterior; q->posterior = p; p->posterior->anterior = p; (d->n)++; return(SUCESSO); } } } 64 a) Preencha os campos de elo com os valores adequados para que seja representada a seqüência (A, B, C, D): (sem descritor) Preencha os campos de elo com os valores adequados para que seja representada a seqüência (A, B, C, D, E, F): (com descritor) 65 66 Vantagens e Desvantagens das Listas Duplamente Encadeadas Vantagens • • Inserção e remoção de componentes sem movimentar os demais; Pode-se ter qualquer quantidade de elementos, limitado pela memória livre, pois cada elemento é alocado dinamicamente. Desvantagens • • • Gerência de memória mais onerosa, alocação / desalocação para cada elemento; Procedimentos mais complexos; Processamento serial (Acesso Seqüencial). 3.2.5 Listas Lineares com Disciplina de Acesso São tipos especiais de Listas Lineares, onde inserção, consulta e exclusão são feitas somente nos extremos. Estas listas lineares com disciplina de acesso são: Filas, Pilhas e Deques. 3.2.5.1 Filas É uma Lista Linear na qual as inserções são feitas no fim e as exclusões e consultas no início da fila. Critério de Utilização FIFO - "First In First Out" (primeiro a entrar é o primeiro a sair) Operações sobre Filas Cria_Fila(f); Destroi_Fila(f); erro = Insere_ Fila(f, i); erro = Exclui_ Fila (f); erro = Consulta_ Fila (f, j); Cria FILA Vazia Desfaz a FILA Insere o dado "i" no fim da FILA Retira da FILA o primeiro elemento Copia em "j" o primeiro elemento da FILA 67 Erros nas operações sobre Filas: FILA CHEIA ou FILA VAZIA 15) Escrever um programa em C que insere, exclui e consulta dados (números inteiros) em uma fila. Solução do problema proposto (15): 3.2.5.1.1 Fila com Vetor // Fila em Vetor #include <stdio.h> #include <string.h> #include <ctype.h> // -------------------------------- Definições #define m 9 #define SUCESSO 0 #define FILA_CHEIA 1 #define FILA_VAZIA 2 // -------------------------------- Tipos de Dados 68 typedef int TDADOS; typedef struct { int primeiro; int ultimo; TDADOS elem[m]; } FILA; // -------------------------------- Prototypes void Cria_Fila(FILA *f); int Insere_Fila(FILA *f, TDADOS dado); int Exclui_Fila(FILA *f); int Consulta_Fila(FILA f, TDADOS *dado); void Imprime_Erro(int erro); // ----------------------------------- Programa Principal int main(void) { FILA f; TDADOS valor; int erro; char ch; Cria_Fila(&f); do { printf("[I]ncluir, [E]xcluir, [C]onsultar ou [F]im?"); do { ch = toupper(getchar()); } while (!strchr("IECF",ch)); switch (ch) { case 'I': printf("\nValor: "); scanf("%d",&valor); erro = Insere_Fila(&f,valor); break; case 'E': erro = Exclui_Fila(&f); break; case 'C': erro = Consulta_Fila(f,&valor); if (erro == SUCESSO) printf("\nPrimeiro da Fila: %d\n",valor); break; } if (erro && ch != 'F') Imprime_Erro(erro); } while (ch != 'F'); } // ------------------------------------ Cria_Fila 69 void Cria_Fila(FILA *f) { f->primeiro = 0; f->ultimo = -1; } // ------------------------------------ Insere_Fila int Insere_Fila(FILA *f, TDADOS dado) { if (f->ultimo == m-1) return(FILA_CHEIA); else { (f->ultimo)++; f->elem[f->ultimo] = dado; return(SUCESSO); } } // -------------------------------- Exclui_Fila int Exclui_Fila(FILA *f) { if (f->ultimo == -1) return(FILA_VAZIA); else { printf("\nExcluido\n"); (f->primeiro)++; if (f->primeiro > f->ultimo) { f->primeiro = 0; f->ultimo = -1; } return(SUCESSO); } } // ------------------------------- Consulta_Fila int Consulta_Fila(FILA f, TDADOS *dado) { if (f.ultimo == -1) return(FILA_VAZIA); else { *dado = f.elem[f.primeiro]; return(SUCESSO); } } 70 // ------------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FILA_CHEIA: printf("\nERRO: Lista Cheia\n"); break; case FILA_VAZIA: printf("\nERRO: Lista Vazia\n"); break; } } Exemplo: FILA VAZIA Inclusão de: 3, 5, 4, 7, 8 e 6 Exclusão dos três primeiros elementos: 3, 5 e 4 Problema com as Filas A reutilização da fila depois que alguns elementos foram extraídos. Solução deste Problema 71 Para reutilizar “as vagas” dos elementos já extraídos, deve-se usar uma Fila Circular. 3.2.5.1.2 Fila Circular 16) Escrever um programa em C que insere, exclui e consulta dados (números inteiros) em uma fila circular. Solução do problema proposto (16): // Fila Circular #include <stdio.h> #include <string.h> #include <ctype.h> // -------------------------------- Definições #define m 9 #define SUCESSO 0 #define FILA_CHEIA 1 #define FILA_VAZIA 2 // -------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int primeiro; int ultimo; int tamanho; TDADOS elem[m]; } FILA; // -------------------------------- Prototypes 72 void Cria_Fila_Circular(FILA *f); int Insere_Fila_Circular(FILA *f, TDADOS dado); int Exclui_Fila_Circular(FILA *f); int Consulta_Fila_Circular(FILA f, TDADOS *dado); void Imprime_Erro(int erro); // ----------------------------------- Programa Principal int main(void) { FILA f; TDADOS valor; int erro; char ch; Cria_Fila_Circular(&f); do { printf("[I]ncluir, [E]xcluir, [C]onsultar ou [F]im?"); do { ch = toupper(getchar()); } while (!strchr("IECF",ch)); switch (ch) { case 'I': printf("\nValor: "); scanf("%d",&valor); erro = Insere_Fila_Circular(&f,valor); break; case 'E': erro = Exclui_Fila_Circular(&f); break; case 'C': erro = Consulta_Fila_Circular(f,&valor); if (erro == SUCESSO) printf("\nPrimeiro da Fila: %d\n",valor); break; } if (erro && ch != 'F') Imprime_Erro(erro); } while (ch != 'F'); } // ------------------------------------ Cria_Fila_Circular void Cria_Fila_Circular(FILA *f) { f->primeiro = 0; f->ultimo = -1; f->tamanho = 0; } // ------------------------------------ Insere_Fila_Circular 73 int Insere_Fila_Circular(FILA *f, TDADOS dado) { if (f->tamanho == m) return(FILA_CHEIA); else { (f->tamanho)++; f->ultimo = (f->ultimo + 1) % m; f->elem[f->ultimo] = dado; return(SUCESSO); } } // -------------------------------- Exclui_Fila_Circular int Exclui_Fila_Circular(FILA *f) { if (f->tamanho == 0) return(FILA_VAZIA); else { printf("\nExcluido\n"); (f->tamanho)--; f->primeiro = (f->primeiro + 1) % m; return(SUCESSO); } } // ------------------------------- Consulta_Fila_Circular int Consulta_Fila_Circular(FILA f, TDADOS *dado) { if (f.tamanho == 0) return(FILA_VAZIA); else { *dado = f.elem[f.primeiro]; return(SUCESSO); } } // ------------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FILA_CHEIA: printf("\nERRO: Lista Cheia\n"); break; case FILA_VAZIA: printf("\nERRO: Lista Vazia\n"); break; 74 } } Como calcular a nova posição: 3.2.5.1.3 Fila com Alocação Dinâmica 17) Escrever um programa em C que insere, exclui e consulta dados (números inteiros) em uma fila alocada dinamicamente. Solução do problema proposto (17): // Fila Dinamica #include #include #include #include <stdio.h> <string.h> <ctype.h> <stdlib.h> // -------------------------------- Definições #define SUCESSO 0 #define FILA_VAZIA 1 #define FALTA_DE_MEMORIA 2 // -------------------------------- Tipos de Dados 75 typedef int TDADOS; typedef struct nodo { TDADOS info; struct nodo *seg; } TNODO; typedef struct { TNODO *primeiro; int tamanho; TNODO *ultimo; } TFILA; // ------------------------------------ Prototypes void Cria_Fila(TFILA *f); int Inserir_Fila(TFILA *f, TDADOS dado); int Excluir_Fila (TFILA *f); int Consultar_Fila (TFILA f, TDADOS *dado); void Destroi_Fila(TFILA *f); void Imprime_Erro(int erro); // ------------------------------------ Programa Principal int main(void) { TFILA f; TDADOS valor; int erro; char tecla; Cria_Fila(&f); do { printf("[I]ncluir, [E]xcluir, [C]onsultar ou [F]im?"); do { tecla = toupper(getchar()); } while (!strchr("IECF",tecla)); switch (tecla) { case 'I': printf("\nValor: "); scanf("%d",&valor); erro = Inserir_Fila(&f,valor); break; case 'E': erro = Excluir_Fila(&f); if (erro) Imprime_Erro(erro); else printf("\nElemento Excluido\n"); break; case 'C': erro = Consultar_Fila(f,&valor); if (erro) 76 Imprime_Erro(erro); else printf("\nValor: %d\n",valor); break; } } while (tecla != 'F'); Destroi_Fila(&f); } // ---------------------------------------------- Cria_Fila void Cria_Fila(TFILA *f) { f->primeiro = NULL; f->tamanho = 0; f->ultimo = NULL; } // ---------------------------------------------- Inserir_Fila int Inserir_Fila(TFILA *f, TDADOS dado) { TNODO *t; t = (TNODO *) malloc(sizeof(TNODO)); if (t == NULL) return(FALTA_DE_MEMORIA); else { t->info = dado; t->seg = NULL; f->tamanho = f->tamanho + 1; if (f->ultimo != NULL) f->ultimo->seg = t; f->ultimo = t; if (f->primeiro == NULL) f->primeiro = t; return(SUCESSO); } } // ---------------------------------------------- Excluir_Fila int Excluir_Fila(TFILA *f) { TNODO *t; if (f->primeiro == NULL) return(FILA_VAZIA); else { 77 t = f->primeiro; f->tamanho = f->tamanho - 1; f->primeiro = t->seg; free(t); if (f->primeiro == NULL) { f->ultimo = NULL; return(FILA_VAZIA); } } return(SUCESSO); } // ---------------------------------------------- Consultar_Fila int Consultar_Fila (TFILA f, TDADOS *dado) { if (f.primeiro == NULL) return(FILA_VAZIA); else *dado = f.primeiro->info; return(SUCESSO); } // ---------------------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FILA_VAZIA: printf("\nERRO: Fila Vazia\n"); break; case FALTA_DE_MEMORIA: printf("\nERRO: Falta de Memória\n"); break; } } // ---------------------------------------------- Destroi_Fila void Destroi_Fila(TFILA *f) { while (f->tamanho != 0) Excluir_Fila(f); } 78 3.2.5.2 Pilhas É uma Lista Linear na qual as inserções, exclusões e consultas são feitas em um mesmo extremo (TOPO). Critério de Utilização LIFO - "Last In First Out" (último a entrar é o primeiro a sair) Operações sobre Pilhas Cria_Pilha(p); Destroi_Pilha(p); erro = Push(p, i); erro = Pop(p, i); erro = Consulta_Pilha(p,j); Cria pilha Vazia Desfaz a pilha Empilha o dado "i" no fim da PILHA Desempilha o primeiro elemento Copia em "j" o primeiro elemento Identificadores da Pilha B(p) T(p) L(p) Base da PILHA Topo da PILHA Limite da PILHA 3.2.5.2.1 Pilha com Vetor 18) Escreva um programa em C que insere (Push), exclui (Pop) e consulta dados (números inteiros) em uma Pilha alocada estaticamente. 79 Solução do problema proposto (18): // Pilha com Vetor #include <stdio.h> #include <string.h> #include <ctype.h> // -------------------------------- Definições #define m 7 #define SUCESSO 0 #define PILHA_CHEIA 1 #define PILHA_VAZIA 2 // -------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int topo; TDADOS elem[m]; } TPILHA; // -------------------------------- Prototypes void Cria_Pilha(TPILHA *p); int Push(TPILHA *p, TDADOS dado); int Pop(TPILHA *p, TDADOS *dado); int Consulta_Pilha(TPILHA p, TDADOS *dado); void Imprime_Erro(int erro); // -------------------------------- Programa Principal int main(void) { TPILHA p; char tecla; TDADOS valor; int erro; Cria_Pilha(&p); do { printf("[P]ush, p[O]p, [C]onsultar ou [F]im? "); do { tecla = toupper(getchar()); } while (!strchr("POCF",tecla)); switch (tecla) { case 'P': printf("\nValor: "); 80 scanf("%d",&valor); erro = Push(&p,valor); if (erro) Imprime_Erro(erro); break; case 'O': erro = Pop(&p,&valor); if (erro) Imprime_Erro(erro); else printf("\nValor: %d\n",valor); break; case 'C': erro = Consulta_Pilha(p,&valor); if (erro) Imprime_Erro(erro); else printf("\nValor: %d\n",valor); break; } } while (tecla != 'F'); } // ---------------------------------------------- Cria_Pilha void Cria_Pilha(TPILHA *p) { p->topo = -1; } // ---------------------------------------------- Push int Push(TPILHA *p, TDADOS dado) { if (p->topo == m-1) return(PILHA_CHEIA); else { p->topo = p->topo + 1; p->elem[p->topo] = dado; return(SUCESSO); } } // ---------------------------------------------- Pop int Pop(TPILHA *p, TDADOS *dado) { if (p->topo == -1) return(PILHA_VAZIA); else { *dado = p->elem[p->topo]; 81 p->topo = p->topo - 1; return(SUCESSO); } } // ---------------------------------------------- Consulta_Pilha int Consulta_Pilha(TPILHA p, TDADOS *dado) { if (p.topo == -1) return(PILHA_VAZIA); else { *dado = p.elem[p.topo]; return(SUCESSO); } } // ---------------------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case PILHA_CHEIA: printf("\nERRO: Pilha Cheia\n"); break; case PILHA_VAZIA: printf("\nERRO: Pilha Vazia\n"); break; } } 19) Escreva um programa em C que simula uma Torre de Hanoi. Solução do problema proposto (19): // Torre de Hanoi #include #include #include #include <stdio.h> <conio.h> // Turbo C++ 1.01 ou Turbo C 2.01 <string.h> <ctype.h> // ---------------------------------------------- Definições #define m 3 #define #define #define #define SUCESSO 0 PILHA_CHEIA 1 PILHA_VAZIA 2 MOVIMENTO_INVALIDO 3 82 // ---------------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int topo; TDADOS elem[m]; } TPILHA; // ---------------------------------------------- Prototypes void Cria_Pilha(TPILHA *p); int Push(TPILHA *p, TDADOS dado); int Pop(TPILHA *p, TDADOS *dado); int Consulta_Pilha(TPILHA p, TDADOS *dado); void Lista_Pilhas(TPILHA p1, TPILHA p2, TPILHA p3); void Lista_Pilha(TPILHA p1, int col); void Imprime_Erro(int erro); // ---------------------------------------------- Programa Principal int main(void) { TPILHA p1,p2,p3; TDADOS valor,v2; int erro,pilha; char tecla1,tecla2; clrscr(); Cria_Pilha(&p1); Cria_Pilha(&p2); Cria_Pilha(&p3); Push(&p1,30); Push(&p1,20); Push(&p1,10); Lista_Pilhas(p1,p2,p3); do { gotoxy(1,6); printf("\n De: p[1], p[2], p[3] ou [F]im? "); do { tecla1 = toupper(getchar()); } while (!strchr("123F",tecla1)); if (tecla1 != 'F') { gotoxy(1,7); printf("\nPara: p[1], p[2], p[3] ou [F]im? "); do { tecla2 = toupper(getchar()); } while (!strchr("123F",tecla2)); switch (tecla1) 83 { case '1': erro = Pop(&p1,&valor); pilha=1; break; case '2': erro = Pop(&p2,&valor); pilha=2; break; case '3': erro = Pop(&p3,&valor); pilha=3; break; } switch (tecla2) { case '1': if (!erro) { erro = Consulta_Pilha(p1,&v2); if (!erro && valor > v2) Imprime_Erro(MOVIMENTO_INVALIDO); if (erro || valor < v2) Push(&p1,valor); else switch (pilha) { case 1: Push(&p1,valor); break; case 2: Push(&p2,valor); break; case 3: Push(&p3,valor); break; } } break; case '2': if (!erro) { erro = Consulta_Pilha(p2,&v2); if (!erro && valor > v2) Imprime_Erro(MOVIMENTO_INVALIDO); if (erro || valor < v2) Push(&p2,valor); else switch (pilha) { case 1: Push(&p1,valor); break; case 2: Push(&p2,valor); break; case 3: Push(&p3,valor); break; } } break; 84 case '3': if (!erro) { erro = Consulta_Pilha(p3,&v2); if (!erro && valor > v2) Imprime_Erro(MOVIMENTO_INVALIDO); if (erro || valor < v2) Push(&p3,valor); else switch (pilha) { case 1: Push(&p1,valor); break; case 2: Push(&p2,valor); break; case 3: Push(&p3,valor); break; } } break; } Lista_Pilhas(p1,p2,p3); if (p2.topo == 2 || p3.topo == 2) { gotoxy(1,10); textcolor(LIGHTRED); printf("Game Over, Congratulations ..."); getchar(); break; } } } while (tecla1 != 'F' && tecla2 != 'F'); } // ---------------------------------------------- Cria_Pilha void Cria_Pilha(TPILHA *p) { p->topo = -1; } // ---------------------------------------------- Push int Push(TPILHA *p, TDADOS dado) { if (p->topo == m-1) return(PILHA_CHEIA); else { p->topo = p->topo + 1; p->elem[p->topo] = dado; return(SUCESSO); 85 } } // ---------------------------------------------- Pop int Pop(TPILHA *p, TDADOS *dado) { if (p->topo == -1) return(PILHA_VAZIA); else { *dado = p->elem[p->topo]; p->topo = p->topo - 1; return(SUCESSO); } } // ---------------------------------------------- Consulta_Pilha int Consulta_Pilha(TPILHA p, TDADOS *dado) { if (p.topo == -1) return(PILHA_VAZIA); else { *dado = p.elem[p.topo]; return(SUCESSO); } } // --------------------------------- Lista_Pilhas void Lista_Pilhas(TPILHA p1, TPILHA p2, TPILHA p3) { gotoxy(1,1); printf(" -- -- --"); gotoxy(1,2); printf(" -- -- --"); gotoxy(1,3); printf(" -- -- --"); gotoxy(1,4); printf("-------------"); gotoxy(1,5); printf(" p1 p2 p3"); Lista_Pilha(p1,2); Lista_Pilha(p2,6); Lista_Pilha(p3,10); } // --------------------------------- Lista_Pilha 86 void Lista_Pilha(TPILHA p, int col) { int i,l=3; if (p.topo != -1) for (i = 0;i <= p.topo;i++) { gotoxy(col,l); printf("%02d",p.elem[i]); l--; } } // --------------------------------- Imprime_Erro void Imprime_Erro(int erro) { gotoxy(1,10); switch (erro) { case PILHA_CHEIA: printf("Erro: PILHA CHEIA"); break; case PILHA_VAZIA: printf("Erro: PILHA VAZIA"); break; case MOVIMENTO_INVALIDO: printf("Erro: MOVIMENTO INVµLIDO"); break; } getchar(); gotoxy(1,10); delline(); } 3.2.5.2.2 Pilha com Alocação Dinâmica 20) Escreva um programa em C que inclui, exclui e consulta dados em uma Pilha Encadeada. 87 Solução do problema proposto (20): // --------------------------------------- Pilha Encadeada #include #include #include #include <stdio.h> <string.h> <ctype.h> <stdlib.h> // ------------------------------------- Defini‡äes #define SUCESSO 0 #define FALTA_DE_MEMORIA #define PILHA_VAZIA 2 1 // -------------------------------------- Tipos de Dados typedef int TDADOS; typedef struct nodo { TDADOS info; struct nodo *seg; } TNODO; typedef struct { TNODO *topo; } TPILHA; // -------------------------------------------- Prototypes void Cria_Pilha(TPILHA *p); int Push(TPILHA *p, TDADOS dado); int Pop(TPILHA *p, TDADOS *dado); int Consulta_Pilha(TPILHA p, TDADOS *dado); void Imprime_Erro(int erro); void Exibe_Pilha(TPILHA p); // -------------------------------------------- Programa Principal int main(void) { TPILHA p; TDADOS valor; char tecla; int erro; Cria_Pilha(&p); do { Exibe_Pilha(p); 88 printf("[P]ush, p[O]p, [C]onsultar ou [F]im?"); do { tecla = toupper(getchar()); } while (!strchr("POCF",tecla)); switch (tecla) { case 'P': printf("Valor a ser Empilhado: "); scanf("%d",&valor); erro = Push(&p,valor); if (erro) Imprime_Erro(erro); break; case 'O': erro = Pop(&p,&valor); if (erro) Imprime_Erro(erro); else printf("Valor Desempilhado: %d, tecle algo",valor); break; case 'C': erro = Consulta_Pilha(p,&valor); if (erro) Imprime_Erro(erro); else printf("Topo: %d, tecle algo",valor); break; } } while (tecla != 'F'); } // ---------------------------------------------- Cria_Pilha void Cria_Pilha(TPILHA *p) { p->topo = NULL; } // ---------------------------------------------- Push int Push(TPILHA *p, TDADOS dado) { TNODO *t; t = (TNODO *) malloc(sizeof(TNODO)); if (t == NULL) return(FALTA_DE_MEMORIA); else { t->info = dado; t->seg = p->topo; p->topo = t; return(SUCESSO); } 89 } // ---------------------------------------------- Pop int Pop(TPILHA *p, TDADOS *dado) { TNODO *t; if (p->topo == NULL) return(PILHA_VAZIA); else { t = p->topo; *dado = t->info; p->topo = t->seg; free(t); return(SUCESSO); } } // ---------------------------------------------- Consulta_Pilha int Consulta_Pilha(TPILHA p, TDADOS *dado) { TNODO *t; if (p.topo == NULL) return(PILHA_VAZIA); else { t = p.topo; *dado = t->info; return(SUCESSO); } } // ---------------------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case FALTA_DE_MEMORIA: printf("ERRO: Falta de Memória"); break; case PILHA_VAZIA: printf("ERRO: Pilha Vazia"); break; } } // ------------------------------------------------- Exibe_Pilha 90 void Exibe_Pilha(TPILHA p) { TNODO *t; printf("Pilha: "); if (p.topo == NULL) printf("VAZIA"); else { t = p.topo; while (t != NULL) { printf("%02d ",t->info); t = t->seg; } } } 3.2.5.2.2 Analisador de Expressões usando Pilhas 21) Criar um Analisador de Expressões utilizando Pilhas. Exemplo: (3 * (4 + 5)) Dicas: 1. 2. 3. 4. 5. 6. Crie duas PILHAS (números e operandos) "(" não faça nada Número (Push pilha 2) empilhe na pilha de números Operando (Push pilha 1) empilhe na pilha de operandos ")" (Pop pilha 2, Pop pilha 2 e Pop pilha 1) execute a operação Até que i = e[0] Valores Válidos: Números: 0 1 2 3 4 5 6 7 8 9 (Números Inteiros Positivos) Operandos: + - * / 91 Solução do Trabalho Proposto (21): // -------------------------------- Analisador de Expressões #include <stdio.h> #include <string.h> #include <ctype.h> // -------------------------------- Definições #define m 50 #define SUCESSO 0 #define PILHA_CHEIA 1 #define PILHA_VAZIA 2 #define TRUE !0 #define FALSE 0 // -------------------------------- Tipos de Dados typedef int TDADOS; typedef struct { int topo; TDADOS elem[m]; } TPILHA; // -------------------------------- Prototypes 92 void Cria_Pilha(TPILHA *p); int Push(TPILHA *p, TDADOS dado); int Pop(TPILHA *p, TDADOS *dado); int Consulta_Pilha(TPILHA p, TDADOS *dado); void Imprime_Erro(int erro); int Codifica(char ch, TDADOS *valor,TDADOS *op); int Testa_Expressao(char *s); int Calcula(int v1, int v2, char op); // -------------------------------- Programa Principal int main(void) { TPILHA p1,p2; char s[256]; int n,i,tipo,erro; TDADOS valor,v1,v2,operador,resposta; printf("Analisador de Expressões\n"); printf("Expressão: "); gets(s); n = strlen(s); if (n > m) printf("ERRO: Expressão muito Longa"); else if (Testa_Expressao(s)) { Cria_Pilha(&p1); Cria_Pilha(&p2); for (i = 0;i < n;i++) { tipo = Codifica(s[i],&valor,&operador); switch (tipo) { case 1: erro = Push(&p1,valor); break; case 2: erro = Push(&p2,operador); break; case 3: erro = Pop(&p1,&v2); erro = Pop(&p1,&v1); erro = Pop(&p2,&operador); resposta = Calcula(v1,v2,operador); erro = Push(&p1,resposta); break; } } erro = Pop(&p1,&resposta); if (erro) Imprime_Erro(erro); else printf("Resposta: %d",resposta); 93 } else { printf("Erro: Expressão Inválida"); } getchar(); } // ---------------------------------------------- Codifica int Codifica(char ch, TDADOS *valor, TDADOS *op) { int codifica = 4; if (ch >= '0' && ch <= '9') { codifica = 1; *valor = ch - 48; } if (strchr("+- ",ch)) { codifica = 2; *op = ch; } if (ch == ')') codifica = 3; return(codifica); } // -------------------------------------------- Testa_Expressao int Testa_Expressao(char *s) { int i,n,abre=0,fecha=0; n = strlen(s); for (i = 0;i < n;i++) if (s[i] == '(') abre++; else if (s[i] == ')') fecha++; if (abre == fecha) return(TRUE); else return(FALSE); } // ------------------------------------------ Calcula int Calcula(int v1, int v2, char op) 94 { switch (op) { case '+': return(v1 + v2); case '-': return(v1 - v2); case '*': return(v1 * v2); case '/': return(v1 / v2); } return(0); } // ---------------------------------------------- Cria_Pilha void Cria_Pilha(TPILHA *p) { p->topo = -1; } // ---------------------------------------------- Push int Push(TPILHA *p, TDADOS dado) { if (p->topo == m-1) return(PILHA_CHEIA); else { p->topo = p->topo + 1; p->elem[p->topo] = dado; return(SUCESSO); } } // ---------------------------------------------- Pop int Pop(TPILHA *p, TDADOS *dado) { if (p->topo == -1) return(PILHA_VAZIA); else { *dado = p->elem[p->topo]; p->topo = p->topo - 1; return(SUCESSO); } } // ---------------------------------------------- Consulta_Pilha int Consulta_Pilha(TPILHA p, TDADOS *dado) { if (p.topo == -1) 95 return(PILHA_VAZIA); else { *dado = p.elem[p.topo]; return(SUCESSO); } } // ---------------------------------------------- Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case PILHA_CHEIA: printf("\nERRO: Pilha Cheia\n"); break; case PILHA_VAZIA: printf("\nERRO: Pilha Vazia\n"); break; } } 3.2.5.3 Deque (“Double-Ended QUEue”) É uma fila de duas extremidades. As inserções, consultas e retiradas são permitidas nas duas extremidades. Deque de Entrada Restrita A inserção só pode ser efetuada ou no início ou no final da lista. Deque de Saída Restrita A retirada só pode ser efetuada ou no início ou no final da lista. Condição Inicial: Esquerda (Esq) e Direita (Dir) no meio do vetor Esq = (m DIV 2 + 1 Dir = (m DIV 2) + 1 96 Deque Vazio Esq = -1 Dir = -1 Deque Cheio Esq = 0 Dir = m-1 Cálculo do Número de Elementos do Deque número_de_elementos = Dir - Esq + 1; 22) Escreva um programa em C que inclui, exclui e consulta dados em um Deque. Solução do problema proposto (22): // -------------------------------------------------- Deque #include <stdio.h> #include <string.h> #include <ctype.h> // -------------------------------------------------- Definições #define m 9 #define #define #define #define SUCESSO 0 DEQUE_ESQUERDO_CHEIO 1 DEQUE_DIREITO_CHEIO 2 DEQUE_VAZIO 3 // ------------------------------------------------- Tipos de Dados 97 typedef int TDADOS; typedef struct { int esq; int dir; TDADOS v[m]; } TDEQUE; // ------------------------------------ Prototypes void Cria_Deque(TDEQUE *d); int Inclui_Esquerda(TDEQUE *d, TDADOS dado); int Inclui_Direita(TDEQUE *d, TDADOS dado); int Exclui_Esquerda(TDEQUE *d, TDADOS *dado); int Exclui_Direita(TDEQUE *d, TDADOS *dado); void Exibe_Deque(TDEQUE d); void Imprime_Erro(int erro); // ------------------------------------ Programa Principal int main(void) { TDEQUE d; TDADOS valor; char ch,op; int erro; Cria_Deque(&d); do { Exibe_Deque(d); printf("[I]nclui, [E]xclui, [C]onsulta ou [F]im: "); do { op = toupper(getchar()); } while (!strchr("IECF",op)); if (strchr("'IEC",op)) { printf("[E]squerda ou [D]ireita: "); do { ch = toupper(getchar()); } while (!strchr("ED",ch)); switch (op) { case 'I': printf("Valor: "); scanf("%d",&valor); if (valor != 0) switch (ch) { case 'E': erro = Inclui_Esquerda(&d,valor); break; case 'D': erro = Inclui_Direita(&d,valor); break; 98 } break; case 'E': switch (ch) { case 'E': erro = Exclui_Esquerda(&d,&valor); break; case 'D': erro = Exclui_Direita(&d,&valor); break; } if (!erro) printf("Valor Excluído: %d",valor); break; case 'C': switch (ch) { case 'E': erro = Consulta_Esquerda(d,&valor); break; case 'D': erro = Consulta_Direita(d,&valor); break; } if (!erro) { printf("Valor Consultado: %d",valor); printf(", tecle <ENTER> para continuar"); getchar(); } break; } if (erro) Imprime_Erro(erro); } } while (op != 'F'); } // ------------------------------------ Cria_Deque void Cria_Deque(TDEQUE *d) { d->esq = -1; d->dir = -1; } // ------------------------------------ Inclui_Esquerda int Inclui_Esquerda(TDEQUE *d, TDADOS dado) { if (d->esq == 0) return(DEQUE_ESQUERDO_CHEIO); else { if (d->esq == -1) { 99 d->esq = m / 2; d->dir = d->esq; } else d->esq = d->esq - 1; d->v[d->esq] = dado; return(SUCESSO); } } // ------------------------------------ Inclui_Direita int Inclui_Direita(TDEQUE *d, TDADOS dado) { if (d->dir == m-1) return(DEQUE_DIREITO_CHEIO); else { if (d->dir == -1) { d->dir = m / 2; d->esq = d->dir; } else d->dir = d->dir + 1; d->v[d->dir] = dado; return(SUCESSO); } } // ------------------------------------ Exclui_Esquerda int Exclui_Esquerda(TDEQUE *d, TDADOS *dado) { if (d->esq == -1) return(DEQUE_VAZIO); else { *dado = d->v[d->esq]; d->esq = d->esq + 1; if (d->esq > d->dir) Cria_Deque(d); return(SUCESSO); } } // ------------------------------------ Exclui_Direita int Exclui_Direita(TDEQUE *d, TDADOS *dado) { if (d->dir == -1) 100 return(DEQUE_VAZIO); else { *dado = d->v[d->dir]; d->dir = d->dir - 1; if (d->dir < d->esq) Cria_Deque(d); return(SUCESSO); } } // ------------------------------------ Consulta_Esquerda int Consulta_Esquerda(TDEQUE d, TDADOS *dado) { if (d.esq == -1) return(DEQUE_VAZIO); else { *dado = d.v[d.esq]; return(SUCESSO); } } // ------------------------------------ Consulta_Direita int Consulta_Direita(TDEQUE d, TDADOS *dado) { if (d.dir == -1) return(DEQUE_VAZIO); else { *dado = d.v[d.dir]; return(SUCESSO); } } // ------------------------------------ Exibe_Deque void Exibe_Deque(TDEQUE d) { int i; printf("Deque: "); if (d.esq == -1) printf("VAZIO"); else for (i = d.esq;i <= d.dir;i++) printf("%02d ",d.v[i]); } 101 // ------------------------------------ Imprime_Erro void Imprime_Erro(int erro) { switch (erro) { case DEQUE_ESQUERDO_CHEIO: printf("ERRO: Deque Cheio à Esquerda"); break; case DEQUE_DIREITO_CHEIO: printf("ERRO: Deque Cheio à Direita"); break; case DEQUE_VAZIO: printf("ERRO: Deque Vazio"); break; } printf(", tecle <ENTER> para continuar"); getchar(); } 102 4. Arquivos Os arquivos permitem armazenar dados em disco, ou seja, na memória secundária, permitindo desta forma, que as informações não sejam perdidas quando o computador é desligado. Os arquivos são formados por registros. Cada registro possui uma chave de acesso, ou seja, um índice que diferencia um registro do outro e que permite localizá-lo. Os registros são compostos de campos. Os campos podem ser de qualquer tipo, permitindo que dados e estruturas de dados sejam armazenados. Tais dados podem ser: inteiros, reais, caracteres, strings, estruturas, etc. Os arquivos podem ser de dois tipos básicos: texto ou binário. Os arquivos tipo texto são formados por linhas de caracteres finalizados pelo caracter ‘\n’. Os arquivos binários permitem armazenamento de qualquer tipo de dado. Em C, existe um tipo de dado pré-definido chamado FILE (definido em stdio.h) que permite definir um ponteiro que aponta para um arquivo, ou seja, aponta para uma fila de bytes. Exemplo: #include <stdio.h> FILE *fp; // onde fp significa File Pointer Observação: Note que o nome do ponteiro do arquivo pode ter qualquer nome (identificador) válido em C, normalmente ele é chamado fp (File Pointer). 103 Em C existem dois tipos de sistemas de arquivos: bufferizado e não-bufferizado. 4.1 Sistema de Arquivo Bufferizado A ligação existente entre o sistema de entrada e saída bufferizado e um arquivo em C é um ponteiro que aponta para o arquivo. O ponteiro do arquivo identifica um determinado arquivo em disco e é utilizado pela fila associada a ele para direcionar cada uma das funções de entrada e saída bufferizada para o lugar em que elas operam. Um ponteiro de arquivo é uma variável ponteiro do tipo FILE *. O tipo de dado FILE é pré-definido em “stdio.h”. Exemplo: #include <stdio.h> int main(void) { FILE *fp; Função: fopen A função fopen (file open) permite abrir (criar ou anexar) um arquivo ligando-o a uma fila de bytes. Sintaxe: FILE *fopen (char *nome_arquivo, char *modo); Prototype: stdio.h nome_arquivo: Deve ser uma string que contenha: "drive:\path\nome_do_arquivo" modo: É uma string que contém as características desejadas (veja tabela abaixo). Observação: t (arquivo texto) e b (arquivo binário) Modo de abertura de arquivos Modo “r” “w” “a” “rb” “”wb” Significado Abre um arquivo-texto para leitura Cria um arquivo-texto para gravação Anexa a um arquivo-texto Abre um arquivo binário para leitura Cria um arquivo binário para gravação 104 “ab” “r+” “w+” “a+” “r+b” Modo “w+b” “a+b” “rt” “wt” “at” “r+t” “w+t” “a+t” Anexa a um arquivo binário Abre um arquivo-texto para leitura/gravação Cria um arquivo-texto para leitura/gravação Abre ou cria um arquivo-texto para leitura/gravação Abre um arquivo binário para leitura/gravação Significado Cria um arquivo binário para leitura/gravação Abre um arquivo binário para leitura/gravação Abre um arquivo texto para leitura Cria um arquivo texto para gravação Anexa a um arquivo texto Abre um arquivo-texto para leitura/gravação Cria um arquivo-texto para leitura/gravação Abre ou cria um arquivo-texto para leitura/gravação Observação: Se ocorrer erro na abertura de um arquivo, fopen devolverá um ponteiro nulo, ou seja, se (fp == NULL) erro. Exemplos: #include <stdio.h> int main (void) { FILE *fp; if ((fp = fopen("teste.dat","r")) == NULL) printf("Erro Fatal: Impossível Abrir o Arquivo\n"); else { printf("Ok, arquivo aberto\n"); ... ou #include <stdio.h> int main (void) { FILE *fp; fp = fopen("a:\fontes\teste.dat","w"); if (fp == NULL) printf("Erro Fatal: Impossível Criar o Arquivo\n”); else 105 { printf("Ok, arquivo criado com sucesso\n"); ... Função: putc A função putc é utilizada para “gravar” (write) caracteres em um arquivo. Sintaxe: int putc (int ch, FILE *fp); Prototype: stdio.h ch é o caracter a ser gravado fp é o ponteiro do arquivo aberto pela função fopen() Observação: Se uma gravação for bem sucedida putc() devolverá o caracter gravado, caso contrário devolverá um EOF. EOF - End of File (Fim de Arquivo) Função: getc A função getc é utilizada para “ler” (read) caracteres de um arquivo. Sintaxe: int getc (FILE *fp); Prototype: stdio.h fp é o ponteiro do arquivo aberto pela função fopen() Função: feof A função feof (file end of file) determina se o fim de arquivo foi encontrado. Devolve 0 se não chegou ao fim do arquivo. Sintaxe: int feof (FILE *fp); Prototype: stdio.h Função: fclose 106 A função fclose (file close) é utilizada para fechar um arquivo aberto. Antes de fechar o arquivo os dados (que ainda estavam no buffer) são gravados. Sintaxe: int fclose (FILE *fp); Prototype: stdio.h Observação: Retorna 0 se a operação foi bem sucedida. 4.2 Argumentos argc e argv Quando o programador que implementar um programa que recebe parâmetros pela linha de comandos é necessário definir dois argumentos (parâmetros) na função main. argc - Identifica o número de parâmetros presente na linha de comandos. argv - É um vetor de strings que possui todos os parâmetros da linha de comandos. Como definir os argumentos: int main(int argc, char *argv[]) Exemplo: (execução do programa “lista.exe” pela linha de comandos) C:\>lista lista.c <enter> 0 1 0 ‘C’ ‘l’ 1 ‘:’ ‘i’ 2 ‘\’ ‘s’ 3 ‘l’ ‘t’ 4 ‘i’ ‘a’ 5 ‘s’ ‘.’ 6 ‘t’ ‘c’ 7 ‘a’ 8 ‘.’ 9 ‘e’ 10 ‘x’ 11 ‘e’ 12 NULL NULL argv[0] “c:\lista.exe’ argv[1] “lista.c” 23) Escrever um programa em C que lista na tela um arquivo texto. Solução do problema proposto (23): Observação: Este programa deve ser compilado e então executado por linha de comando da seguinte forma: 107 C:\>lista lista.c <enter> // lista.c #include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; char ch; if (argc != 2) printf("Sintaxe: LISTA <Nome_Arquivo_Texto>\n"); else { fp = fopen(argv[1],"rt"); if (fp == NULL) printf("ERRO FATAL: Arquivo [%s] Inexistente\n",argv[1]); else { ch = getc(fp); while (!feof(fp)) { printf("%c",ch); ch = getc(fp); } fclose(fp); } } } 24) Escrever um programa em C que lista na tela um arquivo texto, imprimindo ainda o número de caracteres e o número de linhas. Solução do problema proposto (24): // bytes.c #include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; char ch; long unsigned int n = 0; unsigned int l = 1; if (argc != 2) printf("Sintaxe: LISTA <Nome_Arquivo_Texto>\n"); else 108 { fp = fopen(argv[1],"rt"); if (fp == NULL) printf("ERRO FATAL: Arquivo [%s] Inexistente\n",argv[1]); else { ch = getc(fp); while (!feof(fp)) { if (ch == '\n') l++; n++; printf("%c",ch); ch = getc(fp); } fclose(fp); printf("\nNúmero de Caracteres: %d",n); printf("\nNúmero de Linhas: %d",l); } } } 25) Escreva um programa em C que lê strings via teclado e grava-as (caracter por caracter) em um arquivo texto. Solução do problema proposto (25): // grava.c #include <stdio.h> #include <string.h> int main(void) { FILE *fp; char nome[256],linha[256]; char ch; int i,n; printf("Nome do Arquivo Texto: "); scanf("%s",nome); fp = fopen(nome,"rt"); if (fp != NULL) printf("ERRO FATAL: Arquivo [%s] Existe\n",nome); else { fp = fopen(nome,"wt"); if (fp == NULL) printf("ERRO FATAL: Problema na Criação do Arquivo [%s]\n",nome); else 109 { do { gets(linha); if (strcmp(linha,"") != 0) { n = strlen(linha); for (i = 0;i < n;i++) putc(linha[i],fp); putc('\n',fp); } } while (strcmp(linha,"") != 0); putc(EOF,fp); fclose(fp); } } } Escrever um programa em C que lê strings via teclado e grava-as (toda de uma vez) em um arquivo texto. 26) Solução do problema proposto (26): // grava.c #include <stdio.h> #include <string.h> int main(void) { FILE *fp; char nome[256],linha[256]; char ch; printf("Nome do Arquivo Texto: "); scanf("%s",nome); fp = fopen(nome,"rt"); if (fp != NULL) printf("ERRO FATAL: Arquivo [%s] Existe\n",nome); else { fp = fopen(nome,"wt"); if (fp == NULL) printf("ERRO FATAL: Problema na Criação do Arquivo [%s]\n",nome); else { do { gets(linha); if (strcmp(linha,"") != 0) { fprintf(fp,"%s",linha); 110 putc('\n',fp); } } while (strcmp(linha,"") != 0); putc(EOF,fp); fclose(fp); } } } Função: rewind A função rewind estabelece o localizador de posição do arquivo para o início do arquivo especificado, ou seja, faz com que o ponteiro do arquivo (fp) aponte para o byte zero. Sintaxe: void rewind (FILE *fp); Prototype: stdio.h Funções: getw e putw As funções getw e putw são utilizadas para ler e gravar, respectivamente, inteiros em um arquivo. Função: Leitura de inteiros (getw) Sintaxe: int getw (FILE *fp); Prototype: stdio.h Função: Gravação de inteiros (putw) Sintaxe: int putw (int x, FILE *fp); Prototype: stdio.h Funções: fgets e fputs As funções fgets e fputs são utilizadas para ler e gravar strings. Função: Leitura de strings (fgets) Sintaxe: char *fgets (char *str, int comprimento, FILE *fp); Prototype: stdio.h Função: Gravação de strings (fputs) Sintaxe: char *fputs (char *str, FILE *fp); Prototype: stdio.h 111 Observação: A função fgets lê uma string do arquivo especificado até que leia ou um '\n' ou (comprimento - 1) caracteres. Funções: fread e fwrite As funções fread e fwrite são utilizadas para ler e gravar blocos de dados, normalmente uma struct. Função: Leitura de blocos (fread) Sintaxe: int fread (void *buffer, int num_bytes, int cont, FILE *fp); Prototype: stdio.h Função: Gravação de blocos (fwrite) Sintaxe: int fwrite (void *buffer, int num_bytes, int cont, FILE *fp); Prototype: stdio.h buffer: É um ponteiro para a região da memória ou o endereço de uma variável que receberá os dados lidos do arquivo pela função fread ou que será gravada no arquivo pela função fwrite. num_bytes: Especifica a quantidade de bytes a serem lidos ou gravados. cont: Determina quantos blocos (cada um com comprimento de num_bytes) serão lidos ou gravados. fp: É o ponteiro para o arquivo. Função: fseek A função fseek é utilizada para ajustar o localizador de posição do arquivo, ou seja, permite selecionar a posição para efetuar operações de leitura e gravação aleatórias. Sintaxe: int fseek (FILE *fp, long int num_bytes, int origem); Prototype: stdio.h num_bytes: É o número de bytes desde origem até chegar a posição desejada. Origem em arquivos Origem Início do arquivo Posição corrente Identificador SEEK_SET SEEK_CUR 112 Fim do arquivo SEEK_END Funções: fprintf e fscanf As funções fprintf e fscanf se comportam exatamente como printf e scanf, exceto pelo fato de que elas operam com arquivos em disco. Função: Gravação de dados formatados (fprintf) Sintaxe: int fprintf (FILE *fp, char *formato, lista argumentos); Prototype: stdio.h Função: Leitura de dados formatados (fscanf) Sintaxe: int fscanf (FILE *fp, char *formato, lista argumentos); Prototype: stdio.h Função: remove A função remove apaga do disco o arquivo especificado. Sintaxe: int remove (char *nome_arquivo); Prototype: stdio.h Exemplos: Abaixo, são listados três programas: cria.c, lista.c, consulta.c, os quais possuem como registro, uma string com no máximo 80 caracteres. Escreva um programa em C que lê nomes via teclado (máximo 80 caracteres) e grava-os em um arquivo binário qualquer (o nome deve ser informado pelo usuário). O programa termina quando o usuário digitar apenas um enter. 27) Solução do problema proposto (27): // write.c #include <stdio.h> #include <string.h> int main(void) { FILE *fp; 113 char nome[80],arquivo[256]; int n = 0; printf("Nome do Arquivo: "); scanf("%s",arquivo); fp = fopen(arquivo,"rb"); if (fp != NULL) printf("ERRO FATAL: Arquivo [%s] Existe\n",arquivo); else { fp = fopen(arquivo,"wb"); if (fp == NULL) printf("ERRO FATAL: Problema na Criação do Arquivo [%s]\n",arquivo); else { do { printf("Nome: "); gets(nome); if (strcmp(nome,"") != 0) { fwrite(nome,sizeof(nome),1,fp); n++; } } while (strcmp(nome,"") != 0); printf("%d Nomes Gravados\n",n); fclose(fp); } } } Escreva um programa em C que permite listar (na tela) os nomes contidos em um arquivo binário qualquer (criado no programa acima). 28) Solução do problema proposto (28): // read.c #include <stdio.h> #include <string.h> int main(void) { FILE *fp; char nome[80],arquivo[256]; int n = 0; printf("Nome do Arquivo: "); scanf("%s",arquivo); fp = fopen(arquivo,"rb"); if (fp == NULL) 114 printf("ERRO FATAL: Arquivo [%s] Inexistente\n",arquivo); else { fread(nome,sizeof(nome),1,fp); while (!feof(fp)) { printf("Nome: %s\n",nome); fread(nome,sizeof(nome),1,fp); n++; } printf("%d Nomes Lidos\n",n); fclose(fp); getchar(); } } Escreva um programa em C que permite consultar o arquivo de nomes. Para tanto é solicitado, ao usuário, o número do registro para ser calculado a posição deste registro no arquivo. Logo após o registro é exibido na tela. 29) Solução do problema proposto (29): // consulta.c #include <stdio.h> #include <string.h> int main (void) { FILE *fp; char nome[80]; char arquivo[256]; unsigned int n, ok; long int posicao; char ch; printf("Nome do Arquivo: "); scanf(“%s”, arquivo); if ((fp = fopen(arquivo,"rb")) == NULL) { printf("ERRO: Arquivo não EXISTE\n"); getchar(); } else { do { printf("Número do Registro: "); scanf("%d", &n); 115 posicao = n * sizeof(nome); fseek(fp, posicao, SEEK_SET); ok = fread(nome, sizeof(nome), 1, fp); if (ok) printf("%d: Nome: %s\n", n, nome); else printf("ERRO: Registro NÃO existe\n"); printf("Continua [S/N] ? "); do { ch = getchar(); } while (!strchr("SsNn",ch)); } while (strchr("Ss",ch)); fclose(fp); } } Escreva um programa em C que permite ordenar o arquivo de nomes gerado pelo programa “write.c”. Para tanto deve ser criada uma Lista Encadeada. Utilizar um método de classificação qualquer. Após a classificação os nomes devem ser gravados no arquivo original de nomes. 30) Solução do problema proposto (30): // sort.c #include <stdio.h> #include <stdlib.h> // ------------------------------------ Definições #define SUCESSO 0 #define FALTA_DE_MEMORIA #define LISTA_VAZIA 1 2 #define TRUE !0 #define FALSE 0 typedef char TDADOS; typedef struct nodo { TDADOS dado[80]; struct nodo *elo; } TNODO; typedef struct { TNODO *primeiro; } TLISTA; 116 // ------------------------------------ Prototypes void Cria_Lista (TLISTA *l); int Inclui_Lista (TLISTA *l, TDADOS *d); int Exclui_Lista(TLISTA *l, TDADOS *nome); void Destroi_Lista(TLISTA *l); void Exibe_Lista(TLISTA l); void Ordena_Lista(TLISTA *l); // ------------------------------------ Programa Principal int main(void) { FILE *fp; char arquivo[256]; TDADOS nome[80]; TLISTA l; int erro; Cria_Lista(&l); printf("Nome do Arquivo: "); scanf("%s",arquivo); fp = fopen(arquivo,"r+b"); if (fp == NULL) printf("ERRO FATAL: Arquivo [%s] Inexistente\n",arquivo); else { fread(nome,sizeof(nome),1,fp); while (!feof(fp)) { erro = Inclui_Lista(&l,nome); if (erro) { printf("ERRO FATAL: Falta de Memória\n"); getchar(); exit(1); } fread(nome,sizeof(nome),1,fp); } printf("Lista de Entrada"); Exibe_Lista(l); Ordena_Lista(&l); printf("\nLista Ordenada"); Exibe_Lista(l); rewind(fp); erro = Exclui_Lista(&l,nome); while (!erro) { fwrite(nome,sizeof(nome),1,fp); erro = Exclui_Lista(&l,nome); } 117 fclose(fp); } Destroi_Lista(&l); getchar(); } // ------------------------------------ Cria_Lista void Cria_Lista (TLISTA *l) { l->primeiro = NULL; } // ------------------------------------ Inclui_Lista int Inclui_Lista (TLISTA *l, TDADOS *d) { TNODO *p; p = (TNODO *) malloc(sizeof(TNODO)); if (p == NULL) return(FALTA_DE_MEMORIA); else { if (l->primeiro == NULL) { l->primeiro = p; strcpy(p->dado,d); p->elo = NULL; } else { strcpy(p->dado,d); p->elo = l->primeiro; l->primeiro = p; } return(SUCESSO); } } // ------------------------------- Exclui_Lista int Exclui_Lista(TLISTA *l, TDADOS *nome) { TNODO *p; if (l->primeiro == NULL) return(LISTA_VAZIA); else { p = l->primeiro; 118 strcpy(nome,p->dado); l->primeiro = p->elo; free(p); return(SUCESSO); } } // ----------------------------------------- Destroi_Lista void Destroi_Lista(TLISTA *l) { TNODO *p,*q; if (l->primeiro != NULL) { p = l->primeiro; while (p != NULL) { q = p->elo; free(p); p = q; } } Cria_Lista(l); } // ------------------------------------------ Exibe_Lista void Exibe_Lista(TLISTA l) { TNODO *p; if (l.primeiro == NULL) printf("\nVAZIA"); else { p = l.primeiro; while (p != NULL) { printf("\n%s",p->dado); p = p->elo; } } } // ------------------------------------ Ordena_Lista void Ordena_Lista(TLISTA *l) { TNODO *p,*q; TDADOS temp[80]; 119 int trocou; if (l->primeiro != NULL) do { trocou = FALSE; p = l->primeiro; q = p->elo; while (q != NULL) { if (strcmp(p->dado,q->dado) > 0) { strcpy(temp,p->dado); strcpy(p->dado,q->dado); strcpy(q->dado,temp); trocou = TRUE; } p = q; q = p->elo; } } while (trocou); } 120 5. Pesquisa de Dados Uma operação complexa e trabalhosa é a consulta em tabelas. Normalmente uma aplicação envolve grande quantidade de dados que são armazenadas em Tabelas. As tabelas são compostas de registros (normalmente possui uma chave), e os registros de campos. 5.1 Pesquisa Seqüencial Método mais simples de pesquisa em tabela, consiste em uma varredura seqüencial, sendo que cada campo é comparado com o valor que está sendo procurado. Esta pesquisa termina quando for achado o valor desejado ou quando chegar o final da tabela. 23) Escreva um programa em C que faz uma Busca Seqüencial em uma estrutura que possui os campos: chave, nome, altura e peso. 121 Solução do problema proposto (23): // ---------------------------------------------- Pesquisa Sequencial #include <stdio.h> #include <string.h> #include <ctype.h> // ---------------------------------------------- Definições #define MAX 10 typedef struct { int chave; char nome[21]; float peso; float altura; } TABELA; // ---------------------------------------------- Prototypes int Pesquisa_Sequencial(TABELA t[], int valor,int n); // ---------------------------------------------- Programa Principal int main(void) { TABELA t[MAX]; int n = 0; int ch,chave; char tecla; do { printf("Chave: "); scanf("%d",&t[n].chave); printf("Nome: "); gets(t[n].nome); printf("Peso: "); scanf("%f",&t[n].peso); printf("Altura: "); scanf("%f",&t[n].altura); printf("Continua [S/N] ?"); do { tecla = tolower(getchar()); } while (!strchr("sn",tecla)); n++; } while (tecla == 's' && n < MAX); do { printf("Chave para consulta [0 - Sair]: "); scanf("%d",&ch); 122 chave = Pesquisa_Sequencial(t,ch,n); if (chave != -1) { printf("Chave: %d\n",t[chave].chave); printf("Nome: %s\n",t[chave].nome); printf("Peso: %.1f\n",t[chave].peso); printf("Altura: %.1f\n",t[chave].altura); } else printf("Erro: Chave Inexistente\n"); } while (ch != 0); } // ---------------------------------------------- Pesquisa_Sequencial int Pesquisa_Sequencial(TABELA t[], int ch,int n) { int i,chave = -1; for (i = 0;i < n;i++) if (t[i].chave == ch) { chave = i; break; } return(chave); } Observação: A Pesquisa Sequencial apresenta desempenho melhor se a tabela estiver ordenada pela chave de acesso: 24) Escreva um programa em C que faz uma Busca Seqüencial, em uma estrutura ordenada por chave, que possui os campos: chave, nome, altura e peso. Solução do problema proposto (24): // ---------------------------------------------- Pesquisa Ordenada #include <stdio.h> #include <string.h> #include <ctype.h> // ---------------------------------------------- Definições #define MAX 10 typedef struct { int chave; 123 char nome[21]; float peso; float altura; } TABELA; // ---------------------------------------------- Prototypes int Pesquisa_Ordenada(TABELA t[], int ch, int n); void Ordena_Tabela(TABELA t[], int n); // ---------------------------------------------- Programa Principal int main(void) { TABELA t[MAX]; int n = 0; int ch,chave; char tecla; do { printf("Chave: "); scanf("%d",&t[n].chave); printf("Nome: "); gets(t[n].nome); printf("Peso: "); scanf("%f",&t[n].peso); printf("Altura: "); scanf("%f",&t[n].altura); printf("Continua [S/N] ?"); do { tecla = tolower(getchar()); } while (!strchr("sn",tecla)); n++; } while (tecla == 's' && n < MAX); Ordena_Tabela(t,n); do { printf("Chave para consulta [0 - Sair]: "); scanf("%d",&ch); chave = Pesquisa_Ordenada(t,ch,n); if (chave != -1) { printf("Chave: %d\n",t[chave].chave); printf("Nome: %s\n",t[chave].nome); printf("Peso: %.1f\n",t[chave].peso); printf("Altura: %.1f\n",t[chave].altura); } else printf("Erro: Chave Inexistente\n"); } while (ch != 0); } 124 // ---------------------------------------------- Ordena_Tabela void Ordena_Tabela(TABELA t[], int n) { int i,j; TABELA temp; for (i = 0;i < n-1;i++) for (j = i+1;j < n;j++) if (t[i].chave > t[j].chave) { temp = t[i]; t[i] = t[j]; t[j] = temp; } } // ---------------------------------------------- Pesquisa_Ordenada int Pesquisa_Ordenada(TABELA t[], int ch, int n) { int i,chave = -1; for (i = 0; i < n;i++) if (t[i].chave >= ch) if (t[i].chave == ch) { chave = i; break; } return(chave); } 5.2 Pesquisa Binária Método de Pesquisa que só pode ser aplicada em tabelas ordenadas. 125 O método consiste na comparação do "valor" com a chave localizada na metade da tabela, pode ocorrer: valor = chave............chave localizada valor < chave............chave está na primeira metade (esquerda) valor > chave............chave está na segunda metade (direita) A cada comparação, a área de pesquisa é reduzida a metade do número de elementos. O número máximo de comparações será: 25) Escreva um programa em C que faz uma Pesquisa Binária em uma tabela de números inteiros. Solução do problema proposto (25): // --------------------------------------- Pesquisa Binária #include <stdio.h> #define MAX 10 // --------------------------------------- Prototypes void Ordena_Tabela(int t[], int n); int Pesquisa_Binaria(int t[], int n, int valor); void Exibe_Tabela(int t[], int n); // --------------------------------------- Programa Principal int main(void) { int t[MAX]; int indice,n = -1; int valor; do { n++; 126 printf("Número: "); scanf("%d",&t[n]); } while (t[n] != 0 && n < MAX); Exibe_Tabela(t,n); Ordena_Tabela(t,n); Exibe_Tabela(t,n); do { printf("Valor: "); scanf("%d",&valor); if (valor != 0) { indice = Pesquisa_Binaria(t,n,valor); if (indice == -1) printf("ERRO: Valor não está na tabela\n"); else printf("Indice: %d\n",indice); } } while (valor != 0); } // ---------------------------------------------- Ordena_Tabela void Ordena_Tabela(int t[], int n) { int i,j,temp; for (i = 0;i < n-1;i++) for (j = i+1;j < n;j++) if (t[i] > t[j]) { temp = t[i]; t[i] = t[j]; t[j] = temp; } } // ---------------------------------------------- Pesquisa_Binaria int Pesquisa_Binaria(int t[], int n, int valor) { int i,j,indice; int inic,fim,metade; inic = 0; fim = n-1; metade = n / 2; indice = -1; do { if (valor == t[metade]) { indice = metade; 127 break; } else if (valor < t[metade]) { fim = metade - 1; metade = (fim + inic) / 2; } else { inic = metade + 1; metade = (fim + inic) / 2; } } while (indice == -1 && inic <= fim); return(indice); } // ------------------------------------------------- Exibe_Tabela void Exibe_Tabela(int t[], int n) { int i; printf("Tabela: "); for (i = 0;i < n;i++) printf("%02d ",t[i]); printf("\n"); } 5.3 Cálculo de Endereço (Hashing) Além de um método de pesquisa, este método é também um método de organização física de tabelas (Classificação). Onde cada dado de entrada é armazenado em um endereço previamente calculado (através de uma função), desta forma, o processo de busca é igual ao processo de entrada, ou seja, eficiente. Um dos problemas é definir bem o tipo de função a ser usada, pois normalmente as funções geram endereços repetidos. A eficiência deste método depende do tipo de função. Numa tabela com n elementos com valores na faixa de [0..MAX] pode ser utilizada a seguinte a função: Onde: n é o número de elementos. 128 Exemplo: n = 53 entrada: [0..1000] Note que no cálculo dos endereços houve repetições, tais como: 2, 33, 23, 10 e 50, por causa disto, é necessário verificar se o endereço está ocupado ou não. Finalmente os endereços calculados são: 26) Escreva um programa em C que faz um Cálculo de Endereço (Hashing) em uma tabela de números inteiros. Solução do problema proposto (26): // --------------------- Hashing.c #include <stdio.h> #include <string.h> #define k 19 #define TRUE !0 #define FALSE 0 typedef struct { int situacao; int valor; } TABELA; // ---------------------------------------------- Inicializa_Tabela void Inicializa_Tabela(TABELA t[]) { int i; 129 for (i = 0;i < k;i++) { t[i].situacao = FALSE; t[i].valor = 0; } } // ---------------------------------------------- Insere_Tabela void Insere_Tabela(TABELA t[], int entrada) { int endereco; endereco = entrada % k; while (t[endereco].situacao) endereco++; t[endereco].valor = entrada; t[endereco].situacao = TRUE; } // ---------------------------------------------- Hashing int Hashing(TABELA t[], int entrada) { int i, endereco; endereco = entrada % k; while (t[endereco].valor != entrada && endereco != k) endereco++; if (endereco != k) return(endereco); else return(0); } // ---------------------------------------------- Exibe_Tabela void Exibe_Tabela(TABELA t[]) { int i; for (i = 0;i < k;i++) printf("%3d ",i); for (i = 0;i < k;i++) printf("%03d ",t[i].valor); } // ---------------------------------------------- PROGRAMA PRINCIPAL 130 int main(void) { TABELA t[k]; int n = 0, entrada, endereco; char tecla; Inicializa_Tabela(t); do { Exibe_Tabela(t); n++; printf("Número: "); scanf("%d",&entrada); Insere_Tabela(t,entrada); printf("Continua [S/N]? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && n <= k); do { printf("\nValor a ser CONSULTADO: "); scanf("%d",&entrada); endereco = Hashing(t,entrada); if (endereco == 0) printf("ERRO: Valor Inválido\n"); else printf("Endereco: %d\n",endereco); printf("Continua [S/N]? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla)); } 6. Classificação de Dados (Ordenação) É o processo pelo qual é determinada a ordem em que devem ser apresentados os elementos de uma tabela de modo a obedecer à seqüência de um ou mais campos (chaves de classificação). Classificação Interna...................... Memória Principal Classificação Externa...................... Memória Secundária 6.1 Classificação por Força Bruta 131 Logo, os elementos são fisicamente reorganizados. 27) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros. Solução do problema proposto (27): // Sort.c #include <stdio.h> #include <string.h> #define QUANT 10 void Sort(int v[], int n) { int i,j,temp; for (i = 0;i < n-1;i++) for (j = i+1;j < n;j++) if (v[i] > v[j]) { temp = v[i]; v[i] = v[j]; v[j] = temp; } } void Exibe(int v[], int n) { int i; printf("\nLista: "); for (i = 0;i <= n;i++) printf("%2d ",v[i]); 132 } int main(void) { int v[QUANT]; int n = -1; char tecla; do { n++; printf("\nValor: "); scanf("%d",&v[n]); printf("Continua [S/N] ?"); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); Exibe(v,n); } while (strchr("Ss",tecla) && n < QUANT); Sort(v,n); Exibe(v,n); printf("\nTecle qualquer tecla ..."); getchar(); } 6.2 Vetor Indireto de Ordenação (Tabela de Índices) Os elementos não são reorganizados fisicamente, apenas é criado um outro vetor (tabela de índices) que controla a ordem do primeiro. Exemplo: 28) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros utilizando um Vetor Indireto de Ordenação (VIO). Solução do problema proposto (28): 133 // Vio_1.c (Vetor Indireto de Ordenação) #include <stdio.h> #include <string.h> #define TRUE !0 #define FALSE 0 #define QUANT 10 // ---------------------------------------------- Verifica int Verifica(int vio[], int i, int k) { int j, ok = TRUE; for (j = 0;j <= k;j++) if (vio[j] == i) ok = FALSE; return(ok); } // ---------------------------------------------- PROGRAMA PRINCIPAL int main(void) { char v[QUANT][20]; int vio[QUANT]; int i,j,k,l,u = -1; char tecla; int troca,ok; do { u++; printf("\nNome: "); gets(v[u]); printf("Continua [S/N] ?"); vio[u] = FALSE; do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && u <= QUANT); k = 0; j = 1; do { troca = TRUE; for (i = 0;i <= u;i++) { ok = Verifica(vio,i,k); if (ok) 134 if (strcmp(v[i],v[j]) > 0) { j = i; troca = FALSE; } } if (!troca) { vio[k] = j; k++; j = 1; } if (troca) { ok = Verifica(vio,j,k); if (ok) { k++; vio[k] = j; } if (j < u) j++; else j--; } } while (k != u); printf("\nLista de Nomes Ordenados\n"); for (i = 0;i <= u;i++) printf("Nome: %s\n",v[vio[i]]); getchar(); } 29) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros utilizando um Vetor Indireto de Ordenação (VIO). Solução do problema proposto (29): // Vio.c (Vetor Indireto de Ordenação) #include <stdio.h> #include <string.h> #define QUANT 10 int main(void) { char v[QUANT][30]; int vio[QUANT]; 135 int i, j, u = -1, temp; char tecla; do { u++; printf("\nNome: "); gets(v[u]); printf("Continua [S/N] ?"); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && u < QUANT); for (i = 0;i <= u;i++) vio[i] = i; for (i = 0;i < u;i++) for (j = i+1;j <= u;j++) if (strcmp(v[vio[i]],v[vio[j]]) > 0) { temp = vio[i]; vio[i] = vio[j]; vio[j] = temp; } printf("\nLista de Nomes Ordenados\n"); for (i = 0; i <= u;i++) printf("Nome: %s\n",v[vio[i]]); getchar(); } 6.3 Classificação por Encadeamento Os elementos permanecem em seus lugares. É criado então uma lista encadeada ordenada. Esta lista possui um Header (Cabeça) o qual indica o primeiro elemento da lista. Exemplo: 136 30) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros utilizando encadeamento. Solução do problema proposto (30): // Encadea.c (Ordenação por Encadeamento) #include <stdio.h> #include <string.h> #define TRUE !0 #define FALSE 0 #define QUANT 10 typedef struct { char nome[20]; int prox; } TABELA; // ---------------------------------------------- Verifica void Verifica (TABELA t[], TABELA ta[], int *j) { int i = 0,sai; do { sai = FALSE; if (strcmp(t[i].nome,ta[*j].nome) == 0) { *j = i; sai = TRUE; } i++; } while (!sai); } // ---------------------------------------------- Copia void Copia (TABELA t[], TABELA ta[], int *m, int n) { int i; *m = -1; for (i = 0;i <= n;i++) if (t[i].prox == -1) { (*m)++; strcpy(ta[*m].nome,t[i].nome); 137 ta[*m].prox = -1; } } // ---------------------------------------------- PROGRAMA PRINCIPAL int main(void) { TABELA t[QUANT],ta[QUANT]; int i,j,k,m,u = -1; int anterior,primeiro,sai; char tecla; do { u++; printf("\nNome: "); gets(t[u].nome); t[u].prox = -1; printf("Continua [S/N]? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && u < QUANT); primeiro = 0; for (i = 1;i <= u;i++) if (strcmp(t[i].nome,t[primeiro].nome) < 0) primeiro = i; t[primeiro].prox = 0; anterior = primeiro; do { Copia(t,ta,&m,u); if (m != 0) { if (m >= 1) { i = 1; j = 0; do { if (strcmp(ta[i].nome,ta[j].nome) < 0) j = i; i++; } while (i <= m); } } else j = 0; Verifica(t,ta,&j); t[anterior].prox = j; t[j].prox = 0; anterior = j; } while (m != 0); 138 j = primeiro; printf("\nLista de Nomes Ordenados por Encadeamento\n"); for (i = 0;i <= u;i++) { printf("%s\n",t[j].nome); j = t[j].prox; } getchar(); } 6.4 Métodos de Classificação Interna Os métodos de Classificação Interna podem ser: • • • Por Inserção Por Troca Por Seleção Observação: Para os seguintes métodos considere que as entradas são feitas no vetor v, logo após é criado o vetor c (chaves) e o vetor e (endereços). A ordenação é feita no vetor c e o vetor e é o vetor indireto de ordenação, ou seja, será mantido o vetor de entrada intacto. 6.4.1 Método por Inserção Direta Neste método ocorre a inserção de cada elemento em outro vetor ordenado. 139 Utilização: Pequena quantidade de dados, pois é pouco eficiente. O vetor é dividido em dois segmentos. Inicialmente: c[0] e c[1], c[2], ... c[n] A classificação acontece por meio de interações, cada elemento do segundo segmento é inserido ordenadamente no primeiro até que o segundo segmento acabe. Por exemplo: 31) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros utilizando Método por Inserção Direta. Solução do problema proposto (31): 140 // MID.c (Método de Inserção Direta) #include <stdio.h> #define QUANT 10 int main(void) { int v[QUANT],c[QUANT],e[QUANT]; int i,j,k,u = -1; int chave,endereco; char tecla; do { u++; printf("\nNúmero: "); scanf("%d",&v[u]); printf("Continua [S/N] ? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && u < QUANT); for (i = 0;i <= u;i++) { c[i] = v[i]; e[i] = i; } for (i = 1;i <= u;i++) { k = 0; j = i - 1; chave = c[i]; endereco = e[i]; while (j >= 0 && k == 0) if (chave < c[j]) { c[j+1] = c[j]; e[j+1] = e[j]; j--; } else k = j + 1; c[k] = chave; e[k] = endereco; } printf("\n Valores: "); for (i = 0;i <= u;i++) printf("%2d ",c[i]); printf("\nEndereços: "); for (i = 0;i <= u;i++) 141 printf("%2d ",e[i]); getchar(); } 6.4.2 Método por Troca Neste método, compara-se pares de elementos, trocando-os de posição caso estejam desordenados. 6.4.2.1 Método da Bolha (Bubble Sort) Cada elemento do vetor é testado com o seguinte, se estiverem fora de ordem ocorre a troca, isto é repetido até não ocorrer mais trocas. Por exemplo: Observação: Na primeira passagem completa o último elemento esta ordenado, logo na segunda passagem não é necessário ir até o fim. 32) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros utilizando o Método da Bolha. Solução do problema proposto (32): // Bolha.c #include <stdio.h> #include <string.h> 142 #define QUANT 10 #define TRUE !0 #define FALSE 0 int main(void) { int v[QUANT], c[QUANT], e[QUANT]; int i, j, n = -1, m; int chave, endereco, troca; char tecla; do { n++; printf("\nNúmero: "); scanf("%d",&v[n]); printf("Continua [S/N] ? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && n < QUANT); for (i = 0;i <= n;i++) { c[i] = v[i]; e[i] = i; } m = n - 1; do { troca = TRUE; for (i = 0;i <= m;i++) if (c[i] > c[i+1]) { chave = c[i]; c[i] = c[i+1]; c[i+1] = chave; endereco = e[i]; e[i] = e[i+1]; e[i+1] = endereco; j = i; troca = FALSE; } m = j; } while (!troca); printf("\nLista Ordenada\n"); for (i = 0;i <= n;i++) printf("%d\n",c[i]); getchar(); } 6.4.3 Método por Seleção 143 Seleção sucessiva do menor valor da tabela. A cada passo o menor elemento é colocado em sua posição definitiva. 6.4.3.1 Método por Seleção Direta A cada passo do método é feita uma varredura do segmento que corresponde os elementos, ainda não selecionados, e determinado o menor elemento o qual é colocado na primeira posição do elemento por troca. Por exemplo: 33) Escreva um programa em C que ordena (classifica em ordem crescente) um vetor de números inteiros utilizando o Método por Seleção Direta. Solução do problema proposto (33): // Direta.c #include <stdio.h> #include <string.h> #define QUANT 10 #define TRUE !0 #define FALSE 0 int main(void) { int v[QUANT], c[QUANT], e[QUANT]; int i, j, n = -1, min; 144 int chave, endereco, troca; char tecla; do { n++; printf("\nNúmero: "); scanf("%d",&v[n]); printf("Continua [S/N] ? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla) && n < QUANT); for (i = 0;i <= n;i++) { c[i] = v[i]; e[i] = i; } for (i = 0;i <= n-1;i++) { min = i; for (j = i+1;j <= n;j++) if (c[j] < c[min]) min = j; chave = c[i]; c[i] = c[min]; c[min] = chave; endereco = e[i]; e[i] = e[min]; e[min] = endereco; } printf("\nLista Ordenada\n"); for (i = 0;i <= n;i++) printf("%d\n",c[i]); getchar(); } 145 7. Árvores São estruturas de dados (não-lineares) que caracterizam uma relação entre os dados, à relação existente entre os dados é uma relação de hierarquia ou de composição (um conjunto é subordinado a outro). 7.1 Conceitos Básicos Definição É um conjunto finito T de um ou mais nós, tais que: a) Existe um nó principal chamado raiz (root); b) Os demais nós formam n >= 0 conjuntos disjuntos T1, T2, ... Tn, onde cada um destes subconjuntos é uma árvore. As árvores Ti (i >= 1 e i <= n) recebem a denominação de sub-árvores. Terminologia Grau Indica o número de sub-árvores de um nó. 146 Observação: Se um nodo não possuir nenhuma sub-árvore é chamado de nó terminal ou folha. 147 Nível É o comprimento, ou seja, o número de linhas do caminho da raiz até o nó. Observação: Raiz é nível zero (0) Exemplo: Altura É o nível mais alto da árvore. Na árvore acima, a altura é igual a 2. Floresta É um conjunto de zero ou mais árvores disjuntas, ou seja, se for eliminado o nó raiz da árvore, as sub-árvores que restarem chamam-se de florestas. 148 Formas de Representação de Árvores α) Representação Hierárquica β) Representação por Conjunto (Diagrama de Inclusão ou Composição) χ) Representação por Expressão Parentetizada (Parênteses Aninhados) (A(B()C(D(G()H())E()F(I())))) δ) Representação por Expressão não Parentetizada AA 2 B 0 C 3 D 2 G 0 H 0 E 0 F 1 I 0 ε) Representação por Endentação (Digrama de Barras) 149 150 7.2 Árvores Binárias Uma árvore binária (T) é um conjunto finito de nós que pode ser vazio ou pode ser dividida em três sub-conjuntos disjuntos: raiz, subárvore esquerda (Te) e sub-árvore direita (Td). São estruturas onde o grau de cada nó é menor ou igual a dois, ou seja, no máximo grau 2. O número máximo de nós no nível i é 2i. São árvores onde cada nó tem no máximo dois filhos (grau máximo 2), desta forma, obtém-se uma estrutura apropriada para busca binária, pois sabe-se que existe, para cada nodo, duas subárvores (Te e Td). Para cada nó da árvore associa-se uma chave (dado, valor ou informação) que permite a realização de uma classificação. A Construção da árvore deve ser de forma que na sub-árvore à esquerda (Te) da raiz só existam nós com chaves menores que a chave da raiz. E a sub-árvore à direita (Td) só pode conter nós com valores maiores que a raiz. Com esta reestruturação da árvore, a busca de um determinado nó torna-se trivial. O acesso aos dados pode ser feito então através de funções recursivas. Propriedades 1) O número máximo de nodos no k-ésimo nível de uma árvore binária é 2k; 2) O número máximo de nodos em uma árvore binária com altura k é 2k+1-1, para k >= 0; 3) Árvore completa: árvore de altura k com 2k+1-1 nodos. 151 Exemplo de uma aplicação de Árvore Binária (Analisador de Expressão): Expressão: (3 + 6) * (4 – 1) + 5 Conversão de Árvore Genérica em Árvore Binária • • Ligar os nós irmãos; Remover a ligação entre o nó pai e seus filhos, exceto as do primeiro filho. 152 Nota: O nó da sub-árvore à esquerda é filho e o nó da sub-árvore à direita é irmão 153 7.3 Representações 7.3.1 Representação por Contigüidade Física (Adjacência) Os nodos são representados seqüencialmente na memória. Devem ser tomados os seguintes cuidados: • • Ser alocado espaço suficiente para armazenar a estrutura completa; Os nodos devem ser armazenados em uma lista, onde cada nodo i da árvore ocupa o i-ésimo nodo da lista. Exemplo: Sendo i a posição de um nodo e n o número máximo de nodos da árvore. Observações: 1) O pai de i está em i / 2 sendo i <= n. Se i = 1, i é a raiz e não possui pai. 2) O filho à esquerda de i está em 2i se 2i <= n. Se 2i > n, então tem filho à esquerda. 3) O filho à direita de i está em 2i+1. Se 2i+1 <= n. Se 2i+1 > n, então tem filho à direita. Observação: Representação por Contigüidade Física não é um modo conveniente para representar árvores, na maioria dos casos. Vantagens • • Adequado para árvores binárias completas. Útil para o armazenamento em disco ou fita (seqüencial). 154 Desvantagem • • A estrutura pode ter muitos espaços sem uso. Estrutura possui limite finito no número de nodos. 7.3.2 Representação por Encadeamento esq: endereço do nodo filho à esquerda info: contém a informação do nodo dir: endereço do nodo filho à direita typedef char TDados; typedef struct TNodo { struct TNodo *esq; TDados info; struct TNodo *dir; }; typedef struct { TNodo *raiz; } TArvore; TArvore *a; 155 7.4 Caminhamento em Árvores Consiste em processar de forma sistemática e ordenada cada nó da árvore apenas uma vez, obtendo assim uma seqüência linear de nós. Abaixo são mostrados os três tipos de caminhamentos: 7.4.1 Caminhamento Pré-Fixado (Pré-Ordem) 1) Visitar a raiz; 2) Caminhar na sub-árvore da esquerda; 3) Caminhar na sub-árvore a direita. Observação: “Visitar” significa qualquer operação em relação à informação (info) do nodo. Exemplo: Caminhamento: ABDECFG 7.4.2 Caminhamento In-Fixado (Central) 1) Caminhar na sub-árvore da esquerda; 2) Visitar a raiz; 3) Caminhar na sub-árvore da direita. Exemplo: Conforme exemplo acima, o caminhamento In-Fixado é: Caminhamento: DBEACGF 156 7.4.3 Caminhamento Pós-Fixado 1) Caminhar na subárvore da esquerda; 2) Caminhar na subárvore da direita; 3) Visitar a raiz. Exemplo: Conforme exemplo acima, o caminhamento Pós-Fixado é: Caminhamento: DEBGFCA 7.4.4 Algoritmos recursivos para percorrer Árvores Binárias Algoritmos de busca de dados em estruturas hierárquicas, caminhamentos em árvores, por exemplo, utilizam, normalmente, recursividade. Recursividade é uma técnica utilizada em programação quando se deseja que uma função faça uma chamada a si própria. Este mecanismo utiliza uma estrutura de pilha para fazer o controle do retorno de todas as chamadas realizadas. Como vantagens das funções recursivas tem-se: a) clareza na interpretação do código (funções pequenas); b) “simplicidade” e elegância na implementação. Como desvantagens tem-se: a) dificuldade para encontrar erros (debug); b) dificuldade de encontrar o critério de parada da função; c) em alguns casos podem ser ineficientes devido a quantidade de chamadas recursivas. 157 A chamada de uma função recursiva requer espaço para os parâmetros, variáveis locais e endereço de retorno. Todas estas informações são armazenadas em uma pilha e depois desalocadas, ou seja, a quantidade de informações é proporcional ao número de chamadas. Todas as operações envolvidas na recursividade contribuem para um gasto maior de tempo, pois a alocação e liberação de memória consomem tempo. 7.4.4.1 Caminhamento Pré-Fixado (Pré-Ordem) void Caminhamento_Pre_Ordem(TArvore *a) { if (!Vazia(a)) { printf("%c ", a->info); // mostra raiz Caminhamento_Pre_Ordem(a->esq); // mostra sub_esq Caminhamento_Pre_Ordem(a->dir); // mostra sub_dir } } 7.4.4.2 Caminhamento In-Fixado (Central) void Caminhamento_In_Fixado(TArvore *a) { if (!Vazia(a)) { Caminhamento_In_Fixado(a->esq); // mostra sub_esq printf("%c ", a->info); // mostra raiz Caminhamento_In_Fixado(a->dir); // mostra sub_dir } } 7.4.4.3 Caminhamento Pós-Fixado void Caminhamento_Pos_Fixado(TArvore *a) { if (!Vazia(a)) { Caminhamento_Pos_Fixado(a->esq); // mostra sub_esq Caminhamento_Pos_Fixado(a->dir); // mostra sub_dir printf("%c ", a->info); // mostra raiz } } 34) Escreva um programa em C que cria a árvore da página 137. O programa deve exibir na tela os três tipos de caminhamentos. Solução do problema proposto (34): 158 // tree.c // Compilador: Dev-C++ 4.9.9.2 #include <stdio.h> typedef char TDados; typedef struct Nodo { struct Nodo *esq; TDados info; struct Nodo *dir; } TNodo; typedef TNodo TArvore; // --------------------------------------------- Cria TArvore *Cria(TArvore *esq, TDados info, TArvore* dir) { TArvore *p; p = (TArvore*) malloc(sizeof(TArvore)); if (p == NULL) { printf("ERRO FATAL: Falta de Memória\n"); getchar(); exit(0); } else { p->info = info; p->esq = esq; p->dir = dir; } return p; } // --------------------------------------------- Vazia int Vazia(TArvore *a) { if (a == NULL) return(1); else return(0); } // --------------------------------------------- Caminhamento_Pre_Ordem 159 void Caminhamento_Pre_Ordem(TArvore *a) { if (!Vazia(a)) { printf("%c ", a->info); // mostra raiz Caminhamento_Pre_Ordem(a->esq); // mostra sub_esq Caminhamento_Pre_Ordem(a->dir); // mostra sub_dir } } // --------------------------------------------- Caminhamento_In_Fixado void Caminhamento_In_Fixado(TArvore *a) { if (!Vazia(a)) { Caminhamento_In_Fixado(a->esq); // mostra sub_esq printf("%c ", a->info); // mostra raiz Caminhamento_In_Fixado(a->dir); // mostra sub_dir } } // --------------------------------------------- Caminhamento_Pos_Fixado void Caminhamento_Pos_Fixado(TArvore *a) { if (!Vazia(a)) { Caminhamento_Pos_Fixado(a->esq); // mostra sub_esq Caminhamento_Pos_Fixado(a->dir); // mostra sub_dir printf("%c ", a->info); // mostra raiz } } // --------------------------------------------- Destroi TArvore *Destroi(TArvore *a) { if (!Vazia(a)) { Destroi(a->esq); // libera sub_esq Destroi(a->dir); // libera sub_dir free(a); // libera raiz } return(NULL); } // --------------------------------------------- Programa Principal int main(void) { 160 TArvore *a,*a1,*a2,*a3,*a4,*a5,*a6; system(“cls”); a1 = Cria(NULL,'d',NULL); a2 = Cria(NULL,'e',NULL); a3 = Cria(a1,'b',a2); a4 = Cria(NULL,'g',NULL); a5 = Cria(a4,'f',NULL); a6 = Cria(NULL,'c',a5); a = Cria(a3,'a',a6); printf("Caminhamentos na Árvore\n\n Pré-Ordem: "); Caminhamento_Pre_Ordem(a); printf("\n In-Fixado: "); Caminhamento_In_Fixado(a); printf("\nPós-Fixado: "); Caminhamento_Pos_Fixado(a); system(“pause”); return(0); } Resultado do Programa: Caminhamentos na Árvore Pré-Ordem: a b d e c f g In-Fixado: d b e a c g f Pós-Fixado: d e b g f c a 35) Escreva um programa em C que Insere (ordenado), Exclui e exibe na tela os três tipos de caminhamentos na árvore criada. Solução do problema proposto (35): // // // // // arvore.c Autor: Ricardo Andrade Cava Adaptação: Paulo Roberto Gomes Luzzardi Data: 16/09/2005 Compilador: Dev-C++ 4.9.9.2 #include <stdio.h> 161 #include <string.h> #include <stdlib.h> // ------------------------------------ defines #define SUCESSO #define FALTA_DE_MEMORIA #define INFO_NAO_EXISTE 0 1 2 // ------------------------------------ Definição de Tipos typedef int TDados; typedef struct Nodo { struct Nodo *ptesq; TDados info; struct Nodo *ptdir; } TNodo; typedef struct { TNodo *raiz; } TArvore; // ------------------------------------ Prototypes int Inclui(TArvore *a,TDados info); int Inclui_Recursivo(TNodo **ptnodo,TDados info); int Exclui(TArvore *a,int info); int Exclui_Recursivo(TNodo **ptnodo,int info); TNodo **Procura_Maior(TNodo **ptnodo); void Cria_Arvore(TArvore *a); void Pre_Ordem(TArvore a); void Pre_Ordem_Recursivo (TNodo *ptnodo); void Em_Ordem(TArvore a); void Em_Ordem_Recursivo (TNodo *ptnodo); void Pos_Ordem(TArvore a); void Pos_Ordem_Recursivo (TNodo *ptnodo); // ------------------------------------ Programa Principal int main(void) { TArvore a; TDados info; char tecla,op; system(“cls”); Cria_Arvore(&a); do { printf("[I]ncluir\n"); printf("[E]xcluir\n"); 162 printf("[C]aminha\n"); printf("[F]im\n"); printf("\nQual a sua Opção? "); do { tecla = getchar(); } while (!strchr("IiEeCcFf",tecla)); printf("%c\n",tecla); switch (tecla) { case 'I': case 'i': printf("\nInformação: "); scanf("%d",&info); Inclui (&a,info); break; case 'E': case 'e': printf("\nInformação: "); scanf("%d",&info); if (Exclui (&a,info)== INFO_NAO_EXISTE) printf("ERRO: Informação Inexistente\n"); break; case 'C': case 'c': printf("[1] Pré-fixado\n"); printf("[2] In-fixado\n"); printf("[3] Pós-fixado\n"); printf("\nQual o Caminhamento? "); do { op = getchar(); } while (!strchr("123",op)); printf("%c\n",tecla); switch (op) { case '1': printf("\nCaminhamento Pré-Ordem: "); Pre_Ordem(a); printf("\n\n"); break; case '2': printf("\nCaminhamento Em-Ordem: "); Em_Ordem(a); printf("\n\n"); break; case '3': printf("\nCaminhamento Pós-Ordem: "); Pos_Ordem(a); printf("\n\n"); break; } break; } } while (!strchr("Ff",tecla)); } // ------------------------------------ Cria_Arvore 163 void Cria_Arvore (TArvore *a) { a->raiz = NULL; } // ------------------------------------ Inclui int Inclui(TArvore *a, TDados info) { return(Inclui_Recursivo(&(a->raiz),info)); } // ------------------------------------ Inclui_Recursivo int Inclui_Recursivo (TNodo **ptnodo, TDados info) { TNodo *p; if (*ptnodo == NULL) { p = (TNodo *) malloc (sizeof(TNodo)); if (p == NULL) return(FALTA_DE_MEMORIA); else { p->ptesq = NULL; p->ptdir = NULL; p->info = info; *ptnodo = p; return SUCESSO; } } else if (info < (*ptnodo)->info) return(Inclui_Recursivo(&((*ptnodo)->ptesq),info)); else return(Inclui_Recursivo(&((*ptnodo)->ptdir),info)); } // ------------------------------------ Exclui int Exclui(TArvore *a, int info) { return(Exclui_Recursivo( &(a->raiz),info)); } // ------------------------------------ Exclui_Recursivo int Exclui_Recursivo (TNodo **ptnodo, TDados info) { TNodo *p,**aux; 164 if (*ptnodo == NULL) return(INFO_NAO_EXISTE); else if ( info < (*ptnodo)->info) return(Exclui_Recursivo (&((*ptnodo)->ptesq),info)); else if (info > (*ptnodo)->info) return(Exclui_Recursivo (&((*ptnodo)->ptdir),info)); else { if ((*ptnodo)->ptesq == NULL) if ((*ptnodo)->ptdir == NULL) { free ( *ptnodo); *ptnodo = NULL; } else { p = *ptnodo; *ptnodo = (*ptnodo)->ptdir; free(p); } else if ((*ptnodo)->ptdir == NULL) { p = *ptnodo; *ptnodo = (*ptnodo)->ptesq; free(p); } else { aux = Procura_Maior( &(*ptnodo)->ptesq); (*ptnodo)->info = (*aux)->info; return(Exclui_Recursivo(aux,(*aux)->info)); } return SUCESSO; } } // ------------------------------------ Procura_Maior TNodo **Procura_Maior(TNodo **ptnodo) { if ((*ptnodo)->ptdir == NULL) return(ptnodo); else return(Procura_Maior(&(*ptnodo)->ptdir)); } // ------------------------------------ Pre_Ordem 165 void Pre_Ordem(TArvore a) { Pre_Ordem_Recursivo(a.raiz); } // ------------------------------------ Pre_Ordem_Recursivo void Pre_Ordem_Recursivo (TNodo *ptnodo) { if (ptnodo != NULL) { printf("%d ",ptnodo->info); Pre_Ordem_Recursivo(ptnodo->ptesq); Pre_Ordem_Recursivo(ptnodo->ptdir); } } // ------------------------------------ Em_Ordem void Em_Ordem(TArvore a) { Em_Ordem_Recursivo(a.raiz); } // ------------------------------------ Em_Ordem_Recursivo void Em_Ordem_Recursivo(TNodo *ptnodo) { if (ptnodo != NULL) { Em_Ordem_Recursivo(ptnodo->ptesq); printf("%d ",ptnodo->info); Em_Ordem_Recursivo(ptnodo->ptdir); } } // ------------------------------------ Pos_Ordem void Pos_Ordem(TArvore a) { Pos_Ordem_Recursivo(a.raiz); } // ------------------------------------ Pos_Ordem_Recursivo void Pos_Ordem_Recursivo (TNodo *ptnodo) { if (ptnodo != NULL) { Pos_Ordem_Recursivo(ptnodo->ptesq); 166 Pos_Ordem_Recursivo(ptnodo->ptdir); printf("%d ",ptnodo->info); } } 7.5 Árvore de Busca Binária Uma árvore de busca binária (BST - Binary Search Tree) é uma árvore binária cujas chaves (informações ou dados) aparecem em ordem crescente quando a árvore é percorrida em ordem in-Fixado (esquerda -> raiz -> direita, ou seja, caminhamento ERD). Onde a chave de cada nó da árvore deve ser: • maior ou igual que qualquer chave na sua sub-árvore esquerda; • menor ou igual que qualquer chave na sua sub-árvore direita. Em outras palavras, a ordem esquerda-raiz-direita das chaves deve ser crescente. 36) Escreva um programa em C que cria a árvore binária ordenada (a, b, c, d, e, f, g). O programa deve permitir ao usuário buscar o endereço de uma determinada informação, ou seja, uma letra de ‘a’ até ‘z’. Solução do problema proposto (36): // busca.c // Compilador: Dev-C++ 4.9.9.2 #include <stdio.h> #define ESC 27 typedef char TDados; typedef struct Nodo { struct Nodo *esq; TDados info; struct Nodo *dir; } TNodo; typedef TNodo TArvore; // --------------------------------------------- Cria 167 TArvore *Cria(TArvore *esq, TDados info, TArvore* dir) { TArvore *p; p = (TArvore*) malloc(sizeof(TArvore)); if (p == NULL) { printf("ERRO FATAL: Falta de Memória\n"); getchar(); exit(0); } else { p->info = info; p->esq = esq; p->dir = dir; } return p; } // --------------------------------------------- Vazia int Vazia(TArvore *a) { if (a == NULL) return(1); else return(0); } // --------------------------------------------- Caminhamento_In_Fixado void Caminhamento_In_Fixado(TArvore *a) { if (!Vazia(a)) { Caminhamento_In_Fixado(a->esq); printf("\nEndereço: %p - Info: %c", a, a->info); Caminhamento_In_Fixado(a->dir); } } // --------------------------------------------- Destroi TArvore *Destroi(TArvore *a) { if (!Vazia(a)) { Destroi(a->esq); Destroi(a->dir); 168 free(a); } return(NULL); } // --------------------------------------------- Busca TArvore *Busca(TArvore *raiz, TDados chave) { TArvore *a1; if (raiz == NULL) return(NULL); else if (raiz->info == chave) // busca na raiz return(raiz); else { a1 = Busca(raiz->esq,chave); // busca na sub-árvore esquerda if (a1 == NULL) a1 = Busca(raiz->dir,chave); // busca na sub-árvore direita return(a1); } } // --------------------------------------------- Programa Principal int main(void) { TArvore *a,*a1,*a2,*a3,*a4,*a5,*a6,*arv; TDados info; system(“cls”); a1 = Cria(NULL,'a',NULL); a2 = Cria(NULL,'c',NULL); a3 = Cria(a1,'b',a2); a4 = Cria(NULL,'e',NULL); a5 = Cria(NULL,'g',NULL); a6 = Cria(a4,'f',a5); a = Cria(a3,'d',a6); printf("\nCaminhamento In-Fixado: "); Caminhamento_In_Fixado(a); printf("\nESC - Abandona"); do { printf("\nInfo: "); do { info = getchar(); } while (!(info >= 'a' && info <= 'z') && info != ESC); if (info != ESC) { arv = Busca(a,info); 169 printf("\nEndereço do Nodo [%c]: %p", info, arv); } } while (info != ESC); return(0); } 7.6 Árvore AVL O objetivo principal na utilização de árvores AVL é diminuir o custo de acesso as informações desejadas, ou seja, organizar a árvore de forma a otimizar a busca em uma árvore binária. Os algoritmos de árvore AVL são muito parecidos com os algoritmos de uma árvore binária, a diferença está no esforço necessário para se manter uma árvore AVL balanceada. Para manter uma árvore balanceada, precisa-se constantemente refazer a estrutura da árvore nas operações de inserção ou exclusão de elementos. Árvores AVL, B e B++ são árvores balanceadas. Balanceamento em Árvores Diz-se que uma árvore está balanceada (equilibrada), se todos os nós folhas estão à mesma distância da raiz, ou seja, uma árvore é dita balanceada quando as suas sub-árvores à esquerda e à direita possuem a mesma altura. Quando uma árvore não está balanceada, chama-se degenerada. O balanceamento de uma árvore binária pode ser: estático ou dinânico (AVL). O balanceamento estático de uma árvore binária consiste em construir uma nova versão da árvore, reorganizando-a, enquanto que no balanceamento dinâmico (AVL) a cada nova operação realizada na árvore binária, ela sobre rotações deixando-a balanceada. O termo AVL foi colocado em homenagem aos matemáticos russos Adelson-Velskii e Landis. 170 Adelson-Velskii e Landis em 1962 apresentaram uma árvore de busca binária que é balanceada levando-se em consideração a altura das suas sub-árvores, ou seja, uma árvore AVL é uma árvore binária de pesquisa onde a diferença em altura entre as sub-árvores esquerda e direita é no máximo 1 (positivo ou negativo). Está diferença é chamado de fator de balanceamento (fb). Este fator deve ser calculado para todos os nós da árvore binária. O fator de balanceamento de um nó folha é sempre zero, ou seja, fb = 0. O fator de balanceamento (fb) é um número inteiro igual a: fb (nodo) = altura (subárvore direita) – altura (subárvore esquerda); Definição: Uma árvore binária vazia é sempre balanceada por altura. Se T não é vazia e Te e Td são sub-árvores da esquerda e direita, respectivamente, então T é balanceada por altura se: a) Te e Td são balanceadas por altura; b) altura Te – altura Td for igual a 0, 1 ou -1. 7.6.1 Inserção em uma árvore AVL Quando um novo elemento é inserido em uma árvore binária, é necessário verificar se esta inserção quebrou a propriedade de balanceamento da árvore, ou seja, é necessário calcular o fator de balanceamento dos nodos da árvore. Se o Fb de algum nodo for diferente de 0, 1 ou -1, é necessário reestruturar a árvore para que volte a ser balanceada. Na inserção de um nodo em uma árvore AVL, pode-se ter quatro situações distintas, cuja a forma de tratamento é diferente: a) Inserção dos nós: 10, 20 e 30 b) Inserção dos nós: 30, 20 e 10 171 c) Inserção dos nós: 10, 30 e 20 d) Inserção dos nós: 30, 10 e 20 Note que nas quatro situações acima (a, b, c e d), o fator de balanceamento de algum nodo ficou fora da faixa [-1..1], ou seja, algum nodo teve o Fator de Balanceamento (Fb) 2 ou -2. Para cada caso acima, aplica-se um tipo de rotação a árvore: a) Rotação simples à esquerda 172 b) Rotação simples à direita c) Rotação dupla à esquerda (rotação simples à direita + rotação simples à esquerda) d) Rotação dupla à direita (rotação simples à esquerda + rotação simples à direita) Dicas: a) Para identificar quando uma rotação é simples ou dupla deve-se observar os sinais do Fb: 173 • Sinal for igual, a rotação é simples • Sinal for diferente a rotação é dupla b) Se Fb for positivo (+) a rotação para à esquerda c) Se Fb for negativa (-) a rotação para à direita 37) Escreva um programa em C que cria uma árvore binária balanceada utilizando as características de uma árvore AVL (10, 20, 30, 40, ...). O programa deve permitir a entrada de números inteiros até que o usuário digite zero (0). Ao final a árvore é exibida. Solução do problema proposto (37): // avl.c #include <stdio.h> #include <stdlib.h> #define TRUE !0 #define FALSE 0 typedef int TDados; typedef struct nodo { TDados chave; struct nodo *esq,*dir; int bal; }*TNodo; // ------------------------------------ Procura_AVL int Procura_AVL(int x, TNodo *p) { TNodo pDeTNodo; pDeTNodo = (*p); if (!pDeTNodo) return(FALSE); else if (x<pDeTNodo->chave) return(Procura_AVL(x, &pDeTNodo->esq)); else if (x>pDeTNodo->chave) return(Procura_AVL(x, &pDeTNodo->dir)); else return(TRUE); } // ------------------------------------ Insere_AVL 174 int Insere_AVL(int x, TNodo *t, int *h) { TNodo p, q, pDeTNodo; pDeTNodo = (*t); if (!pDeTNodo) { pDeTNodo = (TNodo) malloc(sizeof (struct nodo)); if (pDeTNodo == NULL) abort(); *t = pDeTNodo; *h = TRUE; pDeTNodo->chave = x; pDeTNodo->esq = NULL; pDeTNodo->dir = NULL; pDeTNodo->bal = 0; return(TRUE); } else if (x<pDeTNodo->chave) { if (!Insere_AVL(x, &pDeTNodo->esq, h)) return(FALSE); if (*h) switch(pDeTNodo->bal) { case 1: pDeTNodo->bal = 0; *h = FALSE; break; case 0: pDeTNodo->bal = (-1); break; case -1: p = pDeTNodo->esq; if (p->bal == (-1)) { // Rotação Simples pDeTNodo->esq = p->dir; p->dir = pDeTNodo; pDeTNodo->bal = 0; pDeTNodo = p; *t = pDeTNodo; } else { // Rotação Dupla q = p->dir; p->dir = q->esq; q->esq = p; pDeTNodo->esq = q->dir; q->dir = pDeTNodo; pDeTNodo->bal = (q->bal == (-1)) ? 1 : 0; p->bal = (q->bal == 1) ? (-1) : 0; pDeTNodo = q; 175 *t = pDeTNodo; } pDeTNodo->bal = 0; *h = FALSE; break; } return(TRUE); } else if (x>pDeTNodo->chave) { if (!Insere_AVL(x, &pDeTNodo->dir, h)) return(FALSE); if (*h) switch(pDeTNodo->bal) { case -1: pDeTNodo->bal = 0; *h = FALSE; break; case 0: pDeTNodo->bal = 1; break; case 1: p=pDeTNodo->dir; if (p->bal == 1) { // Rotação Simples pDeTNodo->dir = p->esq; p->esq = pDeTNodo; pDeTNodo->bal = 0; pDeTNodo = p; *t = pDeTNodo; } else { // Rotação Dupla q = p->esq; p->esq = q->dir; q->dir = p; pDeTNodo->dir = q->esq; q->esq = pDeTNodo; pDeTNodo->bal = (q->bal == 1) ? (-1) : 0; p->bal = (q->bal == (-1)) ? 1 : 0; pDeTNodo = q; *t = pDeTNodo; } pDeTNodo->bal = 0; *h = FALSE; break; } return(TRUE); } else { *h = FALSE; 176 return(FALSE); } } // ------------------------------------ Exibe_AVL void Exibe_AVL(TNodo pt,int indent) { int i; if (pt) { Exibe_AVL(pt->dir, indent+1); for (i = 0;i < indent;i++) printf(" "); printf("%d (%d)\n",pt->chave, pt->bal); Exibe_AVL(pt->esq, indent+1); } } // ------------------------------------ main int main(void) { TNodo raiz; int chave, dh; system("cls"); printf("Árvore AVL (0 - Sair)\n\n"); raiz = NULL; do { printf("Chave: "); scanf("%d", &chave); if (chave != 0) { if (!Insere_AVL(chave, &raiz, &dh)) printf("ERRO: Chave Repetida\n"); if (!Procura_AVL(chave, &raiz)) printf("ERRO: Chave Perdida\n"); } } while (chave != 0); printf("\nÁRVORE AVL\n\n"); Exibe_AVL(raiz,0); printf("\n"); system("pause"); } Programa online que demonstra inserções e remoções em uma árvore AVL: http://www.site.uottawa.ca/~stan/csi2514/applets/avl/BT.html 177 7.6.2 Remoção em uma árvore AVL Quando um elemento é removido de uma árvore balanceada AVL, é necessário verificar se esta operação quebrou a propriedade de balanceamento da árvore, ou seja, é necessário calcular o Fator de Balanceamento dos nodos da árvore. Se o Fb de algum nodo for diferente de 0, 1 ou -1, é necessário reestruturar a árvore para que volte a ser balanceada. Na remoção de um nodo em uma árvore AVL, pode-se ter quatro situações distintas, cuja a forma de tratamento é diferente: a) Remoção do nó: 10 Resultado: Com a remoção do nodo 10 a árvore ficará desbalanceada, pois o nodo raiz ficará com Fb = 2. A solução será fazer uma rotação simples à esquerda. b) Remoção do nó: 40 Resultado: Com a remoção do nodo 40 a árvore ficará desbalanceada, pois o nodo raiz ficará com Fb = -2. A solução será fazer uma rotação simples à direita. 178 c) Remoção do nó: 10 Resultado: Com a remoção do nodo 10 a árvore ficará desbalanceada, pois o nodo raiz ficará com Fb = 2. A solução será fazer uma rotação dupla à esquerda (rotação simples à direita + rotação simples à esquerda). d) Remoção do nó: 40 Resultado: Com a remoção do nodo 40 a árvore ficará desbalanceada, pois o nodo raiz ficará com Fb = 2. A solução será fazer uma rotação dupla à direita (rotação simples à esquerda + rotação simples à direita). 179 8. Grafos 8.1 Conceitos Um grafo G é definido como G (V, A) onde V é um conjunto finito e não vazio de vértices e A é um conjunto finito de arestas, ou seja, linhas, curvas ou setas que interligam dois vértices. “Grafo é um par ordenado de conjuntos disjuntos (V, A), onde V é um conjunto arbitrário que se designa por conjunto dos vértices e A um subconjunto de pares não ordenados de elementos (distintos) de V que se designa por conjunto das arestas.” Um vértice é representado por um ponto ou círculo representando um nó, nodo ou informação. Uma aresta pode ser uma reta, seta ou arco representando uma relação entre dois nodos. Quando uma aresta possui indicação de sentido (uma seta), ela é chamada de arco, caso contrário é chamada de linha (veja grafo acima). Orientação é a direção para a qual uma seta aponta, um grafo deste tipo é chamado grafo dirigido ou orientado. 180 G(4,7) – 4 vértices e 7 arestas Cardinalidade (ordem) de um conjunto de vértices é igual a quantidade de seus elementos. Grafo denso possui alta cardinalidade de vértices, enquanto que grafo pouco povoado possui baixa cardinalidade. A ordem (V) de um grafo G é o número de vértices do grafo enquanto que a dimensão (A) é o número de arestas do grafo. No exemplo acima, a ordem do grafo é 4 enquanto que a dimensão é 7. O conjunto de arcos do grafo acima é: {<A,A>, <A,B>, <A,C>, <A,D>, <C,D>, <F,C>, <F,G>, <D,F>} 1 2 3 4 5 6 7 8 Um vértice que não possui nenhuma aresta incidente é chamado de isolado (figura abaixo). Um grafo com nenhum vértice é chamado de vazio. 181 Passeio é uma seqüência de vértices e arestas onde o caminho é um passeio sem vértices repetidos (seqüência de vértices em que cada dois vértices consecutivos são ligados por um arco). Trajeto é um passeio sem arestas repetidas. Passeio é uma seqüência <v0, a1, v1, a2, ..., vk-1, ak, vk> onde v0, v1,....,vk são vértices, a1, a2,...., ak são arcos e, para cada (i, ai) é um arco de vi-1 a vi. O vértice v0 é o início do passeio e o vértice vk é o seu término. Um caminho é um passeio sem vértices repetidos. A dimensão de um caminho ou trajeto é chamado de comprimento. Ciclo é um caminho de comprimento não nulo fechado, ou seja, tem os vértices extremos iguais (é um passeio onde v0 = vk.). Circuito é um trajeto de comprimento não nulo fechado (é um ciclo sem vértices, com exceção feita a v0 e vk). Laço é uma aresta que retorna ao mesmo vértice. Se a aresta não tiver seta ela é dita sem orientação. Diz-se que uma aresta é incidente com os vértices que ela liga, não importando a orientação. Dois vértices são adjacentes se estão ligados por uma aresta. Um vértice é dito isolado se não existe aresta incidente sobre ele. Duas arestas incidentes nos mesmos vértices, não importando a orientação, a1 = (v,w) e a2 = (v,w), então as arestas são paralelas. 182 Diz-se que o grafo é conexo se para cada par de vértices existe pelo menos um passeio que os une. Chama-se grafo não-orientado ou não-dirigido se as arestas representam relacionamento nas duas direções, ou seja, as arestas não possuem uma seta indicando sentido. Grafo não-orientado ou não-dirigido V = { Paulo, Adriane, Paola, Roberta } A = { (Paulo, Adriane) , (Paulo, Paola) , (Adriane, Paola) , (Adriane, Roberta) } Explicação do grafo acima: O exemplo representa uma relação de amizade, ou seja, se Paulo é amigo de Adriane o inverso é verdade, isto se chama: relação simétrica. Quando um grafo possui arcos (seta indicando uma direção) ele é denominado grafo dirigido ou dígrafo (veja próxima figura). Grafo dirigido ou dígrafo 183 Explicação do grafo acima: O exemplo representa uma relação de subordinação, ou seja, se “Paulo” é chefe de “Adriane” o inverso não é verdade, isto se chama: relação não-simétrica. Grau de um vértice, em um grafo não-dirigido, é o número de arestas incidentes ao vértice. Em um grafo dirigido, pode-se dividir o grau em dois: grau de emissão (número de arestas que saem do vértice) e grau de recepção (número de arestas que chegam no vértice). Toda árvore é um grafo, mas nem todo grafo é uma árvore. Um grafo onde existe um número associado a cada arco (peso) é chamado de rede ou grafo ponderado. Um exemplo deste tipo é um grafo representando cidades e distâncias entre as cidades (veja figura abaixo). Grau de um Vértice: É igual ao número de arestas que são incidentes ao vértice. Um laço é contado duas vezes. No exemplo acima, o vértice A tem grau 3 enquanto que o vértice B tem grau 1. Em um grafo dirigido o grau de um vértice é a soma do número de arestas que saem e chegam no vértice. Tipos de Grafos a) Simples: É um grafo que não possui laços nem arestas paralelas. 184 b) Dirigido (dígrafo ou direcionado): Consiste de dois conjuntos finitos: a) vértices e b) arestas dirigidas, onde cada aresta é associada a um par ordenado de vértices chamados de nós terminais. c) Completo: Um grafo completo de n vértices, denominado Kn, é um grafo simples com n vértices v1, v2, . . . , vn, cujo conjunto de arestas contém exatamente uma aresta para cada par de vértices distintos. d) Ciclo: Um grafo ciclo de n vértices, denominado Cn, onde n é maior ou igual a 3, é um grafo simples com n vértices v1, v2, . . . , vn, e arestas v1v2, v2v3, . . ., vn−1vn, vnv1. 185 e) Multigrafo: É um grafo que não possui laços, mas pode ter arestas paralelas. f) Valorado: É um grafo em que cada aresta tem um valor associado (peso), ou seja, possui um conjunto de valores (pesos) associados a cada aresta. g) Planar: É um grafo onde não há cruzamento de arestas. h) Imersível: Um grafo é imersível em uma superfície S se puder ser representado geograficamente em S de tal forma que arestas se cruzem nas extremidades (vértices). Um grafo planar é um grafo que é imersível no plano. Um exemplo de grafo imersível é a representação das conexões de uma placa de circuito impresso, onde as arestas não 186 podem se cruzar, ou seja, os cruzamentos são permitidos apenas nas extremidades. i) Regular: Um grafo é regular quando todos os seus vértices têm o mesmo grau. Grafos completos com 2, 3, 4, e 5 vértices são grafos regulares. Grafos podem ser representados de duas formas: Matriz de Adjacências (forma apropriada para representar grafos densos) ou Lista de Adjacências (forma apropriada para representar grafos esparsos). 8.2 Representação por Lista e Matriz de Adjacências 8.2.1 Lista de Adjacências Um grafo pode ser representado por uma lista Adjacente[vi] = [va, vb] onde va, vb, ... representam os vértices que se relacionam com o vértice vi. Lista de Adjacências para um grafo dirigido: Lista de Adjacências para um grafo não-dirigido: 187 8.2.2 Matriz de Adjacências Um grafo pode ser representado por uma matriz A = (aij), onde aij representa o número de arestas de vi para vj. Matriz de Adjacências para um grafo dirigido: Matriz de Adjacências para um grafo não-dirigido: 188 8.3 Percurso em Amplitude e Percurso em Profundidade Existem dois critérios para percorrer grafos: Percurso em Amplitude e Percurso em Profundidade. Em ambos os percursos parte-se de um nodo qualquer escolhido arbitrariamente e visita-se este nodo. A seguir, considera-se cada um dos nodos adjacentes ao nodo escolhido. Percurso em amplitude ou caminhamento em amplitude: a) Seleciona-se um vértice para iniciar o caminhamento. b) Visitam-se os vértices adjacentes, marcando-os como visitados. c) Coloca-se cada vértice adjacente numa fila. d) Após visitar os vértices adjacentes, o primeiro da fila torna-se o novo vértice inicial. Reinicia-se o processo. e) O caminhamento termina quanto todos os vértices tiverem sido visitados ou o vértice procurado for encontrado. Percurso em profundidade ou caminhamento em profundidade: a) Seleciona-se um vértice para iniciar o caminhamento. b) Visita-se um primeiro vértice adjacente, marcando-o como visitado. c) Coloca-se o vértice adjacente visitado numa pilha. d) O vértice visitado torna-se o novo vértice inicial. e) Repete-se o processo até que o vértice procurado seja encontrado ou não haja mais vértices adjacentes. Se verdadeiro, desempilha-se o topo e procura-se o próximo adjacente, repetindo o algoritmo. f) O processo termina quando o vértice procurado for encontrado ou quando a pilha estiver vazia e todos os vértices tiverem sido visitados. 8.4 Determinação do Caminho Mínimo O caminho de um vértice a outro vértice é mínimo se não existe outro caminho entre eles que tenha menos arcos. O problema de encontrar o caminho mais curto entre dois nós de um grafo ou uma rede é um dos problemas clássicos da Ciência da Computação. Este problema consiste, genericamente, em encontrar o caminho de menor custo entre dois nós da rede, considerando a soma dos custos associados aos arcos percorridos. 189 O mais famoso algoritmo para resolver o problema de caminho mínimo em grafos é o algoritmo de Dijkstra (1959). Este algoritmo apenas funciona se os custos associados aos arcos não forem negativos, mas isso não é muito importante na maioria dos problemas práticos pois, em geral, os custos associados aos arcos são em geral grandezas fisicamente mensuráveis. Algoritmo de Dijkstra Dado um grafo, G=(V,E) , dirigido ou não, com valores não negativos em cada arco ou ramo, utiliza-se o algoritmo de Dijkstra para encontrar a distância mínima entre um vértice inicial (s) e um vértice final (v). Ele determina a distância mínima entre s e os outros vértices na ordem dessas distâncias mínimas, ou seja, os vértices que se encontram mais próximos de s em primeiro lugar. O algoritmo vai usar dist(v), uma estrutura que armazena e referencia a distância de s ao nó v. De início, dist(s)=0 , pois a menor distância de s a si mesmo será sempre zero. O símbolo LIMITE representa um valor maior que o comprimento de qualquer caminho sem ciclos em G. Por outro lado, se dist(v) = LIMITE, indica que ainda não foi encontrado nenhum caminho com distância mínima entre s e v. De início, dist(v) = LIMITE. Será utilizado um conjunto S que contém os vértices cuja distância a s é mínima e conhecida naquele momento da execução do algoritmo. Esta distância será definitiva para cada um deles. Este conjunto será de início constituído somente pelo nó s, com dist(s) = 0. Descrição do Algoritmo Começa-se considerando a distância ao próprio s como zero. Faze-se então dist(s)= 0. Para todos os outros vértices, considera-se a sua distância a s como valendo LIMITE. Fazer então dist(v) = LIMITE. O processo de introdução de um nó Vn não pertencente a S, assumindo portanto que V(S) é diferente do conjunto vazio, consiste no seguinte: 1. Qualquer um que seja Vm não pertencente a S tal que v foi o último nó a 'entrar' em S e (V, Vm) pertencente ao conjunto V, se dist(Vm) > 190 dist(V) + distância associada a (V,Vm) então dist(Vm) = dist(V) + distância associada a (V, Vm); 2. Determinar qualquer que seja Vm não pertencente a S o menor de entre os valores de dist(vm). Seja dist(vj) esse valor; 3. Fazer Vn = Vj , dist(Vn) = dist(Vj) e Vn passa a ser o novo elemento de S; Se o vértice vn coincidir com o vértice final então dist(Vn) é a menor distância entre s e Vn , e parar a execução. Se não coincidir, voltam-se a repetir os passos 1 , 2 e 3. Se não for possível aplicar aqueles passos, ou porque V(S) igual ao conjunto vazio ou qualquer que seja Vm não pertencente a S então (V, Vm) não pertence ao conjunto V, então não é possível determinar a distância mínima de s ao vértice final. 38) Escreva um programa em C que cria um grafo representando a ligação entre seis cidades com suas respectivas distâncias (São Paulo, Rio de Janeiro, Vitória, Recife, Salvador e Natal). O programa deve permitir a entrada da cidade origem (0..5) e da cidade destino (0..5) e exibir o caminho mínimo entre estas duas cidades através da utilização do algoritmo de Dijkstra. Solução do problema proposto (38): // Dijkstra.c 191 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_VERTICES 6 #define LIMITE 32767 #define TRUE !0 #define FALSE 0 typedef struct { char adj; int valor; } Tinfo; // ------------------------------------ newline void newline(void) { printf("\n"); } // ------------------------------------ Inicializa_Grafo void Inicializa_Grafo(Tinfo grafo[][MAX_VERTICES]) { int l, c; for (l = 0; l < MAX_VERTICES; l++) for (c = 0; c < MAX_VERTICES; c++) { grafo[l][c].valor = LIMITE; grafo[l][c].adj = 'N'; } } // ------------------------------------ Cria_Aresta void Cria_Aresta(Tinfo grafo[][MAX_VERTICES], int origem, int destino, int info) { grafo[origem][destino].adj = 'S'; grafo[origem][destino].valor = info; grafo[destino][origem].adj = 'S'; grafo[destino][origem].valor = info; } // ------------------------------------ Imprime_Grafo void Imprime_Grafo (Tinfo grafo[][MAX_VERTICES], char Rot[][5]) { int l, c; for (l = 0; l < MAX_VERTICES; l++) printf(" %d", l); newline(); for (l = 0; l < MAX_VERTICES; l++) printf(" %s", Rot[l]); newline(); } // ------------------------------------ Imprime_Matriz void Imprime_Matriz(Tinfo grafo[][MAX_VERTICES]) 192 { int l, c; printf("Matriz de Adjacencias\n"); for (l = 0; l < MAX_VERTICES; l++) { for (c = 0; c < MAX_VERTICES; c++) printf("%5d ",grafo[l][c].valor); newline(); } newline(); } // ------------------------------------ Entrada_Origem_Destino void Entrada_Origem_Destino(int tam, int *origem, int *destino) { printf("\n Origem [0..%d]: ", tam); do { scanf("%d",origem); } while (*origem < 0 || *origem > tam); printf("Destino [0..%d]: ", tam); do { scanf("%d",destino); } while (*destino < 0 || *destino > tam); } // ---------------------------------------------- Dijkstra long int Dijkstra(Tinfo grafo[][MAX_VERTICES], int origem, int destino, int precede[]) { int i, k; int distancia[MAX_VERTICES]; int menor_cam [MAX_VERTICES]; int atual, dc, menordist, novadist; char parar = 'N'; for (i = 0; i < MAX_VERTICES; i++) { distancia[i] = LIMITE; menor_cam [i] = FALSE; precede[i] = -1; } menor_cam [origem] = TRUE; distancia[origem] = 0; atual = origem; k = atual; while (atual != destino && parar == 'N') { menordist = LIMITE; dc = distancia[atual]; for (i = 0; i < MAX_VERTICES; i++) { if (menor_cam [i] == FALSE) { if (grafo[atual][i].adj =='S') novadist = dc + grafo[atual][i].valor; else novadist = grafo[atual][i].valor; if (novadist < distancia[i]) { distancia[i] = novadist; precede[i] = atual; } 193 if (distancia[i] < menordist) { menordist = distancia[i]; k = i; } } } if (atual == k) parar = 'S'; else { atual = k; menor_cam [ atual] = FALSE; } } return((long) distancia[destino]); } // ------------------------------------ Imprime_Cidades void Imprime_Cidades(void) { int i; char cidades[MAX_VERTICES][30] = {"[0] - (Spa) - Sao Paulo", "[1] - (Rio) - Rio de janeiro", "[2] - (Vit) - Vitoria", "[3] - (Rec) - Recife", "[4] - (Sal) - Salvador", "[5] - (Nat) - Natal"}; for (i = 0;i < MAX_VERTICES;i++) printf("%s\n",cidades[i]); newline(); } // ------------------------------------ main int main(void) { Tinfo grafo[MAX_VERTICES][MAX_VERTICES]; int precede[MAX_VERTICES]; char rotulos[MAX_VERTICES][5] = {"Spa", "Rio", "Vit", "Rec", "Sal", "Nat"},tecla; int origem, destino, aux1, aux2; long int result; Inicializa_Grafo(grafo); Cria_Aresta(grafo, 0, 1, 300); Cria_Aresta(grafo, 0, 3, 400); Cria_Aresta(grafo, 0, 4, 100); Cria_Aresta(grafo, 1, 2, 100); Cria_Aresta(grafo, 1, 5, 70); Cria_Aresta(grafo, 2, 3, 50); Cria_Aresta(grafo, 4, 5, 50); Cria_Aresta(grafo, 3, 5, 150); do { system("cls"); Imprime_Matriz(grafo); Imprime_Cidades(); Imprime_Grafo(grafo, rotulos); Entrada_Origem_Destino(MAX_VERTICES-1,&origem, &destino); result = Dijkstra(grafo, origem, destino, precede); if (result == LIMITE || result == 0) printf("\nNao ha trajeto entre %s e %s", rotulos[origem], rotulos[destino]); else { 194 printf("\nMenor caminho entre %s e %s = %ld", rotulos[origem], rotulos[destino], result); printf("\nCaminho INVERSO Percorrido: "); aux1 = precede[destino]; aux2 = destino; while (aux1 != origem) { printf("\n %s -> %s (%d)", rotulos[aux1], rotulos[aux2], grafo[aux1][aux2].valor); aux2 = aux1; aux1 = precede[aux1]; printf("\n %s -> %s (%d)", rotulos[aux1], rotulos[aux2], grafo[aux1][aux2].valor); } } newline(); printf("\nRepetir [S/N]? "); do { tecla = getchar(); } while (!strchr("SsNn",tecla)); } while (strchr("Ss",tecla)); return(0); } 195