Buscar

UNIVESP - 2020 - Revisão - ESTRUTURAS DE DADOS - EID001

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 12 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 12 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 12 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

ESTRUTURA DE DADOS
Produ�vidade7
TEXTO-BASE
Revisão
Após assistir à videoaula e efetuar a leitura dos materiais, você será capaz de refletir sobre o texto a
seguir:
 REVISÃO FINAL - ESTRUTURAS DE DADOS - EID001
Revisão Final - Estruturas de Dados - EID001
Univesp
João Luís G. Rosa
Este material foi preparado para tentar ajudar a esclarecer as dúvidas apontadas pelos alunos da
disciplina “Estrutura de Dados”, no terceiro bimestre de 2020. É importante salientar que há necessidade
de estudo e consulta aos materiais bibliográficos disponibilizados para um entendimento satisfatório dos
assuntos apresentados. Serão revisados de forma simplificada os seguintes itens solicitados pelos
alunos:
COMANDOS E FUNÇÕES UTILIZADAS PARA O DESENVOLVIMENTO DOS PROGRAMAS
Para se compreender as estruturas de dados, há a necessidade de se conhecer como implementá-las
na linguagem de programação C.
 
 
 // meuprimeiroprograma.c: mostra a mensagem “Linguagem C” 
 #include // inclui a biblioteca para a função de 
 // saída printf 
 void main() // cabeçalho da função main (obrigatória) 
 { // início do corpo da função main 
 // mostra na tela a mensagem “Linguagem C” 
 // e pula uma linha ('\n') 
Comandos e funções utilizadas para o desenvolvimento dos programas•
Lista duplamente ligada•
Árvore binária e árvore binária de busca•
Estruturas pilha e fila•
Árvores binárias e n-árias•
 printf("Linguagem C\n"); 
 } // final do corpo da função main 
 
 
A linguagem C, como a maioria das linguagens de programação procedimentais, possui dados simples,
como int (número inteiro), char (caractere) e float (número real). Possui também dados estruturados,
que são dados compostos de dados simples, como a string (cadeia de caracteres - veja char nome[20]
no exemplo abaixo, que armazena uma cadeia de até 19 caracteres: um caractere é reservado para o
caractere '\0' que representa final de string), ou a struct, tipo de dado muito utilizado neste curso. Para
definir dados do tipo struct, que comporta tipos diferentes, define-se primeiro o tipo:
 
 struct produto 
 { 
 char nome[20]; 
 float volume; 
 float preco; 
 }; 
 
 
 
 
Depois, define-se as variáveis desse tipo:
 
 struct produto brinquedo; // variável do tipo struct produto 
 
Veja os exemplos de iniciação de duas variáveis boneca e pato do tipo struct produto:
 
 struct produto boneca = 
 { 
 “Gloriosa Gloria”, // valor do nome 
 1.88, // valor do volume 
 29.99 // valor do preço 
 }; 
 
 struct produto pato = {“Daphne”, 0.12, 9.98}; 
 
 
Para acessar os campos de uma struct, usa-se o operador . (ponto): boneca.nome contém a string
“Gloriosa Gloria”.
Três propriedades que um programa deve manter quando armazena dados:
A definição de uma variável simples obedece a esses três pontos. A declaração int var = 0; por
exemplo, provê o tipo (int) e um nome simbólico para o valor (var). Também faz com que o programa
aloque memória para o valor 0 e mantenha o local internamente.
Veremos agora uma segunda estratégia baseada em ponteiros, que são variáveis que armazenam
endereços ao invés dos próprios valores (outro tipo de dado muito utilizado neste curso são os
ponteiros). Mas antes de discutir ponteiros, vejamos como achar endereços de memória explicitamente
para variáveis comuns. Aplique o operador de endereço, &, a uma variável para pegar sua posição na
memória; por exemplo, se exemp1 é uma variável, &exemp1 é seu endereço na memória do
computador.
 
 void main() 
 { 
 int exemp1 = 10; 
 float exemp2 = 1.5; 
 
 printf("Valor de exemp1: %d; endereco de exemp1: %d\n", exemp1, &exemp1); 
 printf("Valor de exemp2: %g; endereco de exemp2: %d\n", exemp2, &exemp2); 
 
 } 
 
A saída desse programa é:
 
 Valor de exemp1: 10; endereco de exemp1: 6487580 
 Valor de exemp2: 1.5; endereco de exemp2: 6487568 
 
