Estrutura de Dados
Estrutura de Dados
Estrutura de Dados
MANTENEDORA MANTIDA
Para Iniciar.........................................................................................5
Desafio...............................................................................................7
Desafio: Tipo Abstrato de Dados..................................................9
Agenda | Desafio: Tipo abstrato de dados....................................13
Para Concluir.....................................................................................147
PARA INICIAR
Olá, estudante!
A partir de agora, confira as estruturas básicas usadas em programação.
Iniciaremos conhecendo um pouco sobre linguagens de programação e como
elas se diferenciam.
Em seguida, para criar estas estruturas básicas, conheceremos a Linguagem
C, suas características e formas de construção de programas.
Uma característica comum em linguagens é o poder de trabalhar com
agrupamento de dados. Estudaremos essas formas de agrupamento, tais como
tipos homogêneos e tipos heterogêneos. Identificaremos como usá-los e
teremos a oportunidade de fazer uma aplicação prática envolvendo este aspecto
para que esses conceitos sejam bem fixados.
Serão apresentados os fundamentos de estruturas, como pilhas, filas e listas,
além de suas implementações estáticas e dinâmicas. Você também conhecerá
aspectos de uso de funções, como passagem de parâmetros por valor e
referência, e como isso acontece no uso das estruturas de dados.
Você também terá a oportunidade de se apropriar do conceito de Tipos
Abstratos de Dados e como implementá-los criando bibliotecas funcionais
e reutilizáveis. Para isso, usaremos a Linguagem C, porque ela trabalha
em um nível de abstração um pouco mais baixo que outras linguagens e
permite a manipulação de endereços por meio de ponteiros, o que ajudará na
compreensão do funcionamento das estruturas em geral.
Se você quiser construir códigos eficientes, atendendo aos requisitos dos
clientes, precisa conhecer mais a fundo o funcionamento das estruturas e
utilizá-las de forma a alcançar seus objetivos. Assim, você conseguirá avaliar o
desempenho de um algoritmo, o que irá auxiliar nos processos de escolha e de
produção de códigos melhores.
Vamos iniciar essa jornada?
5
DESAFIO
Confira, a seguir, uma situação prática relacionada aos conteúdos que você
estudará a partir de agora. Essa é uma importante etapa de seu processo
avaliativo. Então, dedique-se ao máximo e busque, em sua trajetória de estudos,
fundamentar os resultados esperados no desafio que está proposto a seguir.
Aproveite, pois essa é uma grande oportunidade de praticar novos conhecimentos!
7
Desafio: Tipo Abstrato de Dados
Resultado esperado
Arquivo zip contendo os arquivos produzidos em linguagem C, cumprindo os
aspectos do desafio e critérios de avaliação.
Desenvolvimento
Individual. Confirmar com professor tutor.
Critérios de avaliação
• Apresentar o cabeçalho do TAD denominado estrutura.h.
• Inserir a implementação do TAD com o código-fonte, denominado estrutura.c,
e o compilado, estrutura.o.
• Mostrar requisicao.h e requisicao.o.
• Expor que o código funciona, apresentando os dados corretos no teste
original proposto [teste.c].
• Desenvolver o código otimizado, consumindo o mínimo de memória (não há
variáveis desnecessárias, não estão sendo armazenados dados temporários
desnecessariamente?).
• Aplicar o código, otimizado no tempo de resposta de inserção e remoção (O
código está com a menor complexidade possível para o caso?).
• Evidenciar que o código atende a todos os requisitos demandados.
• Entregar atividade conforme prazo estabelecido.
Forma de entrega
Arquivo em formato.zip com o agrupamento dos arquivos produzidos. O arquivo
em .zip deverá conter seu nome. Exemplo: o aluno José Augusto Seabra cria o
arquivo JoseAugustoSeabra.zip, a ser entregue em ferramenta do Ambiente
Virtual de Aprendizagem (AVA).
13
ESTUDO E PRÁTICA I
LINGUAGENS E MÓDULOS
A tecnologia da informação está por toda parte. Essa é uma área que só cresce
em demanda e oportunidades. Basicamente essa área envolve um hardware, um
programa de computador e uma estrutura de dados de suporte. O que
tratamos aqui é um destes pilares: a estrutura dos dados.
E-book
15
Linguagens de programação
QUAIS LINGUAGENS DEVEMOS CONHECER?
Para saber programar, é preciso conhecer o que torna uma linguagem de
programação diferente da outra. Afinal, por que há tantas linguagens de
programação? É preciso conhecer mais de uma linguagem?
18 Linguagens de programação
• Aumenta a capacidade de expressar ideias;
• Aprimora a base para escolha de uma melhor linguagem;
• Melhora a capacidade de aprender novas linguagens;
• Melhora entendimento do significado da implementação;
• Melhora o uso das linguagens já conhecidas;
• Permite o avanço da computação.
CARACTERIZANDO LINGUAGENS
É possível agrupar linguagens conforme o seu domínio ou uso. Para Sebesta
(2012), existem os seguintes domínios: aplicações científicas, aplicações de
negócios, inteligência artificial, programação de sistemas operacionais ou
suporte e aplicações WEB. Além destes domínios, há também as aplicações de
lazer, como os jogos.
As linguagens são construídas para resolver uma dificuldade de uso ou
computacional qualquer. Sebesta (2012) cita três características que permitem
avaliar uma linguagem: sua legibilidade, sua facilidade de escrita e sua
confiabilidade. Para obter mais ou menos estas características, as linguagens
precisam dar mais ênfase em algumas de suas formas de codificação. Além
destes aspectos, Sebesta (2012) apresenta outros aspectos que influenciam
o modelo da linguagem, tais como a arquitetura dos computadores e a
metodologia de desenvolvimento.
Linguagens de programação 19
Outra forma de caracterizar linguagens é pelo seu paradigma. Conforme
Ferreira (2020), os paradigmas podem ser: imperativo, declarativo, estrutural,
procedimental, funcional e orientado a objetos. Apesar de as linguagens serem
caracterizadas pelo paradigma, na verdade o paradigma está relacionado à
forma de pensar do programador, à maneira como ele desenvolve suas soluções.
Ascencio e Campos (2012) observam que qualquer paradigma pode ser aplicado
em qualquer linguagem, até mesmo em Assembly, o que irá depender de como o
programador fez uso da linguagem.
20 Linguagens de programação
Programação linear
Linguagens que apresentam característica linear são aquelas que não
possuem estruturas de controle (for, while, ...). A maneira de criar repetições
no código é por meio de um comando do tipo “GO TO”. Seu código é uma
sequência de comandos ou operações que só alteram a sua sequência de
execução por uma chamada explícita para redirecionamento para uma
determinada linha do código. Observe um exemplo de código com uma
linguagem de alto nível antiga, o Basic.
Observe que, no Basic, cada linha é numerada, o que serve como um rótulo
para orientar o GOTO. O comando REM é comentário e sua linha é ignorada na
execução. Sua execução é sequencial e, conforme as condições na execução,
o comando GOTO faz o redirecionamento para determinada linha.
Linguagens de programação 21
À medida que o código cresce, fica bastante difícil compreendê-lo, acompanhar
as idas e vindas nas linhas. Esse tipo de linguagem não favorece a manutenção, e
o reaproveitamento de código para outros fins é praticamente impossível.
Curiosidade
Dado a característica de ir e vir no código, parecendo um
emaranhado, este tipo de programação era chamado
macarrão ou espaguete.
As linhas precisavam ser numeradas, pois a execução
ocorria na sequência numérica das linhas. As linhas eram numeradas de 10
em 10, pois, se fosse necessário incluir um código entre o já existente, seria
necessário renumerar todas as linhas posteriores. Como o salto já era feito
de 10 em 10, era possível incluir linhas com os valores dentro dessa faixa, a
exemplo do que foi feito com os comentários neste código.
Programação estruturada
Este tipo de programação se caracteriza pela organização do código em
blocos. Além dos blocos caracterizando condicionais e laços, existem os
procedimentos e as funções. Quando o bloco é uma função, podem ser passados
parâmetros que são usados no interior do bloco e, ao final de sua execução,
a função devolve um resultado. Quando o bloco é um procedimento, também
podem ser passados parâmetros, mas o procedimento não devolve resultado.
Confira um exemplo de código em uma linguagem estruturada de alto nível,
o Pascal.
22 Linguagens de programação
program example; //Declaração de início do programa
var //Declaração das variáveis globais
x:integer;
y:integer;
Linguagens de programação 23
begin //Início do bloco de código do procedimento troca
aux := v1;
v1 := v2;
v2 := aux;
end; //Fim do bloco de código do procedimento troca
begin //Início do bloco de código do programa principal
writeln(’Digite varios numeros para saber o fatorial’);
writeln(’Termine com -1’);
read(x);
while x > 0 do
24 Linguagens de programação
No bloco da função fat, percebe-se que o nome da função é usado para
receber o seu valor de retorno. Em Pascal, o nome da função é usado como
variável de retorno, ou seja, ao terminar a execução, o valor que estiver em fat
será o retorno da função.
Observe a diferença na chamada do procedimento e da função. Quando se
chama a função, ela deve ser usada para atribuir seu resultado em uma variável
ou servir de retorno para outra função. Neste caso, usa-se o seu resultado para
impressão através da função writeln. Na chamada do procedimento, não há
retorno. Logo, não há atribuições ou uso de resultado para algum outro fim.
Dica
Na declaração da função ou do procedimento, a
declaração das variáveis fica entre parênteses após seu
nome, e essas são chamadas parâmetros da função ou do
procedimento. Quando de sua chamada para execução, os
valores passados nos parâmetros são chamados argumentos.
Linguagens de programação 25
public String getTitular() {
//Declaração de método getter
return this.titular;
}
public void setTitular(String titular) {
//Declaração do setter
this.titular = titular;
}
public int getNumero() {
return numero;
}
public void setNumero(int numero) {
this.numero = numero;
}
public double getSaldo() {
return saldo;
}
public void sacar(int valor) {
//Método de manipulação de atributo seguro
if((saldo - valor) < 0) throw new
SaldoInsuficienteException(“Saldo insuficiente: “ + saldo);
saldo -= valor;
}
public void depositar(int valor) {
saldo += valor;
}
}
26 Linguagens de programação
Observando a classe, percebe-se que é um conjunto de código autocontido.
Seus dados (atributos) possuem funções de manipulação (métodos) para sua
manipulação, o que garante a segurança na operação dos atributos. Os atributos
normalmente não são acessados diretamente. Para acessá-los, são usados seus
métodos de acesso (getter e setter). Essa é apenas uma visão introdutória,
sendo que mais de detalhe serão apresentados juntamente com os tipos
abstratos de dados.
Dica
Em Java, os métodos são declarados sempre indicando
o tipo de retorno, por exemplo, public double getSaldo()
– double é o tipo de retorno. Mas, nem todos os métodos
possuem retorno. E, para representar isso, há, por exemplo,
public void setNumero(int numero) – ao usar void como tipo de
retorno, diz-se que não há retorno. Assim, se o tipo de retorno é void, há um
procedimento, e se há um tipo não void de retorno, esta é uma função.
FORMAS DE EXECUÇÃO
Existem basicamente três tipos de linguagem quanto à forma de execução:
interpretadas, híbridas e compiladas. Aho et al. (2008) explicam a diferença
entre as formas de execução. Acompanhe.
Processador de linguagem interpretada – conforme a figura, tem-se um
interpretador que realiza o processamento da linguagem. O interpretador
executa o programa-fonte sobre os dados fornecidos, gerando a saída. Um
exemplo de linguagem que funciona assim é o Python.
Linguagens de programação 27
Programa fonte
Interpretador Saída
Entrada
Figura 1 - Um interpretador
Fonte: Adaptado de Aho et al. (2008)
Programa fonte
Tradutor
28 Linguagens de programação
Programa-fonte
Compilador
Programa-objeto
Figura 3 - Um compilador
Fonte: Adaptado de Aho et al. (2008)
LINGUAGEM C
Conforme Kernighan e Ritchie (1989), a linguagem C é de uso geral e tipada,
possuindo como tipos fundamentais: o caractere, o inteiro e números de ponto
flutuante. Além dos tipos fundamentais, possui outros como ponteiros, vetores,
estruturas, entre outros.
Nestes estudos, a linguagem C será apresentada devido à sua característica
facilitada de manipulação da memória. Você conhecerá essa linguagem com
exemplos explicados. O primeiro exemplo apresenta a estrutura básica da
linguagem. Acompanhe.
Linguagens de programação 29
int main() {
//Declaração da função main que devolve um inteiro
como retorno e início do bloco de código
int a = 10; //Declaração da variável a inicializada com 10
int b = 20; //Declaração da variável b inicializada com 20
return a + b;
//retorno da função com a soma das variáveis a e b
} //Final do bloco da função
#include <stdio.h>
int main() {
printf(“%s\n”, “ola mUndo.”);
return 0;
}
30 Linguagens de programação
No local onde se usa a diretiva #include é onde será inserido o conteúdo do
arquivo de cabeçalho, no caso do exemplo, é o arquivo stdio.h da biblioteca
do C. Este módulo contém as funções de entrada e saída (input/output, ou
simplesmente I/O). O arquivo está cercado de <> para indicar que o compilador
deve procurar o arquivo no diretório padrão configurado pelo instalador do
compilador. Se o arquivo estiver em local diferente, o nome do arquivo deve ser
indicado entre aspas e não cercado de <>.
Outra diretiva de pré-processamento muito usada é a #define. Ela funciona
como um meio de buscar e substituir. Confira um exemplo.
#include <stdio.h>
#define LENGTH 5
int main() {
int i;
for(i = 0; i < LENGTH; i++) {
printf(“Passo %d\n”, i);
}
return 0;
}
Linguagens de programação 31
O compilador GCC
O compilador faz a tarefa de pegar um código-fonte e transformá-lo em um
programa-objeto executável por meio de etapas. Aho et al. (2008) comenta
sobre as quatro etapas do compilador, conforme apresentado na figura, a seguir.
Esqueleto do programa-fonte
Pré-processador
Programa-fonte
Compilador
Montador
32 Linguagens de programação
Pré-processador – realiza o tratamento prévio do código-fonte,
incluindo cabeçalhos, fazendo substituições com define ou mesmo
realizando macros.
Dica
Para usuários Windows, você pode aprender como
instalar o GCC em links que estão disponíveis no item
Quero Saber Mais.
A edição do código-fonte pode ser feita por qualquer editor de texto puro
(bloco de notas do Windows, por exemplo).
Linguagens de programação 33
Dica
Um excelente editor de código é o vscode, que você pode
acessar no item Quero Saber Mais.
$ gcc -E fonte.c
$ gcc -S fonte.c
$ gcc fonte.c
34 Linguagens de programação
Executando sem opções, o compilador executa todas as etapas gerando
na saída o arquivo executável. O arquivo de saída terá o nome a.out – se o
ambiente for Linux/Unix, ou a.exe – se o ambiente for Windows.
Mão na massa
Linguagens de programação 35
REFERÊNCIAS
39
Representação dos dados
INTRODUÇÃO
Um computador é uma máquina que pode fazer praticamente qualquer coisa
que se deseja, mas, para isso, é necessário criar representações (abstrações)
das entidades do mundo real para uma forma computacional viável. Conforme
Moraes (2001), há limitações sobre o que o computador pode representar. E, por
isso, é preciso criar essas abstrações, para simplificar estes elementos em suas
partes essenciais.
int variavel;
int *ponteiro;
Referência e derreferência
Para manipular a memória, é preciso conhecer algumas funções e operadores.
Conforme Reese (2013), o operador de endereço & é usado para obter o
endereço de seu operando. Por exemplo:
int valor = 0; //declaração da variável valor de tipo simples inteiro, inicializada com 0
#100100 0
#100104 #100100
int valor = 20; //declaração da variável valor de tipo simples inteiro, inicializada com 0
Observe que neste exemplo foi usado para imprimir a variável *p, e o valor
retornado é o da variável valor. Isso ocorre porque p aponta para o endereço de
valor e, com o uso do operador de indireção, recupera-se o valor que está nesse
endereço. Veja um exemplo mais completo.
int main() {
printf(“a = %d\n”, *p); //Ao imprimir, usamos a indireção do ponteiro p (o valor de a, que é 10)
printf(“a = %d\n”, a); //Ao imprimir, usamos a variável a (que agora é 20)
return 0;
O que se fez aqui foi usar um endereço já alocado para atribuir para o
ponteiro, neste caso, o endereço da variável a. Mas, é possível alocar um espaço
para ser usado pelo ponteiro. Para isso, são usadas as funções malloc ou calloc.
A função malloc simplesmente aloca o espaço solicitado, e a função calloc,
além de alocar o espaço, zera seu conteúdo. Como a inicialização da variável
normalmente não é necessária (ela será feita em outro momento), usa-se a
função malloc.
Mão na massa
int main() {
int *p; //Declarando o ponteiro p – neste momento o endereço que ele contém é um lixo
if(p == null) return 1; //Se a alocação não for possível (sem memória livre), o resultado
da alocação é null
printf(“a = %d, *p = %d\n”, a, *p); //Nessa impressão, a e *p possuem o mesmo valor, mas
estão em endereços distintos
printf(“a = %d, *p = %d\n”, a, *p); //Nessa impressão vemos que os endereços são diferentes,
e os valores mudaram
free(p);
return 0;
#include <stdio.h>
void adiciona1PorValor(int valor) {
valor++;
printf(“Argumento valor = %d\n”, valor);
}
void adiciona1PorReferencia(int *valor) {
(*valor)++;
printf(“Argumento valor = %d\n”, *valor);
}
int main() {
int valor_original = 100;
adiciona1PorValor(valor_original);
printf(“Valor_original = %d\n\n”, valor_original);
adiciona1PorReferencia(&valor_original);
printf(“Valor_original = %d\n”, valor_original);
return 0;
}
Tipos homogêneos
Tipos homogêneos (arrays) são a representação de locais contíguos de
memória, permitindo definir uma determinada quantidade de valores de mesmo
tipo. Backes (2013) descreve array como “um conjunto de variáveis de um
mesmo tipo, com a vantagem de estarem todas associadas ao mesmo nome e
igualmente acessíveis por um índice.”
Deitel e Deitel (2011) explicam que, em linguagem C, um array possui um
nome, e para acessar um de seus elementos usa-se o operador [] (abre e fecha
colchetes). O seu primeiro elemento possui índice 0 (zero). Deitel e Deitel (2011)
explicam que o índice que fica entre os colchetes é chamado formalmente de
subscrito. Confira uma declaração de um vetor em C.
int vetor[20];
#include <stdio.h>
#include <stdlib.h>
int main() {
int vetor[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *ponteiro = (int *) malloc(sizeof(int) * 10);
int i;
for(i = 0; i < 10; i++) {
ponteiro[i] = vetor[i] * 10;
printf(“vetor[%d] = %d, ponteiro[%d] = %d\n”, i, vetor[i], i,
ponteiro[i]);
}
free(ponteiro);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int vetor[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *ponteiro = (int *) malloc(sizeof(int) * 10);
int i;
for(i = 0; i < 10; i++) {
*ponteiro = vetor[i] * 10;
printf(“vetor[%d] = %d, ponteiro[%d] = %d\n”, i, vetor[i], i,
*ponteiro);
ponteiro++;
}
ponteiro -= 10;
free(ponteiro);
return 0;
}
Neste exemplo, ao invés de usar o operador [], usou-se a indireção para obter
o valor para onde o ponteiro aponta e para atribuir o valor para onde o ponteiro
aponta. O resultado obtido é o mesmo. Foi usada a aritmética de ponteiros para
ir avançando para a posição da memória que se quer armazenar os valores. Para
poder liberar a memória de ponteiro, é preciso retornar à sua posição inicial (
ponteiro -= 10; ).
Mão na massa
Curiosidade
Dado a característica dos arrays de serem alocações
de memória contígua, eles são as estruturas mais
performáticas de busca de posição, pois isso ocorre por
um simples cálculo aritmético, indo direto ao endereço
para recuperar o valor desejado daquela posição.
#include <stdio.h>
#include <strings.h> //inclusão para poder usar strcpy
int main() {
char nome_1[] = “Alberto Roberto”;
char nome_2[20];
strcpy(nome_2, nome_1);
printf(“%s \n%s\n”, nome_1, nome_2);
strcpy(nome_2, “Pedro”);
printf(“%s \n%s\n”, nome_1, nome_2);
return 0;
}
Mão na massa
Tipos heterogêneos
Nem sempre os dados que se quer relacionar são de mesmo tipo, como
observado em tipos homogêneos. Por exemplo, é possível querer relacionar
os dados de uma data com um inteiro para o dia, o mês, como uma string e
outro inteiro para o ano (13, março, 1930). Um vetor não poderia manter estes
três valores. Para isso, são necessários tipos heterogêneos. Para criar um tipo
heterogêneo em C, tem-se o struct.
Conforme Kernighan e Ritchie (1989), um struct é uma coleção de variáveis
que podem ser de tipos diferentes. Para armazenar, por exemplo, o nome, CPF e
salário em uma variável, uma solução é o uso de um struct. Veja um exemplo de
uso de struct.
Neste exemplo, foi criada uma struct empregado. Para usar esta estrutura,
é preciso definir uma variável. Aqui foi usada a variável e, e seu tipo será
definido com a palavra struct, seguida do nome da estrutura criada no início do
programa. Depois de declarada a variável, ela já pode ser usada (o compilador já
aloca espaço para a estrutura). Para acessar cada um dos membros da estrutura,
é usado o operador ponto (variável.membro).
Mão na massa
Use este código como exemplo para criar outras
estruturas, manipule-as e confira o resultado para ir se
acostumando com este tipo de variável.
#include <stdio.h>
#include “empregado.h”
int main() {
struct empregado *e = get_empregado();
set_nome(e, “Jurandir Matias de Souza Prado”);
set_cpf(e, 98765432100);
set_salario(e, 3500);
printf(“%s\nCPF %.0f\nSalario: R$ %.2f\n”, get_nome(e), get_
cpf(e), get_salario(e));
}
Observe que, para quem usa o TAD, o que ele precisa é incluir o cabeçalho do
TAD e usar as funções que ele fornece.
A compilação do TAD deve ser feita da seguinte forma:
Mão na massa
Execute esse processo criando os arquivos
apresentados, realizando o processo de compilação.
Teste e confira o resultado.
O compilador C
https://player.vimeo.com/
video/800842054?h=e374cd4ff2
https://player.vimeo.com/
video/800842078?h=bf180b0596
https://player.vimeo.com/
video/800842115?h=36a17804f5
67
Manipulando memória com C
https://player.vimeo.com/
video/800842137?h=fd7c62d960
https://player.vimeo.com/
video/800842154?h=3146a7537c
Criando bibliotecas
https://player.vimeo.com/
video/800842174?h=ced8cf256b
68
Infocast
69
Tipos simples e ponteiros
Olá, estudante!
Confira a imagem que irá te auxiliar nesse podcast.
#include <stdlib.h>
int main() {
int var1;
int var2[2];
int *var3;
int *var4;
var1 = 10;
var3 = &var1;
*var3 = 20;
var3 = (int *) malloc(sizeof(int));
*var3 = 50;
var2[0] = 1;
var2[1] = 2;
var4 = var2;
*var4 = 11;
var4[1] = 22;
var4 = (int *) malloc(sizeof(int) * 2);
*var4 = 111;
var4++;
*var4 = 222;
}
#include <stdlib.h>
#include <strings.h>
int main() {
struct empregado {
char nome[50];
int tempo_servico_anos;
double salario_base;
};
#include <stdio.h>
printf(“%s”, variavel);
printf(“%20s”, variavel);
printf(“%-20s”, variavel);
printf(“%d”, variavel);
printf(“%20d”, variavel);
printf(“%-20d”, variavel);
printf(“%020d”, variavel);
printf(“%f”, variavel);
printf(“%10.2f”, variavel);
printf(“%-20s: %15.2f\n”, titulo, valor);
#include <strings.h>
strcpy(destino, origem);
strncpy(destino, origem, n);
strcat(destino, origem);
strncat(destino, origem, n);
strlen(str);
strcmp(str1, str2);
Compiladores
https://johnidm.gitbooks.io/compiladores-para-humanos/
content/
Biblioteca stdio
https://petbcc.ufscar.br/stdio/
83
Biblioteca stdlib
https://petbcc.ufscar.br/stdlib/
https://code.visualstudio.com/
Instalação GCC
https://www.mundodoshackers.com.br/como-instalar-o-gcc-
no-windows
84
Tutorial instalação GCC
https://www.youtube.com/watch?v=FzPBZjkoEmA
85
Resumindo
Criei programas que usam tipos homogêneos por declaração e por ponteiros.
87
ESTUDO E PRÁTICA II
ESTRUTURAS BÁSICAS
E-book
89
Estruturas estáticas básicas
ESTRUTURAS ESTÁTICAS BÁSICAS: CONCEITOS INICIAIS
Um dos elementos fundamentais em programas são as estruturas de dados.
Um programa essencialmente manipula dados para gerar os resultados
desejados. E, para isso, precisa contar com estruturas que possam permitir
ou favorecer essa manipulação. Conheça agora três estruturas básicas ou
fundamentais que são aplicadas em programação: a pilha, muito usada em
sistemas operacionais e compiladores; a fila, também usada em sistemas
operacionais e nos mais diversos problemas do dia a dia; e, finalmente, a lista,
que serve de infraestrutura para diversas outras estruturas de dados.
Nas aplicações usadas no dia a dia, são empregadas algumas estruturas de
dados para viabilizar seu tratamento e geração das informações pretendidas
com a aplicação.
Confira, a seguir, as estruturas fundamentais mais usadas em aplicações e sua
implementação de forma estática, que é sua forma de uso mais simples.
ANÁLISE ALGORÍTMICA
A análise algorítmica é uma forma de avaliar o desempenho de determinado
algoritmo. Isso permite que possamos fazer escolhas para definir a melhor forma
de resolver um problema. Isso é comumente feito pelas análises do melhor
caso, pior caso ou caso médio. Acompanhe aqui a análise dos piores casos.
Conforme Loudon (2000), a análise dos piores casos é a mais usada e também é
conhecida como notação-O (diz-se big O). Loudon (2000) define complexidade
computacional como “[...] a taxa de crescimento dos recursos (normalmente
tempo) que um algoritmo necessita no que diz respeito ao tamanho dos dados
que processa”. Com essa definição, o autor cita que “[...] a notação-O é uma
expressão formal de complexidade de um algoritmo.”
O(1) - Constante
O(log n) - Logarítmica
O(n) - Linear
O(n log n) - n log n
O(n2) - Quadrática
O(n3) - Cúbica
Com isso, você já pode usar essa notação para avaliar a complexidade de
algoritmos.
Pilha ou Stack
Uma das estruturas mais simples é a pilha ou Stack. Conforme Moraes (2001),
a pilha “[...] é uma estrutura linear na qual todos os acessos (inserção, remoção
ou consultas) são realizados em uma só extremidade, denominada TOPO”.
Remoções Inserções
Topo
Base
Figura 1 - Pilha
Fonte: Adaptado de Moraes (2001)
Você pode observar na primeira linha a definição parcial do tipo, para que
o nome Stack possa ser usado pelo usuário da biblioteca na declaração de
uma pilha.
Fila ou Queue
É difícil imaginar um contexto que não tenha algum tipo de fila sendo usado.
Há filas para atendimento de pessoas, filas para execução de rotinas, filas de
impressão, enfim, há filas para as mais diversas utilidades. A fila é muito útil
pela sua característica de justiça. Quem chega primeiro é atendido primeiro.
Moraes (2001) define fila como “[...] uma lista linear na qual todas as inserções
são realizadas em uma das extremidades (fim da fila). E além disso, todas as
remoções são realizadas em outra extremidade (início da fila)”. Dado essas
características, o autor cita que essas estruturas são chamadas FIFO (First In
First Out), ou seja, o primeiro que entrar será o primeiro a sair.
Remoções Inserções
Início Fim
Figura 4 - Fila
Fonte: Do autor (2023)
Remover
0 1 2 3 4 5 6 7 8 9 10
Se esse deslocamento não for feito, ficarão vazios no início, o que impediria
a entrada de novo elemento no fim quando todos os espaços do final forem
preenchidos, mesmo havendo espaços vagos no início. Assim, temos a inserção
com complexidade O(1) e a remoção com complexidade O(n). As funções peek e
size também têm complexidade O(1).
Na criação do tipo abstrato de dados para a fila, tem-se a implementação e o arquivo
de cabeçalho. Confira, agora, o arquivo de cabeçalho de uma fila de inteiros:
Observe, na primeira linha, a definição parcial do tipo, para que possa ser
usado pelo usuário da biblioteca. A seguir, tem-se a função que cria uma fila
vazia, devolvendo-a como retorno. Essa função recebe como parâmetro o
tamanho da fila a criar. Se a função apresentar erro, o ponteiro devolvido será
NULL. Sua complexidade é O(1).
Mão na massa
Dada a descrição do arquivo de cabeçalho apresentado,
faça a implementação da fila.
Lista
Certamente essa é a estrutura mais versátil dentre as que já foram
apresentadas até aqui. Podemos ter inserção, consulta e remoção pelas
extremidades ou em qualquer posição válida. Da mesma forma que na fila, não
podemos deixar espaços livres. Então, a lista deve estar preenchida com seus
espaços em uma das extremidades.
Figura 3 - Lista
Fonte: Do autor (2023)
Mão na massa
Dada a descrição do arquivo de cabeçalho apresentado,
faça a implementação da lista. Na galeria de vídeos,
o vídeo: Lista baseada em vetores pode te ajudar.
109
Estruturas básicas usando
listas encadeadas
LISTAS ENCADEADAS : CONCEITOS INICIAIS
Listas encadeadas, ou listas dinâmicas, são estruturas que podem crescer
indefinidamente. Elas podem ser aplicadas como base para as estruturas
básicas, como filas e pilhas, não necessitando delimitar um tamanho máximo
para estas estruturas.
Loudon (2001) cita uma das mais importantes estruturas de dados, a lista
encadeada. O autor aponta que servem a um propósito similar ao de arrays,
agrupar dados, mas oferecem algumas vantagens sobre arrays. Ela é uma
resposta aos problemas que temos com a implementação de listas com arrays,
pois permite que a estrutura cresça na medida que for necessário e só ocupam
o espaço de memória para elementos inseridos. A forma de trabalhar é diferente
da que usamos com vetores, o que apresenta vantagens e desvantagens,
conforme você poderá observar aqui.
LISTA ENCADEADA
Para alocar espaço para um vetor, o compilador precisa buscar um espaço
de memória grande o suficiente para abrigar o tamanho todo do vetor.
Nodo simples
O nodo simples é uma estrutura que tem suporte para guardar a informação a
ser armazenada e um ponteiro que aponta para o próximo elemento.
Próximo
Info
Mão na massa
Nodo duplo
Em alguns casos, pode não ser suficiente conhecer quem é o próximo
elemento. Poderá ser preciso saber também quem é o anterior. Neste caso, são
necessários dois ponteiros: um para o próximo e um para o anterior.
Anterior Próximo
Info
Mão na massa
Dica
Um nodo duplo, quando criado, também deve ter sua
informação setada. Tanto o próximo quanto o anterior
devem apontar para NULL, para que um novo nodo
possa ser colocado em qualquer extremidade.
Início Fim
0 1 2 3
Podemos observar que, para inserir no final, basta setar o ponteiro próximo
do nodo da posição 3 para o novo nodo, ou seja, a complexidade é O(1). Outras
funções, no entanto, terão complexidade O(n), pois será necessário navegar
pelos elementos até chegar na posição de inserção desejada. A navegação nessa
lista depende de um ponteiro para seu início e seguirá, de nodo em nodo, por
meio dos ponteiros próximo. A definição da estrutura para essa lista seria:
struct List_ {
int size;
Node *Head;
};
Início Fim
0 1 2 3
Figura 5 - Lista encadeada de nodo duplo
Fonte: Do autor (2023)
struct DList_ {
int size;
DNode *Head;
DNode *Tail;
};
Pilha
A pilha é uma estrutura que possui funções de baixa complexidade, O(1),
quando se usa vetores em sua implementação. A única razão para avaliarmos
o uso de listas encadeadas como suporte para a pilha é pela possibilidade de
redução de uso de memória quando essa pilha é necessária para um grande
número de elementos. No caso do vetor, a pilha já inicia, mesmo vazia,
consumindo todo o espaço de memória, como se estivesse cheia.
Criando um tipo abstrato de dados para a pilha que usa lista encadeada de
nodo simples, precisamos ter a implementação e o arquivo de cabeçalho dessa
estrutura. O arquivo de cabeçalho ficará quase igual ao da pilha implementada
sobre o vetor, o que terá de diferença é a não existência da função full, pois essa
pilha não precisa ter limite de tamanho. E, na função create, não é necessário
parâmetro de tamanho da pilha. A função destroy terá sua complexidade
aumentada, como você perceberá a seguir. Observe o arquivo de cabeçalho de
uma pilha de inteiros:
• A função peek irá usar o campo top da estrutura da pilha, que aponta
para o topo da pilha, tendo também complexidade O(1).
• A função push irá setar o próximo do novo nodo para top e irá definir
top para o novo nodo. Com isso, tem-se complexidade O(1).
• A função pop irá guardar o top em um ponteiro, que será o elemento
retirado, e setar o top para o seu próximo. Com isso, complexidade é O(1).
• E, finalmente, a função size somente irá ler o campo size da pilha,
tendo também complexidade O(1).
Mão na massa
Fila
A fila baseada em vetor é uma estrutura que possui a função de inserção e
consulta com complexidade O(1), mas a de remoção como O(n). Uma estratégia de
implementação de fila com nodo simples pode ser observada na figura, a seguir.
Início Fim
Mão na massa
Início Fim
0 1 2 3
Ao criar um tipo abstrato de dados para a lista que usa encadeamento de nodo
duplo, precisamos ter a implementação e o arquivo de cabeçalho dessa estrutura.
O arquivo de cabeçalho ficará igual ao da lista implementada sobre o vetor, o que
terá de diferença é a não existência da função full, pois essa lista não precisa ter
limite de tamanho e na função create não é necessário parâmetro de tamanho da
lista. Observe o arquivo de cabeçalho de uma lista de inteiros:
Dependendo das funções que mais são usadas na lista, conforme o problema
que se quer resolver, uma será melhor do que a outra, com base nas complexidades
apresentadas. Também devemos considerar o fato de a lista baseada em vetor
ocupar o espaço máximo de memória, mesmo quando está vazia.
Mão na massa
https://player.vimeo.com/
video/800842192?h=7b69f2932a
https://player.vimeo.com/
video/800842248?h=f4dd04fda3
https://player.vimeo.com/
video/800842232?h=805afefcce
129
Infocast
131
Pilha sob vetor
Olá, estudante!
Confira a imagem que irá te auxiliar nesse podcast.
#include <stdlib.h>
#include “pilhaVetor.h”
struct Stack_ {
int *elements;
int length;
int size;
};
Stack* create(int length) {
Stack *stack = (Stack *) malloc(sizeof(Stack));
stack->elements = (int *) malloc(sizeof(int) * length);
stack->length = length;
stack->size = 0;
return stack;
}
int top(Stack *stack, int *value) {
if(stack->size == 0) return 1;
*value = stack->elements[stack->size - 1];
return 0;
}
int push(Stack *stack, int value) {
if(stack->size == stack->length) return 1;
stack->elements[stack->size++] = value;
return 0;
}
#include <stdio.h>
int main() {
int temp;
int state;
Stack *stack = create(5);
for(int i = 10; i < 80; i+=10) {
state = push(stack, i);
if(state == 0) {
top(stack, &temp);
printf(“Topo = %d\n”, temp);
} else {
printf(“Pilhas cheia\n”);
}
}
Stack* create() {
Stack *stack = (Stack *) malloc(sizeof(Stack));
stack->top = NULL;
stack->size = 0;
return stack;
}
int top(Stack *stack, int *value) {
if(stack->size == 0) return 1;
*value = get_info(stack->top);
return 0;
}
int push(Stack *stack, int value) {
Node *node = create_node(value);
set_next(node, stack->top);
stack->top = node;
stack->size++;
return 0;
}
int pop(Stack *stack, int *value) {
if(stack->size == 0) return 1;
top(stack, value);
Node *temp = stack->top;
stack->top = get_next(temp);
free(temp);
stack->size--;
return 0;
}
#include <stdio.h>
int main() {
int temp;
int state;
Stack *stack = create(5);
for(int i = 10; i < 80; i+=10) {
state = push(stack, i);
if(state == 0) {
top(stack, &temp);
printf(“Topo = %d\n”, temp);
} else {
printf(“Pilhas cheia\n”);
}
Linguagem C
https://dev.to/nfo94/resumo-basico-da-linguagem-c-
para-logica-de-programacao-23o4
https://www.huicode.com.br/p/exercicios-resolvidos-de-
linguagem-c.html
143
Resposta de exercícios com vários códigos em C
https://github.com/misaelrezende/Exercicios-do-Livro-
Linguagem-C-Completa-e-Descomplicada
144
Resumindo
145
PARA CONCLUIR
147
Estruturas Fundamentais
Análise de algoritmos:
Estudo do desempenho de algoritmos. Há
várias formas de avaliar um algoritmo,
tipicamente pelas análises de melhor caso,
pior caso e caso médio. A mais usada e
apresentada nesta unidade é a análise de
pior caso.
Biblioteca:
Conjunto de tipos e funções
relacionadas para um determinado
fim. Vimos aqui bibliotecas de entrada
e saída (stdio), de manipulação de
memória (stdlib) e também
construímos bibliotecas para TADs.
Estrutura dinâmica:
São aquelas em que o tamanho varia
conforme são inseridos ou removidos
os dados.
Estrutura estática:
São aquelas que possuem um tamanho
definido, independentemente da
quantidade de dados que estejam
presentes em sua estrutura.
Fila:
Estrutura de dados onde a entrada é
realizada em seu final e a saída em seu
início, também chamada FIFO - First In -
149 149
First Out.
Estruturas Fundamentais
Linguagem de programação:
É uma forma de expressar um processo ou
sequência de passos, possuindo regras
formais para isso.
Lista:
Estrutura de dados mais versátil.
Permite entrada, saída e
consulta em qualquer posição da
estrutura.
Nodos:
São estruturas que permitem
armazenar os dados e um ou
dois endereços, que
correspondem aos endereços de
seus vizinhos.
Pilha:
Estrutura de dados em que suas operações
de inserção e remoção ocorrem pela mesma
extremidade, chamada topo.
Tipos heterogêneos:
São estruturas em que os seus dados
armazenados podem ser de tipos
=
diferentes.
Tipos homogêneos:
São estruturas em que os seus dados
armazenados são de mesmo tipo.
=
151
REFERÊNCIAS
152