Estruturas de Dados em C

Propaganda
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
Download