(Os endereços de memória mostrados na saída acima dependem da máquina em que o programa foi
executado). O uso de variáveis comuns trata o valor como uma quantidade nomeada e a posição como
uma quantidade derivada. A estratégia que usa ponteiros trata a posição como a quantidade nomeada e
o valor como uma quantidade derivada. Este tipo especial de variável – o ponteiro – armazena o
endereço de um valor. Então, o nome do ponteiro representa a posição. Aplicando o operador *,
chamado de operador de valor indireto ou de dereferenciação, fornece o valor da posição. Suponha por
exemplo, que ordem é um ponteiro. Então, ordem representa um endereço, e *ordem representa o
valor naquele endereço. *ordem torna-se equivalente a um tipo comum.
onde a informação é armazenada;I.
que valor é mantido lá;II.
que tipo de informação é armazenada.III.
Seja o seguinte exemplo:
 
 int jumbo = 23; 
 int *pe = &jumbo; 
 
 
Declarando e iniciando ponteiros
O computador deve manter o tipo do valor para o qual um ponteiro se refere. Por exemplo, o endereço
de um char parece igual ao endereço de um float, mas char e float usam diferentes números de bytes
e diferentes formatos internos para armazenarem valores. Portanto, uma declaração de ponteiro deve
especificar o tipo de dado para o qual o ponteiro aponta. Por exemplo, o seguinte exemplo tem esta
declaração:
 
 int *p_atualiza; 
 
p_atualiza é um ponteiro e aponta para o tipo int.
Alocando memória com malloc
Usamos nos exemplos anteriores a iniciação de ponteiros a endereços de variáveis; em tempo de
compilação há a alocação das variáveis e os ponteiros provêm outra forma de acesso a elas que já
poderia ter sido feito através dos próprios nomes das variáveis. O verdadeiro valor dos ponteiros
acontece quando se aloca memória não utilizada por outras variáveis durante o tempo de execução.
Nesse caso os ponteiros se tornam o único acesso àquela memória. Em C, pode-se alocar memória com
a função malloc(). Basta dizer para que tipo de dado você quer memória; malloc() acha um bloco do
tamanho indicado (a quantidade de memória a ser alocada em número de bytes) e retorna o endereço
do bloco. O endereço retornado é de um tipo genérico (void). Basta transformá-lo no tipo desejado
usando o operador cast (). Atribua esse endereço a um ponteiro e pronto!
A função malloc possui o seguinte protótipo:
 
 void *malloc(size_t size); 
 
que recebe como parâmetro o tamanho em bytes da região a ser alocada e retorna um ponteiro para a
área alocada. Se ocorrer um erro ao alocar memória, retorna um ponteiro para NULL.
O seguinte exemplo pede a alocação dinâmica de um vetor de inteiros (vector) com vinte e cinco
posições, altera o valor na posição dez e depois libera a área de memória, usando o comando free()
 
 int *vector = NULL; /* declaração do ponteiro */ 
 vector = (int*) malloc(25 * sizeof(int)); /* alocação de memória para vector */ 
 vector[10] = 34; /* altera o valor da posição dez para trinta e quatro */ 
 free(vector); /* liberta a área de memória alocada */ 
 
É possível também criar struct dinâmicas, usando ponteiros. Para acessar os elementos de uma struct
dinâmica, através de seu ponteiro, pode-se usar o operador ->. Ou continuar acessando pelo operador .
(ponto), desde que se acesse antes o conteúdo do elemento apontado, pelo operador de
dereferenciação (*). Veja o exemplo abaixo, que apresenta os dois métodos de acesso aos elementos
(membros) de uma struct. A função de entrada scanf exige o uso do operador de endereço (&) em seuparâmetro. A função de entrada gets pode ser usada quando a entrada for uma string e nesse caso, o
operador de endereço não é usado.
 
 #include 
 #include // necessário para uso da função malloc() 
 
 typedef struct produto 
 { 
 char nome[20]; 
 float volume; 
 float preco; 
 } ELEMENTO; 
 
 typedef ELEMENTO* PONT; 
 
 void main() 
 { 
 ELEMENTO *ps = (PONT) malloc(sizeof(ELEMENTO));// aloca espaço para *ps 
 
 printf("Entre com o nome do item produto: "); 
 gets(ps->nome); // método 2 para acesso ao membro 
 printf("Entre com o volume em centimetros cubicos: "); 
 scanf("%f", &((*ps).volume)); // método 1 para acesso ao membro 
 printf("Entre com o preco: R$"); 
 scanf("%f", &(ps->preco)); // método 2 
 printf("\nNome: %s\n", (*ps).nome); // método 1 
 printf("Volume: %g centimetros cubicos\n", ps->volume); 
 printf("Preco: R$%g\n", ps->preco); // método 2 
 free(ps); // libera área alocada por malloc() 
 } 
 
 
LISTA DUPLAMENTE LIGADA
As listas ligadas são estruturas de dados lineares e podem ser simplesmente ou duplamente ligadas. As
listas simplesmente ligadas apresentam, em sua estrutura, apenas um ponteiro para o próximo
elemento. As listas duplamente ligadas apresentam dois ponteiros: um para o antecessor (elemento
anterior) e outro para o sucessor (próximo elemento).
 
 struct no 
 { 
 int info; 
 
 struct no *prox; 
 struct no *ant; 
 }; 
 
no é o tipo de cada elemento da lista. O ponteiro prox apontará para o nó posterior (próximo) ao nó atual
e o ponteiro ant apontará para o nó anterior ao nó atual.
Existem duas formas básicas de controle de listas duplamente ligadas:
1. Dois ponteiros que apontam respectivamente, para o início e o fim da lista. O ponteiro ant do primeiro
nó será NULL, assim como o ponteiro prox do último nó:
2. Uma lista duplamente encadeada circular. Neste tipo o ponteiro ant do primeiro nó aponta para o
ultimo nó, e o ponteiro prox do último nó aponta para o primeiro elemento. Neste tipo de lista você
precisa de ao menos um ponteiro de controle, para apontar, para o primeiro elemento, ou para o último,
ou para o último elemento alterado.
Deve-se notar que a vantagem da lista duplamente ligada sobre a simplesmente ligada é o segundo
ponteiro (o ponteiro de retrocesso). Em estruturas de dados extensas, você pode varrer a lista em busca
de dados nos dois sentidos ao mesmo tempo, reduzindo o tempo da busca pela metade (no pior dos
casos).
ÁRVORE BINÁRIA E ÁRVORE BINÁRIA DE BUSCA
Uma árvore binária é uma estrutura de dados não linear caracterizada por:
Essa definição é recursiva e devido a isso, muitas operações sobre árvores binárias utilizam recursão. É
o tipo de árvore mais utilizada em computação. A principal utilização de árvores binárias são as árvores
binárias de busca. Em uma árvore binária, por definição, cada nó poderá ter até duas subárvores. A
profundidade de um nó é a distância desse nó até a raiz. A maior profundidade de um nó é a altura da
árvore.
Uma árvore binária de busca (ou árvore binária de pesquisa) é uma estrutura de dados de árvore
binária baseada em nós, que possui a propriedade de ordenação, ou seja, todos os nós da subárvore
esquerda possuem um valor numérico inferior ao nó raiz e todos os nós da subárvore direita possuem
um valor superior ao nó raiz. Essa propriedade vale para todos os nós da árvore. O objetivo dessa
árvore é estruturar os dados de forma a permitir a busca binária, mais eficiente que a busca sequencial.
Para implementação de uma árvore binária (de busca ou não) pode-se usar a definição:
 
 typedef struct aux{ 
 int chave; 
 /* Dados armazenados vão aqui */ 
 struct aux *esq, *dir; 
 } NO; 
 
 typedef NO* PONT; 
 
 
no qual PONT é o ponteiro para um elemento da árvore (inicialmente o nó raiz), uma struct NO. esq e
dir são os ponteiros para as subárvores esquerda e direita, respectivamente.
Para realizar a busca binária não recursiva numa árvore binária de busca, que está ordenada, pode-se
usar a função buscaNo abaixo, que devolve o ponteiro do nó buscado. A função recebe um ponteiro
para o nó raiz, a chave buscada, e o ponteiro para o pai do nó. Inicialmente, o nó atual recebe o nó raiz.
Se encontrar a chave procurada, retorna. Abastece o pai com o ponteiro para o nó pai deste. Se não
encontra a chave, verifica se a chave procurada é maior que a chave do nó analisado, se for procura na
subárvore esquerda. Caso contrário, procura na subárvore direita. Um retorno NULL significa que não
encontrou. Encontrando o nó, um pai NULL significa que o nó buscado é o nó raiz.
 
 PONT buscaNo(PONT raiz, int ch, PONT *pai){ 
 PONT atual = raiz; 
 *pai = NULL; 
 while (atual) { 
 if(atual->chave == ch) return(atual); 
 *pai = atual; 
 if(ch < atual->chave) atual = atual->esq; 
 else atual = atual->dir; 
Não possui nenhum elemento (árvore vazia), ou1.
Tem um elemento denominado raiz, com dois ponteiros para duas estruturas diferentes,
denominadas subárvore esquerda e subárvore direita.
2.
 } 
 return(NULL); 
 } 
 
 
Para inserir um nó na árvore binária de busca, pode-se usar a função adiciona, que é recursiva. Recebe
a raiz da árvore e o nó a ser inserido e insere o nó na árvore de forma ordenada. Lembre-se que uma
árvore binária de busca apresenta os nós à esquerda de qualquer nó com valores menores que este e
os da direita, maiores. Retorna à raiz da árvore resultante.
 
 PONT adiciona(PONT raiz, PONT no) 
 { 
 if (raiz == NULL) return(no); 
 
 if (no->chave < raiz->chave) 
 raiz->esq = adiciona(raiz->esq, no); 
 else 
 raiz->dir = adiciona(raiz->dir, no); 
 // ignoro se o nó tiver chave igual 
 return(raiz); 
 } 
 
 
ESTRUTURAS PILHA E FILA
Pilha é uma estrutura de dados do tipo lista linear em que todas as inserções e remoções de elemento
só podem ser feitos em uma extremidade chamada topo. As pilhas também são chamadas de estruturas
de dados LIFO (Last In, First Out), ou seja, o último elemento inserido é o primeiro removido.
A pilha pode ser estática ou dinâmica. A pilha estática é implementada em um vetor (ou arranjo) e
a pilha dinâmica é uma lista ligada dinâmica com a propriedade de que novos elementos são
sempre inseridos em seu final (ou topo) e elementos excluídos também são retirados de seu final
(topo).
Para implementar uma pilha dinâmica, pode-se usar as seguintes definições:
O ponteiro topo aponta para o topo da pilha (será NULL caso a pilha esteja vazia).
 
 typedef struct { 
 int chave; 
 // outros campos... 
 } REGISTRO; 
 
 typedef struct aux { 
 REGISTRO reg; 
 struct aux* prox; 
 } ELEMENTO; 
 
 typedef ELEMENTO* PONT; 
 
 typedef struct { 
 PONT topo; 
 } PILHA; 
 
 
As duas únicas operações na pilha são implementadas pelas funções push (colocar um elemento no
topo da pilha) e pop (retirar o elemento do topo da pilha). Veja que na função push, um espaço é
reservado na memória pelo malloc() para armazenar o novo elemento e na função pop, o espaço da
memória é tornado disponível pela função free().
 
 #definetrue 1 
 #define false 0 
 
 typedef int bool; 
 
 /* Inserção em pilha (sempre no topo). Note que a função retorna bool, mas sempre retornará true */ 
 
 bool push(PILHA* p, REGISTRO reg) { 
 PONT novo = (PONT) malloc(sizeof(ELEMENTO)); 
 novo->reg = reg; 
 novo->prox = p->topo; 
 p->topo = novo; 
 return true; 
 } 
 
 
 /* Exclusão do elemento do topo da pilha e cópia desse elemento para o endereço apontado por reg */ 
 
 bool pop(PILHA* p, REGISTRO* reg){ 
 if ( p->topo == NULL) return false; 
 *reg = p->topo->reg; 
 PONT apagar = p->topo; 
 p->topo = p->topo->prox; 
 free(apagar); 
 return true; 
 } 
 
 
Uma fila é uma estrutura de dados o tipo lista lineard que usa o método FIFO (First In, First Out), que
significa o primeiro elemento a entrar é o primeiro a sair. A ideia fundamental da fila é que só podemos
inserir um novo elemento no final da fila e só podemos retirar o elemento do início. A fila pode ser
estática ou dinâmica. A fila estática é implementada em um vetor (ou arranjo) e a fila dinâmica é
uma lista ligada dinâmica com a propriedade de que novos elementos são sempre inseridos em
seu final e elementos excluídos são retirados de seu início.
Para implementar uma fila dinâmica, pode-se usar as seguintes definições:
 
 typedef struct { 
 int chave; 
 // outros campos... 
 } REGISTRO; 
 
 typedef struct aux { 
 REGISTRO reg; 
 struct aux* prox; 
 } ELEMENTO, *PONT; 
 
 typedef struct { 
 PONT inicio; 
 PONT fim; 
 } FILA; 
 
 
Veja que na função inserirNaFila abaixo, um espaço é reservado na memória pelo malloc() para
armazenar o novo elemento e na função excluirDaFila, o espaço da memória é tornado disponível pela
função free().
 
 bool inserirNaFila(FILA* f,REGISTRO reg) { 
 PONT novo = (PONT) malloc(sizeof(ELEMENTO)); 
 novo->reg = reg; 
 novo->prox = NULL; 
 if (f->inicio==NULL){ 
 f->inicio = novo; 
 }else{ 
 f->fim->prox = novo; 
 } 
 f->fim = novo; 
 return true; 
 } 
 
 
 bool excluirDaFila(FILA* f, REGISTRO* reg) { 
 if (f->inicio==NULL){ 
 return false; 
 } 
 *reg = f->inicio->reg; 
 PONT apagar = f->inicio; 
 f->inicio = f->inicio->prox; 
 free(apagar); 
 if (f->inicio == NULL){ 
 f->fim = NULL; 
 } 
 return true; 
 } 
 
 
ÁRVORES BINÁRIAS E N-ÁRIAS
Sabe-se que uma árvore n-ária é uma árvore em que cada nó pode ter até n filhos. Portanto, trata-se de
uma generalização das árvores binárias, quando n = 2. Em árvores n-árias não há ordenação dos nós,
então para inserir um determinado elemento será necessário conhecer a subárvore em que será
inserido. Para cada nó, deve-se conhecer o primeiro filho e o próximo irmão. Pode-se usar a seguinte
definição para implementar árvores n-árias:
 
 typedef struct no { 
 int chave; 
 /*Aqui vão outros dados*/ 
 struct no *primFilho; 
 struct no *proxIrmao; 
 } NO; 
 
 typedef NO* PONT; 
 
 
Uma característica importante da árvore n-ária é que a mesma não é ordenada, ao contrário da árvore
binária de busca, por isso, uma forma de representá-la é usando uma estrutura em que há um ponteiro
para o primeiro filho e outro ponteiro para o próximo irmão. Em suma, na prática converte-se a árvore n-
ária para uma árvore binária: a conversão de árvore n-ária para binária é trivial; basta eleger um filho
qualquer como raiz da subárvore esquerda, e os demais como subárvore direita e seus descendentes.
Seguem as funções em C que implementam a busca de uma chave em uma árvore n-ária e a inserção
de um elemento em uma árvore n-ária. A função abaixo buscaChave retorna o endereço do NO que
contém a chave ch, ou NULL caso esta não seja encontrada. A função insere adiciona um nó com
chave novaChave, debaixo do nó com chave chavePai. Retorna true se conseguiu inserir, e false se o
pai não for encontrado.
 
 PONT buscaChave(int ch, PONT raiz){ 
 if (raiz == NULL) return NULL; 
 if (raiz->chave == ch) return raiz; 
 // busca nos filhos 
 PONT p = raiz->primFilho; 
 while(p) { 
 PONT resp = buscaChave(ch, p); 
 if (resp) return(resp); 
 p = p->proxIrmao; 
 } 
 return(NULL); 
 } 
 
 bool insere(PONT raiz, int novaChave, int chavePai){ 
 PONT pai = buscaChave(chavePai,raiz); 
 if (!pai) return(false); 
 PONT filho = criaNovoNo(novaChave); 
 // insere no pai 
 PONT p = pai->primFilho; 
 if (!p) pai->primFilho = filho; 
 else { 
 while (p->proxIrmao) p = p->proxIrmao; 
 p->proxIrmao = filho; 
 } 
 return(true); 
 } 
 
 
Conclusão
Os alunos alegaram dificuldades no entendimento dos slides apresentados nas videoaulas, que usavam
código pronto. Acredito que, com a apresentação do primeiro item deste texto (Comandos e funções
utilizadas para o desenvolvimento dos programas), uma introdução à linguagem de programação C,
apresentando as principais construções e dados utilizados, seguido dos outros itens solicitados pelos
alunos, ajudarão a melhorar o entendimento da matéria. Mas só com o estudo e compreensão em
programação e nas estruturas de dados, é que o aluno terá domínio e conhecimento. Isso se faz com a
implementação dos diversos códigos apresentados, resolução de exercícios e estudo do assunto por
meios dos textos disponibilizados pelo ambiente de aprendizagem.

Continue navegando