0% acharam este documento útil (0 voto)
14 visualizações202 páginas

Estrutura de Dados

O documento aborda conceitos fundamentais de Estruturas de Dados e Algoritmos, incluindo Tipos Abstratos de Dados (TADs), filas, pilhas, listas e funções em C++. Ele explora a importância da modularização e a criação de funções personalizadas, além de discutir métodos de pesquisa e ordenação. O texto também destaca a diferença entre passagem por valor e por referência em funções, enfatizando a relevância dessas estruturas para o desenvolvimento de software.

Enviado por

thiago henrique
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato DOCX, PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
14 visualizações202 páginas

Estrutura de Dados

O documento aborda conceitos fundamentais de Estruturas de Dados e Algoritmos, incluindo Tipos Abstratos de Dados (TADs), filas, pilhas, listas e funções em C++. Ele explora a importância da modularização e a criação de funções personalizadas, além de discutir métodos de pesquisa e ordenação. O texto também destaca a diferença entre passagem por valor e por referência em funções, enfatizando a relevância dessas estruturas para o desenvolvimento de software.

Enviado por

thiago henrique
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato DOCX, PDF, TXT ou leia on-line no Scribd

Aula 1

1 Tema: Da nascente do Rio Ailã no Monte Caburaí (RR) ao Arroio Chuí(RS) -


uma viagem para conhecer as Estruturas de Dados e outros tópicos

Agora, chegou sua vez de criar Tipos Abstratos de Dados (TADs), listar suas operações e
implementá-las na linguagem C++ para torná-los "concretos".

O conteúdo é muito extenso e os conceitos muito abstratos. Sendo assim, o nosso maior
desafio será: como tornar essa disciplina amigável e apaixonante porque Algoritmos e Estruturas de
Dados são elementos indispensáveis para você se tornar um desenvolvedor.

2 Viajando para se familiarizar com os termos

"Estrutura de Dados é a organização e a representação das informações, entre outras, na forma


de pilhas, filas, árvores, listas ligadas e vetores, geralmente na memória do computador para obter a
devida abstração de um problema real e a melhor eficiência na execução dos algoritmos cujas
operações atuam sobre essas estruturas de dados." (MORAES. C.R., 2001, p. 9)

Grafo

O grafo é uma Estrutura de Dados não linear. Podemos defini-la como sendo um conjunto de
nós (vértices) e suas conexões (arcos) entre eles. Além disso, não existe limitações para os vértices.

Embora não seja objeto de estudo dessa disciplina, precisamos saber que são usados em
muitas aplicações como as relacionadas abaixo:

 Redes de computadores;
 Computação Gráfica;
 Diagrama de Entidade Relacionamento (E-R);
 Modelagem de Circuitos Digitais, entre outras.
No dia da viagem, eles estavam tão ansiosos que chegaram bem cedo ao aeroporto Tom Jobim
no Rio de Janeiro e, quando o check in abriu, foram os primeiros da fila.

A fila é uma estrutura de dados muito usada em computação. A Fila segue a regra chamada:
FIFO - first in, first out (o primeiro elemento que entra é o primeiro que sai).

A inserção de um elemento na Fila acontece sempre ao final.

A remoção de um elemento da Fila acontece sempre no início.

Não é difícil de fazer uma analogia com estrutura da fila e as filas que enfrentamos no nosso
dia-a-dia: a fila do banco, a fila para comprar um ingresso no cinema, etc.

Se pensarmos em termos de um Sistema Operacional para gerenciar a impressão de vários


documentos, também fica fácil entendermos a filosofia da estrutura da fila: o primeiro documento a
chegar é o primeiro a ser impresso se ignorarmos prioridade.

Pesquisar
No nosso curso, além de estudarmos as Estruturas de Dados, teremos outros tópicos
necessários para que possamos implementar TDAs (TADs).

Estudaremos dois métodos de pesquisa: sequencial e binária. A pesquisa sequencial é indicada


para listas que não estão ordenadas enquanto que a pesquisa binária, mais rápida, é perfeita para listas
ordenadas.

Lista

A lista era ordenada por sobrenome senão a pesquisa seria muito demorada. Uma lista não
precisa ser ordenada, depende da aplicação. Quando você vai ao mercado, você faz uma lista do que
precisa comprar que não está em ordem alfabética. A lista é uma das Estruturas de Dados mais simples
para agrupar dados, formando um conjunto com elementos do mesmo tipo e preservando a relação de
ordem linear entre os elementos. Os elementos de uma lista são chamados de nós ou nodos e
operações poderão ser feitas com as listas.

As formas de inserção e remoção dos elementos da lista é que definirão os tipos de listas
(pilha, fila).

Tipos de Listas lineares

Sequencial - os dados são armazenados na Memória Principal de forma contigua. Como


exemplo: as matrizes.

Encadeada - os dados vão sendo armazenados na memória em posições não adjacentes (não
contíguas). Como é isso? Só quando estudarmos Alocação Dinâmica na Memória.

Ordenação

Classificar os dados é uma necessidade na maioria dos programas. Teremos a oportunidade de


conhecer alguns métodos de ordenação e compará-los.
Depois que o check in foi feito, as malas foram para a esteira.

E foram sendo empilhadas à medida que iam chegando.

Pilha

A pilha é um tipo de Estrutura de Dados onde a inserção de um elemento sempre acontece no


topo da pilha e a remoção, também.

Pelo fato da inserção e remoção acontecerem na mesma extremidade da pilha, seu método de
acesso é do tipo LIFO - lost in, first out (o último elemento que entra é o primeiro que sai).

Esta estrutura é muito usada pelo Sistema Operacional nas chamadas de funções onde o
endereço de retomo é empilhado.

Struct

Podemos definir um struct como sendo um conjunto de elementos geralmente agrupados sob
uma lógica e associados por um nome. Esses elementos podem ser variáveis simples, matrizes, outras
estruturas e até funções.

Por essa definição, podemos concluir que um struct pode ser formado por elementos de tipos
diferentes. Cada elemento da estrutura é chamado de membro ou campo.

Observe a ficha que o Sr. Augusto deu para todos preencherem. Ela tem 8 membros dos quais
5 são do tipo vetor de char e três do tipo int (telefones e idade) ou então 7 do tipo vetor de char porque
se colocarmos o ddd, o número ficará muito grande para ser armazenado em uma variável do tipo int.
De qualquer maneira, estamos criando um novo tipo de "variável", visto que ela tem membros
com tipos diferentes. Como acessar esses membros? Só lendo a aula de struct, é claro!

Função
Na disciplina de Algoritmos, construímos programas só com uma função. Nesse período,
aprenderemos a dividir o programa em partes menores com funções muito específicas.

Uma função é um bloco contendo cabeçalho, início e fim. Ela é identificada por um nome que
deverá seguir as regras usadas para se nomear variáveis.

A função serve para executar tarefas menores como ler, calcular, determinar o maior/menor
valor entre uma lista de valores, ordenar, converter para maiúsculas, entre outras. Após executar estas
tarefas menores, a função retoma, ou não, um determinado valor para a função chamadora.

A lista encadeada, também conhecida como lista ligada, se caracteriza por não ter seus nós,
obrigatoriamente, alocados de forma contígua.

Como não existe garantia de uma alocação contigua, cada nó da lista leva o endereço do
próximo nó para não perder o encadeamento.

O estudo de ponteiros é fundamental para listas encadeadas.

Representamos a lista por um ponteiro para o primeiro nó e, do primeiro ao último, segue-se o


encadeamento através dos ponteiros presentes em cada nó.

O último elemento da lista aponta para NULL.


AULA 2 – FUNÇÕES

1 Libere sua imaginação. Construa suas funções.

Todo programa codificado em C++ é formado por uma ou mais funções, mas a única
obrigatória é a função main. Na disciplina de Algoritmos, construímos programas com uma só função
e você deve se lembrar do tamanho deles no início. Apesar de pequenos, mesmo assim, era difícil
depurar os erros.

Embora tenhamos continuado com uma única função, nossos programas foram aumentando à
medida que incorporamos novas instruções e identificar e corrigir erros para que fosse possível a
compilação, tomou-se uma tarefa muito trabalhosa. Nesta aula, iremos entrar em contato com o
conceito de modularização e como consequência, aprenderemos a construir funções.

2 Processando Informações

Modularização

O grau de complexidade que alcança alguns programas nos leva a dividi-lo em módulos para
que possamos melhorar o entendimento do programa, depurar os erros com mais facilidade,
simplificar a manutenção porque podemos trocar somente um determinado módulo, reutilizar o código
no mesmo programa ou em outro programa, etc. Esses módulos são "pequenos programas" com
tarefas bem definidas. São conhecidos em algumas linguagens por procedimentos e, na linguagem C+
+, por funções. Chamamos essa técnica que decompõe um programa em partes menores de
modularização.

Função

Por que criar funções se a linguagem C++ apresenta um conjunto razoável de funções pré-
definidas?
Porque, por maior que seja esse conjunto, nunca atenderá, totalmente, às necessidades de
todos os programas.

Conceito de Funções

Uma função é um conjunto de comandos limitado por um par de chaves e precedido por um
cabeçalho.

Na construção de uma função, todas as estruturas que você estudou na disciplina de


Algoritmos poderão ser usadas no corpo da função, mas uma função é independente da outra e, por
essa razão, jamais poderá ser criada dentro de outra função.

Definição da função:

Entendemos que a criação de uma função é resultado de uma necessidade que você constatou
e da ausência de uma função pré-definida.

Alguns exemplos de funções que você poderá criar:


Função que calcule a média aritmética;

Função que reajusta salário;

Função que exibe um menu;

Função que gera um outro vetor;

Função que ordena um vetor;

Função que remove elementos de uma fila.

O Protótipo de uma Função

O protótipo de uma função contém o tipo de retomo da função, o nome da função, seguido de
um par de parênteses que podem ou não ter parâmetros. É finalizado, obrigatoriamente, com o ;(ponto-
e-vírgula).

Em outras palavras, o protótipo de uma função é o cabeçalho da função com ;(ponto-e-virgula)


ao final.

<tipo de função> nome_da_função (declarações dos parâmetros);

Localização das Funções

As funções poderão ser colocadas antes, ou depois, da main.

Antes: Se colocadas antes da main, elas serão "conhecidas" pelo compilador antes de serem
chamadas, evitando maiores problemas, mas, à medida que o número de funções cresce, diminui a
legibilidade do programa.

Depois: Se colocadas depois da main, aumentamos a legibilidade, mas criamos um problema:


função é chamada antes da identificação dela pelo compilador. Esse contratempo foi resolvido de uma
forma muito simples: os protótipos das funções serão localizados antes da main e as definições, depois
da main. Veja um exemplo na seguinte figura.
Atenção

1 - Não confunda. Definição é o cabeçalho e o corpo enquanto que protótipo é o cabeçalho


com ponto e vírgula ao final, podendo até se apresentar de forma simplificada como veremos mais
adiante.

2- A prototipagem é uma técnica que declara funções através dos seus protótipos antes da
main, indicando assim que as funções existem, mas se encontram em outro lugar.

Chamada da função e o seu retorno

Quando uma função é chamada, o fluxo de controle é desviado para essa função e, a partir
desse momento, os comandos da função são executados e, ao finalizar, o fluxo retorna ao comando
seguinte daquele onde ela foi ativada (se for void) ou ao ponto de onde ela foi chamada, para funções
com retorno. Para que tudo isso seja possível, não poderemos desconsiderar o tipo de retorno e os
parâmetros, caso existam. As figuras abaixo poderão esclarecer melhor o que foi dito, mas, para de
fato você entender, precisará conhecer uma estrutura de dados chamada pilha que estudaremos em
outra aula.

Exemplo de uma chamada de função com retorno

Tipos de Funções

Função sem parâmetros

Na linguagem C++ não é obrigatório o uso de parâmetros em todas as funções. Sendo assim,
nesse primeiro momento, vamos analisar as funções sem parâmetros, pois não necessitam do domínio
dos conceitos de passagem por valor ou por referência e sem retorno por serem de mais fácil
entendimento. Uma função que não retorna nada para a função chamadora é do tipo void e esse tipo de
função pode ser considerado, hierarquicamente, abaixo da main, tendo em vista que ela "faz tudo"
mesmo sem receber qualquer argumento.

Vejamos alguns protótipos:

void asteriscos();

void menu() ;

void calculaReajuste().
Atenção

Você deve ter percebido que nada foi colocado entre os parênteses, mas poderia, embora seja
dispensável, colocar a palavra void.

Sendo assim, o primeiro protótipo poderia ser escrito da seguinte forma: void asteriscos(void).

Como é feita a chamada de uma função sem retorno?

Uma funçào do tipo void, tendo ou não parâmetros, é chamada pelo nome, isto é, não precisará
que um comando lhe anteceda. Se tiver parâmetros, eles estarão presentes entre os parênteses.
Vejamos a sintaxe abaixo.

Quando lemos o protótipo de algumas


funções sem retomo, não conseguimos visualizar todas as operações que serão realizadas, mas isso é
natural porque é somente a definição de uma função que apresenta o conjunto de operações que
deverão ser realizadas.

Função com parâmetros

Para que possamos trabalhar com funções com parâmetros, primeiro precisamos saber como a
passagem para esses parâmetros é feita. Duas são as formas básicas de passagem dos parâmetros:

Passagem por valor

Na passagem por valor, uma cópia do valor, ou valores, é passada para os parâmetros formais
da função chamada através dos parâmetros reais da função chamadora. Sendo que os parâmetros reais
poderão estar representados por variáveis ou constantes.
A função chamada poderá operar esses valores, mas não alterará os valores da função
chamadora.

Concordamos que para alguns poderá parecer confuso e, por essa razão vamos exemplificar
com algo do cotidiano.

Suponha um autor de livro e um leitor. Ao tentar entender a solução de um problema, o leitor


percebe que tem um erro. Ele corrige o erro, mas não corrige o original que está na editora, visto que
ele recebeu uma cópia do livro.

As funções pré-definidas que você viu em Algoritmos eram todas com passagem por valor.

Funções com passagem por valor podem, ou não ter retorno.

Se a função tiver retorno, lembre-se de que uma função só poderá retornar um valor para a
função chamadora.

Na figura abaixo, você poderá observar como os valores são passados para os parâmetros da
função chamada:
Passagem por referência

Na passagem por referência, o endereço da variável da função chamadora é passado para a


função chamada e dessa forma, o valor poderá ser alterado. Na linguagem C, essa passagem tinha que
ser feita através do uso da variável ponteiro e acarretava, quando não operado corretamente, em
muitos problemas. Tendo em vista os problemas causados pela manipulação dos ponteiros, a
linguagem C++ criou um tipo novo de dado, chamado referência que nada mais é do que uma forma
de atribuir um nome alternativo (apelido ou alias) para um objeto.

Esse conceito aplicado aos parâmetros formais, parâmetros da função chamada, possibilitará
que a função chamada receba o endereço do valor da função chamadora, alterando-o.

Esse tipo de passagem ficou mais simples do que o uso de ponteiros porque o compilador se
responsabiliza por tudo. Como indicar uma referência?

Vamos usá-lo nos parâmetros formais da função. Observe o protótipo abaixo.


Passagem por referência por ponteiros

Não se preocupe com os nomes usados por alguns autores para se referenciar a esse tipo:
passagem por nome ou passagem por endereço. O importante é que o uso de uma variável do tipo
ponteiro é obrigatório. Embora a variável do tipo ponteiro não seja objeto de estudo aqui, vale a pena
dizer que uma variável do tipo ponteiro não guarda dado, mas o endereço de uma variável.

Passagem por referência

É a passagem que usa o tipo referência, já definida nesse tópico.


Atenção

Referência não é ponteiro, mas ambos manipulam endereço. Filosofia pura.

Vamos agora analisar alguns protótipos de funções, tendo em vista os novos conceitos
estudados.
Atenção

Embora possamos simplificar o protótipo de uma função não escrevendo os nomes dos
parâmetros como foi feito nos dois últimos exemplos, talvez seja um bom hábito sempre escrevê-los.

Como é feita a chamada de uma função que retorna valor?

Uma função que retorna valor deverá ser chamada através de um comando, não importando se
ela tem, ou não, parâmetros.

Atenção
Fique atento. Uma função só poderá retornar um valor!

O comando return

Uma função que retorna valor terá, obrigatoriamente esse comando no corpo da função. Isso
não quer dizer que não poderá ter mais de um comando return no corpo da função.

Quando existir múltiplas respostas, poderemos ter vários tipos de retorno.

return...;

Pode estar presente uma variável, uma constante, uma expressão.

Uma função, que não seja a main, chamando outra função poderá causar algum problema?

Qualquer função poderá chamar outra função desde que a função chamada já esteja declarada,
ou definida. Para responder a essa pergunta, observe o exemplo abaixo onde três funções foram
colocadas antes da função main e, no entanto, não compilou.
Variáveis locais

A maioria dos exemplos apresentados até aqui teve variáveis declaradas dentro das funções. A
esse tipo de declaração, onde as variáveis só são visualizadas nas funções onde foram declaradas,
chamamos de variáveis locais.

Variáveis globais

Variáveis declaradas fora do escopo de todas as funções são chamadas de variáveis globais.
Esse tipo de variável poderá ser manipulado por qualquer função.

Atenção

Variáveis globais deverão ser usadas com muita cautela.

E como fica a análise da função main()?

Ao longo dos anos, vários ajustes foram feitos para que existisse um padrão. O tipo int foi
escolhido embora pudesse ser omitido. Mais tarde, aconteceu a remoção do int implícito. Sendo assim,
o cabeçalho da main passou a ser:
int main()

Já vimos que é opcional o uso da palavra void entre os parênteses quando a função não tem
parâmetros.

Se a main é do tipo int, então tem retorno. Porque funciona sem return?

A main é uma função chamada pelo sistema operacional e, ao finalizar, retorna,


automaticamente, para o SO. Sendo pontual, o return da main poderia ser dispensável, visto que ele
serve para informar que o programa foi executado com sucesso e isso, podemos constatar. Entretanto
seu retorno poderá ser usado pelo sistema operacional, mas isso é uma outra história.

Dica

Um bom hábito é escrever return 0; ou return EXIT_SUCCESS; em projetos maiores.

Passando uma estrutura de dados homogênea para uma função

Nossos exemplos só abordaram passagens de valores, ou de endereços, de variáveis


numéricas, mas agora vamos aprender como passamos para uma função uma estrutura de dados
homogênea. Em particular, uma matriz unidimensional (vetor).

Atenção

Na disciplina de Algoritmos, na Aula 8, estudamos vários conceitos que serão importantes


agora. Releia seus apontamentos ou, assista ao ppt da referida aula, se você tiver feito o download.
Um dos conceitos dizia respeito ao armazenamento na MP de um vetor. Falamos que só era
armazenado o primeiro endereço do primeiro elemento do vetor. Vejamos as figuras do programa, do
armazenamento na MP e das saídas do programa compilado no Dev-CPP e no Visual C++.
Vamos agora analisar alguns protótipos com passagem de vetores para funções.

Sua preocupação tem justificativa, visto que, ao estudar, você poderá encontrar os mais
variados cabeçalhos para a função main em livros com grande aceitação no mercado. Vejamos alguns
exemplos.
Atenção

Não pense que é fácil, baseada no protótipo de uma função, identificar sua finalidade ou até
mesmo os objetivos dos parâmetros.

Agora estaremos escrevendo as definições dos quatro protótipos.

Aula 3 – Estruturas homogêneas

1 Tema: As Estruturas de Dados heterogêneas chegaram para agrupar

Podemos definir uma estrutura como sendo um conjunto de elementos, geralmente, agrupados
sob uma lógica e associados por um nome.

Esses elementos podem ser variáveis simples, matrizes, outras estruturas e até funções.

Por essa definição, podemos concluir que uma estrutura pode ser formada por elementos de
tipos diferentes.

Cada elemento da estrutura é chamado de membro ou campo.

Como se define uma estrutura?

Para se definir uma estrutura, você precisa seguir a seguinte sintaxe:


A definição termina com um; porque é um comando. É bom ressaltar que nenhuma variável
foi declarada.

Respondendo aos questionamentos...

Onde irei usar isso? Quando definimos uma estrutura, o nome dado à estrutura se torna um
tipo assim como você usa int, float, double, char. Com este nome, você poderá declarar variáveis,
inclusive arrays(matrizes) e, o mais importante, nem será necessário usar a palavra struct antes, só o
nome que você deu. Onde se define uma estrutura? Uma estrutura pode ser definida dentro de uma
função e então, dizemos que ela é urna estrutura local ou então, definida antes de todas as funções e
dizemos que ela é urna estrutura global. Veja exemplos.
Podemos então concluir que uma estrutura definida de forma global poderá ser usada por todas
as funções. Enquanto que uma estrutura definida localmente, só poderá ser usada na função onde foi
definida.
Existe uma estrutura que chamamos de estrutura anônima. Essa estrutura não tem
identificador. Todas as variáveis precisarão ser declaradas na definição. Observe a sintaxe dessa
estrutura.

Alguns autores chamam de identificador o nome que damos à estrutura. Outros, chama de
gabarito ou, simplesmente, nome da estrutura.

Sendo assim, para facilitar o entendimento, passaremos a usar nomeDaEstrutura.

Agora que a variável foi declarada, o compilador aloca na Memória Principal todos os
membros da estrutura.

Nosso primeiro exemplo será bem matemático, tendo em vista que todos se recordam das
coordenadas cartesianas. Escolhemos o primeiro método.
Vamos entender esta estrutura e as duas variáveis declaradas através das figuras abaixo.
Você está certo. As variáveis de uma estrutura são agrupadas por uma lógica e estão
associadas a um mesmo nome. Para acessar cada membro, é muito fácil. Observe o próximo item.

Como se acessa um membro de uma estrutura?

Um membro da estrutura pode ser acessado usando o operador membro das estruturas(.),
chamado operador ponto, colocado entre o nome da variável estrutura e o nome do membro.
D
,

Neste exemplo, apresentamos uma das grandes vantagens do uso da variável estrutura na
programação: a facilidade de se atribuir todo o conteúdo de uma variável estrutura a outra variável
estrutura. Você deve estar lembrado que precisávamos usar strcpy para copiar vetores de char e
comandos de atribuição para as demais variáveis. Usando variável estrutura, com um único comando
de atribuição, copiamos todos os conteúdos dos membros mesmo que eles sejam vetores de char.
Observe como teria ficado o programa sem o uso da variável estrutura. Imagine se a variável estrutura
tivesse 10 membros?
2 Construindo um array de estruturas

Na disciplina de Algoritmos, nós estudamos que um array é um conjunto de variáveis todas do


mesmo tipo. Talvez possa causar certa estranheza estarmos falando em array de estruturas, mas pense
de forma mais ampla: as estruturas não são iguais? Sendo assim, construir um conjunto de estruturas
está correto.

Como se declara um array de estruturas?

1º método

Da mesma forma que declaramos as estruturas simples, podemos declarar os arrays de


estruturas. A declaração é feita depois da definição. Se a definição for global, a declaração acontece
em cada função.

2º método

A declaração é feita junta com a definição.


3 Visualizando um array de estruturas e acessando um elemento do array

4 Acessando um elemento do array

Precisamos ter muito cuidado quando formos acessar um array de estruturas porque primeiro
temos que escolher a variável do array como fazíamos com os arrays unidimensionais. A figura
esclarece isso do lado esquerdo quando relaciona as variáveis estruturas com um par de colchetes e o
deslocamento dentro dele(índice).

Depois, acrescentamos separado por um ponto, os membros da estrutura. Você poderá ver isso
no lado direito da figura.

Cada retângulo está dividido por linhas tracejadas porque uma variável do tipo int, ou do tipo
float, ocupa quatro posições de memória.

Atenção
Gostaríamos de relembrar que os dados são alocados de forma contígua na Memória Principal
como se fosse uma matriz unidimensional. A figura acima é meramente ilustrativa e tem como
objetivo possibilitar uma melhor compreensão sobre um array de estruturas.

5 Algumas considerações

Num primeiro momento, é normal se questionar: por que não continuamos usando arrays
unidimensionais. Não seria mais fácil? Será?

Você se lembra, logo no início, quando perguntava ao professor ou a um colega: “como o


computador sabe que a idade é dessa pessoa e não da outra?” e a pessoa lhe respondia: “Não sabe, pois
os nomes das variáveis têm que ter sentido para você.” E, muitas vezes, você ficava sem entender,
mas, com o passar do tempo, foi percebendo que se tivesse que criar três variáveis para guardar nomes
de pessoas e três variáveis para guardar as idades delas, você escolhia algo parecido com: nome1,
nome2, nome3, idade1, idade2, idade3.

Até que um dia, você aprendeu matrizes e começou a se questionar novamente como as
matrizes poderiam se relacionar. Enfim, este dia chegou. Usando um array de estruturas.

Antes desta aula, dimensionaríamos três arrays unidimensionais (vetores) ou um array


unidimensional inteiro e um array bidimensional real.

int codigo[1000];float precoCompra[1000], precoVenda[1000]; ou

int codigo[1000];float precos[1000] [2];


Veja como ficaria se tivéssemos feito a opção por três arrays unidimensionais.

Atenção

Temos portanto, duas formas de resolver o problema: uma matriz de estrutura ou três matrizes.
Se preferirmos a segunda, percebe-se que não existe uma relação entre as matrizes e quando formos
ordenar, precisaremos de muitos trechos para trocar as variáveis de lugar. Não que esteja errado, mas
tudo ficaria mais extenso se continuássemos usando somente as matrizes. Lembre-se que elas não
desaparecem, mas se agregam.

6 Estruturas aninhadas

Muitas vezes, definimos estruturas que podem ser membros de várias estruturas.

Em qualquer cadastro, por exemplo, tem data: data de nascimento, data de pagamento, data de
recebimento, data de entrega, etc.

Sendo assim, poderíamos definir uma estrutura data e visualizá-la da seguinte forma:
Acessando um membro de uma estrutura dentro de um array de estruturas

nomeDoArrayl[deslocamento no array].nomeDaEstruturaAninhada.membro

Atenção

Na aula passada, estudamos que o uso de funções melhora a legibilidade e a manutenibilidade


do programa logo, chegou o momento de criarmos funções com/para estruturas.

Você verá algumas coisas interessantes que poderemos fazer ao construirmos funções com
estruturas como por exemplo: estruturas poderão ser retornos de função, estruturas poderão conter
função dentro delas além de tudo que já foi feito com os arrays homogêneos.
7 Passagem por valor

Da mesma forma que passamos o conteúdo de uma variável simples ou de um elemento do


vetor, podemos passar um membro (campo) de uma estrutura. Vale a pena relembrar que, na passagem
por valor, passamos uma cópia do conteúdo logo, o valor original não se altera.

Neste exemplo, chamaremos a função três vezes, passando os membros da estrutura de tal
maneira que retorne o maior dos quatro números.
8 Passagem por referência

Quando estudamos funções, para quem já tinha visto a linguagem C, notou que a passagem
por referência simplificou o uso da passagem por endereço(ponteiros). Por isso, citamos SAADE,
Joel(2003, p.112): “Pode-se dizer que a passagem por referência é uma forma disfarçada de ponteiro”.
Neste exemplo, passaremos o endereço de um membro do struct por referência. A função irá
calcular a idade da pessoa em 2020 e irá alterar o valor do membro.
9 Passagem de um membro que é um vetor - passagem por endereço,

Nós já vimos que os arrays são, em verdade, endereços tanto é que o que fica entre parênteses
é o deslocamento em relação ao endereço base. Sendo assim, a passagem de um membro que é um
vetor se dá de mesma forma que fizemos com os arrays unidimensionais. Só para reforçar esse
conceito que consideramos muito importante, vamos observar o programa abaixo. Nele, fizemos
referência ao nome da estrutura e aos nomes das três variáveis do array, mas somente os nomes das
três variáveis foram precedidos pelo símbolo & que representa endereço.

Na saída, percebemos que o endereço da estrutura é o mesmo da primeira variável do array de


estruturas algo que já dissemos desde a disciplina de Algoritmos quando explicamos porque o valor do
primeiro elemento do array era 0(zero).

O endereço da segunda variável está distante de 31 posições, visto que, como está
representado em hexadecimal, o final 10 seria equivalente em decimal a 16(1 x 16 + 0) enquanto 2f
seria equivalente a 47(2 x 16 +15). Então, 47 -16=31 que é o tamanho declarado para o vetor de char.
Assim como 4e seria equivalente a 78(4 x 16 + 14) então, 78 - 47= 31. c.q.d (como queriamos
demostrar).

Já que provamos que o vetor é um endereço, vamos ao exemplo onde passaremos um membro
da estrutura que é um vetor.
10 Estrutura como parâmetro de uma função

Neste exemplo, apresentaremos uma função que recebe uma estrutura para ser manipulada
dentro da função.
11 Estrutura como valor de retorno de função

Neste exemplo, apresentaremos uma função do tipo estrutura logo, terá como retorno, uma
estrutura.
12 Funções como membros de estrutura,
Neste exemplo, apresentaremos uma estrutura cujos dois membros são funções. Uma com
retorno e outra, sem retorno.
Aula 4 – Ordenação e pesquisa

1 Ordenar e Pesquisar - uma parceria que não se desfaz mais

A ordenação e a pesquisa são dois tópicos fundamentais para a programação e ficou muito
difícil escolher entre tantos métodos quais deveriam ser abordados sem ter que agregar novos
conceitos a esta aula. No que diz respeito aos métodos de Ordenação, a decisão ficou mais complicada
porque um dos métodos precisa do conceito de recursividade que não é objeto de estudo desta
disciplina e que necessita de uma maturidade maior de programação. Sabemos que este estudo não se
encerra em uma aula e sugiro que você continue sua pesquisa para conhecer os outros métodos que
falaremos, de forma superficial sobre eles. Esta aula estará dividida em dois momentos: Ordenação e
Pesquisa.

2 Ordenação

Normalmente, quando o usuário insere dados, ele não se preocupa se os mesmos estão, ou não,
em ordem, pois ele acredita que o sistema fará isso para ele. Este é um quesito básico e o cliente não
precisa mais deixar isto claro. Sendo assim, qualquer programador, precisa conhecer os métodos de
ordenação. Decorar? Não! Não estou dizendo isso, mesmo porque ele poderá estar na sua biblioteca,
mas alguns são tão simples que você acabará sabendo. O que disse foi saber escolher o método certo
para cada aplicação, mas, para que isso seja possível, você deverá compreender a lógica de cada
método para que não tenha que decorar as vantagens de cada um e sim, saber citá-las baseado no
conhecimento da lógica do algoritmo de cada método.

Recordando...
Antes de conhecermos os métodos de ordenação, seria conveniente reforçar um pouco mais, já
que vimos na aula passada, como os vetores numéricos (int, float ou double), vetor de char, matriz de
char ou matriz de struct podem ter seus conteúdos comparados e trocados de lugar se não estiverem
ordenados de forma crescente, decrescente ou alfabética, como veremos nos exemplos a seguir.
Char de um caracter
Vetor de char
Membros de struct
3 Os métodos de ordenação

Os métodos de ordenação constituem um bom exemplo de como resolver problemas utilizando


computadores. As técnicas de ordenação permitem p. de algoritmos distintos para resolver uma mesma
tarefa. Dependendo da aplicação, cada algoritmo considerado possui uma vantagem particular sobre os
outros algoritmos. (ZIVIANI, N., 2002, p.69)
Ordenar significa dispor elementos de um conjunto seguindo um determinado critério. Esse
critério estará baseado em um atributo do elemento do conjunto (nome, matrícula, salário, coeficiente
de rendimento, etc). Sabemos que um conjunto quando se encontra ordenado, facilita a recuperação
dos seus elementos. Você já imaginou se a lista telefónica não estivesse ordenada?

Ordenação Interna versus Ordenação externa

Quando o método de ordenação só usa a Memória Principal para realizar o processo de


ordenação, esse método é classificado como Ordenação Interna, mas se usa uma memória auxiliar
porque o arquivo é muito grande, é classificado como Ordenação Externa. Os métodos de ordenação
interna são classificados em dois tipos:
Embora saibamos das vantagens dos métodos eficientes, fizemos a opção de apresentar os
métodos simples, deixando para um outro momento, o estudo dos métodos eficientes por serem
complexos e, como sempre digo, um passo de cada vez. Na disciplina de Algoritmos, falamos que não
existe uma solução única para um problema. Evidentemente, as soluções apresentadas para os métodos
de ordenação também não são únicas, mas tentei manter um padrão para ficar mais fácil o
entendimento. Sendo assim, preferi usar em todos os métodos, a estrutura do for.

4 Selection Sort (Ordenação por seleção)


Começaremos pelo método cujo algoritmo é o mais simples de entendimento. Afirmamos isso
porque seu princípio é sempre buscar o menor e ir posicionando-o no início como veremos a seguir.

Construiremos o passo a passo com figuras. Depois, apresentaremos todos os passos juntos.
Codificaremos na linguagem C++, incluindo alguns trechos de impressão para ajudá-lo no
entendimento do método e disponibilizaremos uma animação para tornar ainda mais clara a
explicação. Esperamos que com todos esses passos, você consiga compreender a lógica deste método,
mas se ficar alguma dúvida, fale com seu professor.

Atenção

Relembrando: embora estejamos falando de primeiro, segundo, terceiro, quarto ou quinto,


sabemos que cada elemento do vetor é indexado pelo deslocamento em relação ao endereço base, isto
é, índice 0, 1, 2, 3 ou 4.
Você terá que construir um algoritmo que tenha uma repetição dentro de outra repetição, visto
que você percorreu o vetor quatro vezes (2,4,6,8) para identificar o menor e, a cada vez que percorria o
vetor, você comparava os elementos para encontrar o menor. Supondo que você usará duas estruturas
de repetição (for), poderemos afirmar que cada vez que o for mais interno terminar, o algoritmo
identificará o menor número e fará a troca.

A cada nova busca, o número de comparações diminuirá e esses procedimentos se repetirão


até que o vetor esteja ordenado.

A figura abaixo reúne os passos importantes para lhe auxiliar na construção do algoritmo.

Seleciona o menor, após o termino do for mais interno.

Tente construir uma função de nome seleção que receba um vetor, o tamanho do vetor e
ordene pelo método Selection Sort(Ordenação por Seleção). Depois, clique no botão e veja uma outra
solução para essa função. Clique também no botão exemplo para acompanhar o passo a passo do
programa.
Atenção

Observe que o for mais externo não se preocupa com a última posição do vetor porque quando
compara a penúltima com a última, a última, automaticamente, será ordenada.

O for mais interno irá “varrendo” o vetor a partir da posição seguinte que estiver ordenada até
a última posição do vetor fazendo comparações e armazenando a posição do menor valor.

A variável aux guarda a posição do menor elemento da comparação.

Quando o for mais interno finalizar, o valor armazenado na posição indicada em aux, será
trocado de lugar como o elemento que se encontrar em posição trocada até que todo o vetor esteja
ordenado.

Você poderá acompanhar a execução do algoritmo de duas maneiras: através da salda, uma
vez que incluímos trechos para exibir o vetor em momentos diferentes do processo de ordenação ou
através da figura.
5 Insertion Sort (Ordenação por inserção)

Suponha que todas as suas contas a pagar cheguem antes do dia 30 e que, nesse dia, você
coloque em ordem de acordo com o dia de pagamento. Então, você pega a primeira conta com a mão
direita e passa para a esquerda e corno é a primeira, poderia afirmar que ela está ordenada. Quando
você pega a segunda, com a mão direita, verifica se a data de vencimento é maior do que a primeira e
se for, coloca atrás da conta que está na mão esquerda, mas se não for, coloca na frente. Este processo
vai se repetindo até a última conta. Este é o princípio básico do método de inserção que considera o
vetor como dois subvetores, um ordenado como as contas da sua mão esquerda e o outro,
desordenado, como as contas que a sua mão direita vai apanhando e tentando localizar a posição
correta no conjunto de contas da mão esquerda.
6 A lógica do Método Insertion Sort

Você terá que construir um algoritmo que tenha uma repetição dentro de outra repetição.
Entretanto, a repetição interna, se usada com a estrutura do for, deverá ter um teste que além de
controlar a variável do for, interrompa a repetição se a variável tiver um conteúdo maior do que as que
lhe antecedem em posição no vetor.

A figura abaixo reúne os passos importantes para lhe auxiliar na construção do algoritmo.
Tente construir uma função de nome inserção que receba um vetor, o tamanho do vetor e
ordene pelo método Insertion Sort(Ordenação por Inserção).

Veja a seguir uma outra solução para essa função. Veja também um exemplo para acompanhar
o passo a passo do programa.

void insercao(int vet[], int tam)

int j,i, aux;

for (i=1;i<tam;i++)

{
aux = vet[i];

for(j=i; j>0 && aux <vet[j-1];

j--)

vet[j]=vet[j-1];

vet[j]=aux;

Atenção

Observe que o for mais externo começa com a segunda posição do vetor porque o for de
dentro sempre compara com os elementos anteriores. O teste de j>0 se justifica porque dentro dos
colchetes, ele subtrai de 1 o valor de j e se deixasse j chegar a zero, o índice ficaria negativo o que não
é permitido na linguagem C++.

O elemento fundamental do for interno é o teste: j>0 && aux <vet[j-1] porque incorpora um
teste interrompendo a repetição quando a condição aux <vet[j-1] não for mais atendida. O for usado
desta forma é uma herança da linguagem C e talvez mais fácil de ser construído do que a estrutura do
while. Nada contra a estrutura do while, mas como já havia dito, tentamos manter uma uniformidade
na apresentação desses métodos.
Você poderá acompanhar a execução do algoritmo de duas maneiras: através da saída, uma
vez que incluímos trechos para exibir o vetor em momentos diferentes do processo de ordenação ou
através da figura.
7 Bubble Sorte (Ordenação por Bolha)
8 A lógica do Método Bubble Sort

Você terá que construir um algoritmo que tenha uma repetição dentro de outra repetição. Na
repetição interna, toda vez que dois valores de posições adjacentes forem encontrados fora de ordem,
deverão ser trocados de posições.No exemplo, o borbulhamento começou de baixo para cima, mas
você poderá fazer de outra forma. A figura abaixo reúne os passos importantes para lhe auxiliar na
construção dos algoritmos.
9 Afinal, qual seria o melhor método de ordenação?

Como disse no início, não teríamos como fazer um estudo incluindo muitos métodos de
ordenação para que ao final, pudéssemos fazer uma análise consistente. Sendo assim, levantaremos
somente alguns pontos sobre os três métodos estudados que são os mais simples, mas não são os
indicados para grandes volumes de dados.
Vamos agora empregar os três métodos estudados numa mesma aplicação para que possamos
constatar diferenças visíveis na construção do código, implicando num tempo maior para ordenar o
conjunto de dados uma vez que o número de trocas é muito maior. Observe com muita atenção esse
aspecto. O programa consiste em armazenar códigos e alturas de 5 atletas. Ordenar pelo código e
exibir códigos e alturas.
10 Método de ordenação por seleção cujo critério é nome, usando struct

Neste exemplo, resolvemos usar struct para que você perceba a grande vantagem, mesmo
usando o Método de Ordenação por Seleção, ao trocarmos os elementos de lugar, pois se estivéssemos
usando vetores, teríamos que usar tantos trechos de trocas quantos fossem os vetores e, no entanto, por
termos usado struct, só usamos um trecho de troca.
11 Pesquisa

Hoje em dia, quando se fala em pesquisar, logo alguém responde com um nome de um site de
busca. Às vezes, falo para meus alunos da área tecnológica que nem todas as profissões têm essa
"mania" e cito como exemplo o estudante/profissional da área de Direito que pode fazer provas
consultando livros desde que não tenham marcação. Você já se imaginou consultando um dicionário
do Aurélio? Claro que não! Muitos de vocês dirão. Entretanto, se você for fazer uma prova de língua
estrangeira, em muitos lugares, poderá consultar o dicionário, mas não seu notebook. Enfim, o que
pretendemos passar é que não paramos para pensar como esse processo acontece, pois já está
incorporado em nossas vidas, mas, nós que somos dessa área, deveríamos nos preocupar porque o
processo de pesquisa é fundamental na programação.

12 Pesquisa sequencial
Quando uma matriz não está ordenada, a única saída para pesquisar um elemento é fazendo
uma "varredura" até encontrar o elemento, ou até o final para concluir que não encontrou. A pesquisa
sequencial é simples, mas ineficiente, pois fazendo uma analogia com algo do nosso dia a dia,
perceberemos a grandeza do problema. Vamos imaginar que você chegou à cidade de São Paulo e
quer procurar o endereço da sua amiga num catálogo telefónico, fora de ordem. Isso é um problema
sem solução, diria você, com certeza, rindo. Não que seja sem solução, visto que você poderá achar
logo de primeira ou demorar dias, dias, dias ... Sei que estamos exagerando, mas queremos deixar
claro que esse tipo de pesquisa só será "eficiente" para um número pequeno de dados, mas temos que
começar pelo mais fácil, não é verdade?
Como os dois trechos são bem parecidos, vamos apresentar somente uma aplicação.
13 Pesquisa binária

Todos os algoritmos que vimos no item anterior apresentam a vantagem de serem simples
entretanto, a grande desvantagem é que podem procurar na matriz toda e não achar o elemento. A
pesquisa binária chega para suprir essa deficiência, mas faz uma exigência: a matriz tem que estar
ordenada. Utiliza a ideia de "Dividir para Conquistar" , visando reduzir o espaço a ser pesquisado.

1) Suponha o vetor de 10 elementos.

2) Suponha que 18 é o número a ser procurado.

3) Vamos calcular o meio do vetor, sabendo-se que o início é 0 e o final é 9: (0+9)/2(divisão


inteira). Logo, 4.

4) Como o número procurado é maior do que o meio, o início passa ser o seguinte do meio.

5) Calcula-se o novo meio: (5+9)/2=7

6) Como o número procurado é menor do que o número que está na posição do meio, o meio
passa a ser o fim.

Calcula-se o novo meio: (5+7)/2=6 e, nesse caso, o meio coincidiu com o número procurado.
AULA 5 - A ESTRUTURA DE DADOS – LISTA

1 A lista abre as portas para as outras Estruturas de Dados

Na primeira aula, vimos que a família do Sr. Augusto quando fez o check-in no aeroporto, a
atendente verificou se os nomes dele e dos parentes estavam na lista de passageiros.

A palavra Lista faz parte do nosso quotidiano, pois quantas vezes não fizemos uma Lista de
compras antes de irmos ao supermercado ou uma Lista de convidados para uma festa de aniversário ou
uma Lista de afazeres antes de viajarmos? Podemos concluir que uma Lista é, sem dúvida, a forma
mais simples de agruparmos dados.

Na aula de hoje, faremos um estudo sobre as Listas Lineares e aprenderemos a construir vários
trechos que serão úteis para as outras estruturas que também iremos estudar. A estrutura que permite
representar um conjunto de dados de forma a preservar a relação de ordem linear (ou total) entre eles é
a lista linear. Uma lista linear é composta de nós, os quais podem conter, cada um deles, um dado
primitivo ou um dado composto. (VELOSO,P.,SANTOS,C., AZEREDO,P., FURTADO, A., 1983,79)

Todas as Estruturas de Dados têm suas importâncias e aplicações, mas, normalmente,


começamos pelas Listas porque uma das formas mais simples de representá-las é com estruturas já
conhecidas como as matrizes homogêneas e as matrizes heterogêneas. Além disso, elas servirão de
base para que possamos implementar Estruturas de Dados que iremos estudar nas próximas aulas tais
como Pilhas e Filas.

Da definição dos autores, vamos destacar alguns conceitos:

1) Nó ou nodo – é um item da lista.

3) Se “uma Lista é composta de nós” então, uma Lista Vazia é uma lista que não tem nós.

Os elementos de uma Lista Linear podem ser agrupados de duas formas:

Sequencial

Esse tipo de estrutura apresenta os nós em posições contíguas de memória, isto é, um após o
outro como já vimos quando estudamos matrizes na disciplina de Algoritmos. Sendo assim, fica fácil
identificarmos o endereço de qualquer nó de uma Lista Sequencial.
Encadeado

Esse tipo de estrutura será visto em uma aula, mas você já teve um contato com ela na
primeira aula quando Sr, Augusto fez aquela brincadeira com os netos onde foram colocados bilhetes
e, em cada bilhete, além de algo que eles tinham para retirar, existia uma referência para um outro
local onde estava o próximo bilhete.

Este é princípio básico da Lista Encadeada, isto é, um conjunto de nós (nodos) encadeados
onde cada nó contém o dado e um apontamento para o próximo nó, visto que eles não estão alocados
de forma contígua.

Esta classificação que acabamos de estudar diz respeito à forma como os dados são
armazenados na Memória Principal. Entretanto, não pode ser confundido com Alocação Estática ou
Alocação Dinâmica.

Alocação Estática é determinada durante a compilação e é o que estamos fazendo e iremos


fazer até à aula 7. Já na Alocação Dinâmica, você poderá determinar o tamanho, ou acrescentar,
durante a execução.

Reunindo estas classificações, podemos concluir que existem quatro possibilidades de


Alocação de Memória.
Sobre este assunto, gostaria de colocar meu ponto de vista, pois não estou aqui para dizer para
você o que é comum, mas o que é possível porque nunca saberemos o que um cliente pode desejar ou
o que você poderá um dia precisar.

Na aula de hoje, não falaremos sobre Alocação Dinâmica. Sendo assim, nos restringiremos à
Lista Linear Sequencial.

A Lista Linear Sequencial é ideal para um conjunto pequeno de dados. Apresenta a vantagem
do acesso ao nó pelo índice e a desvantagem da movimentação quando se remove dado e insere se
desejarmos deixar os vazios ao final. A forma de acesso à LISTA no que diz respeito à inserção e à
remoção de um dado determina uma classificação mostrada na figura abaixo.

Nós podemos realizar várias operações com as Listas, mas, antes de qualquer operação,
precisaremos preparar a Lista para receber os dados. Quando falamos em preparar, estamos nos
referindo ao processo de inicialização da Lista que não poderá ser confundido com o fato de zerarmos
todos os elementos de um vetor que servirá como um vetor acumulador. Em um dos exemplos,
faremos isso, mas embora tenha o mesmo nome, o processo não é o mesmo.

Inicializar uma Lista é atribuir zero à variável que será responsável por armazenar a
quantidade de dados que já se encontram na Lista.
Exemplos de algumas operações que podem ser realizadas com uma Lista Linear Sequencial.

Evidentemente, é quase improvável que todos esses trechos estejam presentes em uma
aplicação ao mesmo tempo e, por essa razão, resolvi apresentar quatro aplicações, procurando incluir a
maioria dos trechos citados.

Gostaria que você tivesse clareza de que existe uma grande probabilidade de quando
estivermos estudando esta aula que eu venha a achar que as soluções apresentadas poderiam estar
muito melhores, mas isso é normal porque quando construímos um algoritmo somos influenciados
pelo momento que estamos vivendo e, embora estejamos investidos em fazer sempre o melhor, às
vezes, não acontece. Ainda bem que existe a manutenção para resolver esses e outros problemas.
Vamos aos exemplos!!!

Esta função recebe o endereço do vetor, por referência o endereço da variável t e o tamanho do
vetor. Inicialmente, é feito o teste para verificar se a Lista está cheia, visto que a variável t contém o
tamanho atual e a variável tamanho contém o tamanho atual da Lista.

Caso a Lista não esteja cheia, é solicitada a digitação do código do produto e, como o tamanho
atual corresponde também à posição que deverá ser armazenado o dado, o código digitado é colocado
na Lista e o tamanho da Lista é incrementado. Como a variável t foi passada por referência, o valor se
altera na função main ().

Exibir Lista
Exibe um elemento da LISTA

Aplicação
Este exercício terá uma Lista com 5 nós para você poder testar. Os elementos desta Listo serão
inteiros e matrículas (suponha de funcionários de uma empresa de pequeno porte). Além da opção de
reiniciar a Lista para que você possa fazer vários testes, foram colocadas, no menu, 4 opções: Inserir
matricula na Lista, Exibir Lista, Procurar matricula na Lista e Remover matricula da Lista. Para todos,
foram criadas funções.

Inserir matrícula na LISTA


Esta função parece diferir da primeira apenas no fato da pergunta sobre a matrícula a ser
inserida, não estar dentro da função. Entretanto, no processo de inserção, em um único momento,
existe uma diferença que não é prejudicial. Você sabe qual é?

Esta função é idêntica à do primeiro exemplo.

Procurar matrícula na LISTA

Somente a linha posicao = buscaSequencial(matricula,mat,tam); seria a chamada da função,


mas se eu não colocasse todo o trecho, ficaria difícil entender o que sena feito com o retomo da
função.

Para procurar a matricula, foi usada uma função que realiza uma busca sequencial que recebe
o endereço de um vetor e o tamanho atual, ficando com três possibilidades de retorno: -1, se a Lista
estiver vazia; -2 se a matrícula não foi achada ou a posição onde se encontra a matricula. A chamada
da função é feita com retorno para uma variável que terá seu valor testado para que o usuário possa
obter respostas diferenciadas de acordo com o retorno. O fato de encontramos a expressão posicao + 1
é porque os índices, como sabemos, estão defasados de 1.
Remover matrícula da LISTA
Somente a linha remove(matricula,mat, tam ); sena a chamada da função, mas coloquei as
outras duas para você entender sem ter que ler o código todo. Para que possa remover uma matricula
da Lista, a função remove chama a função buscaSequencial. Além de fazer os testes, ela desloca todas
as matrículas para cima, mas isso não é obrigatório. Foi uma opção, pois você pode inserir o valor em
qualquer posição.

Aplicação
EXEMPLO 3

Este exercício terá uma Lista com 10 nós para você poder testar. Os elementos desta Lista
serão inteiros e idades. Além da opção de reiniciar a Lista, foram colocadas, no menu, 4 opções:
Inserir idade na LISTA, Exibir Lista, Ordenar LISTA e Exibir frequencia. Para todos, foram criadas
funções.

Inserir idade na LISTA

A função insere é idêntica a dos outros exemplos, mas gostaria de fazer algumas
considerações sobre o trecho que antecede à chamada da função, visto que coloquei uma proteção para
garantir que somente as idades no intervalo de 10 - 19 serão aceitas. Suponha que este programa esteja
realizando uma pesquisa que seleciona um grupo de atletas nesta faixa etária. Observe também que
está presente uma variável de nome 13 que recebe O toda vez que um número é inserido. Ela será
testada em outro trecho. Fique atento!
Exibir Lista

Esta função é idêntica à do primeiro exemplo.

Ordenar LISTA

Gostaria que ficasse atento ao trecho que chama a função seleção porque após terminar a
ordenação, a variável f3 recebe o valor 1, significando que a Lista está ordenada que é uma condição
necessária para a função que exibe a ocorrência das idades no conjunto (frequência). Quanto ao
método de ordenação, foi apenas uma escolha entre os três que estudamos.
Exibir frequência
Começo analisando a chamada da função frequências, pois ela está dentro de um teste feito
com a variável f3, uma vez que ela sinaliza se a Lista está, ou não ordenada. Deixando mais claro: toda
vez que uma idade é inserida, f3 recebe 0 e quando a Lista é ordenada, f3 recebe 1. Sendo assim, a
função frequências só será executada se a Lista já estiver ordenada.

A função é bem simples e creio que a linha que vou analisar é que poderá trazer alguma
dúvida.

frequência[IDS[i]-10]++;

Se somente idades no intervalo de 10 a 19 serão armazenadas no vetor IDS, podemos associar


o vetor frequência da seguinte maneira:

Posição 0 com a idade 10

Posição 1 com a idade 11

...

Posição 8 com a idade 18

Posição 9 com a idade 19

Como? Se você subtrair 10 da idade, obterá a posição e poderá incrementar direto o vetor
frequência.

Gostou? Mas isto foi possível porque o vetor era inteiro e uma expressão foi conseguida.
Aplicação

#include <iostream>

using namespace std;

void insere(int IDS[], int idade, int &t, int tamanho);

void exibe(int IDS[], int t);

void selecao(int IDS[], int t);

void frequencias(int IDS[], int t);

int main()

int tam, op, f3=0, idades[10], id;


//Inicialização

tam = 0;

do

system("cls");

cout<<"\nMenu 2 - LISTA \n";

cout<<"\n0- Reinicar a LISTA";

cout<<"\n1- Inserir idade na lista";

cout<<"\n2- Exibir lista";

cout<<"\n3- Ordenar lista";

cout<<"\n4- Exibe frequencia";

cout<<"\n5- Sair";

cout<<"\nOpcao: ";
cin>>op;

system("cls");

switch(op)

case 0: //reinicialiação

tam = 0;

break;

case 1:

cout << "\nDigite idade(10-19) a ser inserida na Lista: ";

cin >> id;

while(id<10 || id >19)

cout<<"\nAtencao para o intervalo.";

cout<<"Digite idade(10-19) a ser inserida na Lista: ";


cin>>id;

insere(idades,id,tam, 10);

break;

case 2: exibe(idades,tam);

break;

case 3: //ordena Metodo selecao poderia ser insercao ou bolha

selecao(idades, tam);

f3=1;

break;

case 4:if(f3==1)

frequencias(idades,tam);
else

cout<<"\nOrdene primeiro o vetor\n";

break;

case 5: cout<<"\nFinalizando o programa da LISTA\n";

break;

default: cout<<"\nOpcao invalida\n";

cout<<"\n\n"; system("pause");

}while(op !=5);

void insere(int IDS[], int idade, int &t, int tamanho)


{

if (tamanho == t)

cout << "\nAtencao! Lista cheia\n";

else

IDS[t] = idade;

t++;

void exibe(int IDS[], int t)

int x;

if (t == 0)
cout << "\nAtencao! Lista vazia\n";

else

for(x = 0; x < t; x++)

cout << "\n" << IDS[x];

void selecao(int IDS[], int t)

{int i, j, menor, temp;

if (t == 0)

cout << "\nAtencao! Lista vazia\n";

else

{ for(i=0; i<t -1; i++)

menor=i;
for(j=i+1; j<t ; j++)

if(IDS[j] < IDS[menor])

menor=j;

temp=IDS[i];

IDS[i]=IDS[menor];

IDS[menor]=temp;

cout<<"\nLista Ordenada\n";

void frequencias(int IDS[], int t)

int i, c, frequencia[]={0,0,0,0,0,0,0,0,0,0};
if (t == 0)

cout << "\nAtencao! Lista vazia\n";

else

for(i=0;i<t;i++)

{ frequencia[IDS[i]-10]++;

cout<<"\n"<<IDS[i]<<"\t"<< frequencia[IDS[i]-10]; //SÓ PARA FINS DIDÁTICOS

// exibe frequência dos números sem repetição

cout<<"\n\nIdade\tFrequencia\n";

cout<<"\n"<<IDS[0]<<"\t"<< frequencia[IDS[0]-10];

for(i=1; i<t; i++)

if (IDS[i] != IDS[i-1] )

cout<<"\n"<<IDS[i]<<"\t"<< frequencia[IDS[i]-10];
}

Aula 6 - A ESTRUTURA DE DADOS – PILHA

1 Empilhar para organizar

"Uma pilha é um tipo especial de Lista Linear em que todas as operações de inserção e
remoção são realizadas numa mesma extremidade, denominada topo." Nesta aula, estudaremos a Pilha
uma das mais simples Estruturas de Dados, mas com muitas aplicações. Lendo a definição acima,
podemos concluir que a remoção do elemento acontece num processo inverso ao da inserção, isto é, o
último a entrar é o primeiro a sair. Este critério que determina a sequência de entrada e saída desta
forma, chama-se LIFO( Last In First Out).
Você percebe que o número 30 foi o último a entrar na Pilha e passa a se posicionar no topo.
Na remoção, o número 30 é o primeiro a sair.

Uma das características da Pilha é que a inserção, remoção e acesso acontecem somente em
uma extremidade como você pode observar na figura. Normalmente, apresento o vetor como uma
matriz linha, mas para deixar mais parecido com uma Pilha, apresentei-o como matriz coluna e ainda
dei um giro de 180°. Tudo pelo "bem da Pilha".

Sei que você deve estar curioso querendo saber por que o 30 da última Pilha ficou cinza e não
foi removido. Não é verdade? Quando estivermos construindo o trecho de remoção, você entenderá.

Os procedimentos de inserção e remoção lembram qualquer empilhar/desempilhar do nosso


dia a dia. Assim como a retirada de um elemento que não esteja no topo da Pilha.

O número de operações que pode ser realizado com uma Pilha é muito pequeno, tendo em
vista o critério LIFO.

Três operações são consideradas básicas e, vou colocá-las no início, mas as demais também
são necessárias para que as primeiras possam funcionar perfeitamente ou para fornecer alguma
informação.

Inicializa() ou Init() - Inicializa a Pilha


Empilhar() ou Push() - Insere no topo da Pilha um elemento

Desempilhar() ou Pop() - Remove do topo da Pilha um elemento

acessoTopo() ou Top() - Tem acesso ao elemento que se encontra no topo da Pilha

verificaPilhCheia() ou isFull() - Verifica se Pilha está cheia

verificaPilhVazia() ou isEmpty() - Verifica se Pilha está vazia

Atenção

Muitos autores não destacam as três últimas operações porque, normalmente, elas estão
embutidas nas demais ou, se não estiverem, um comando if, ou de atribuição, pode resolver o
problema.

Nós vamos construir trechos para uma Pilha que poderá armazenar até 5 números inteiros.

Estarei apresentando, quando existir, o trecho que chama a função e a função.

2 Inicializar

A inicialização da Pilha irá se resumir a um comando de atribuição, preparando-a para receber


os elementos que serão inseridos. Como na Pilha, o topo, isto é, a posição mais alta da Pilha é de
maior importância, uma variável sempre deverá conter este valor. Sendo assim, para inicializar uma
Pilha, precisaremos colocar um valor que não seja possível ser índice de vetar na linguagem C++.
Gostaria de enfatizar que a inicialização não significa zerar todos os elementos da Pilha, mas é como
se deixássemos engatilhado um ponteiro que seria acionado assim que o primeiro valor entrasse na
pilha. Em programação, usamos uma variável e a inicialização foi feita na declaração. Observe o
trecho.

3 Empilhar (Push)

Esta é uma operação básica da Pilha. Para que possa ser empilhado um elemento, é preciso
testar se existe lugar na Pilha.

Verificar se a Pilha está cheia poderia ser feito em separado, mas preferi fazer junto, pois se
resume a um if, tornando-se mais rápido do que chamar uma função.

Além disso, uma chamada de função implica em colocar o endereço da instrução seguinte em
uma Pilha para que o retorno seja possível e, se a função for algo muito simples, talvez não valha a
pena ser criada. Foi assim que pensei e por essa razão reunimos duas operações nesta função. Observe.

Atenção

A função recebe o endereço do vetor, o endereço da variável que contém a posição do


elemento que se encontra no topo e o valor a ser inserido. Os dois primeiros parâmetros precisam ter
seus endereços passados porque serão alterados dentro da função. Observe que antes de inserir na
Pilha, a variável t é incrementada porque o topo passa a ser o valor que será inserido na instrução
abaixo. É uma função de fácil implementação.

Acompanhe agora, na figura abaixo, supondo que você digitou cinco números para serem
empilhados.

4 Desempilhar (Pop)

Esta é outra operação básica da Pilha. Para que possa ser desempilhado um elemento, é
preciso testar se a Pilha não está vazia.

Verificar se a Pilha está vazia poderia ser feito em separado, mas preferi fazer junto, pois se
resume a um if, tornando-se mais rápido do que chamar uma função.
Esta função é do tipo int, mas poderia ser do tipo void.

Atenção

A função recebe o endereço do vetor, o endereço da variável que contém a posição do


elemento que se encontra no topo e o endereço da variável que vai receber o valor que será
“desempilhado”.

Todos os parâmetros precisam ter seus endereços passados porque serão alterados dentro da
função.

Observe que depois de copiar o conteúdo do elemento que estava no topo da Pilha, a variável t
é decrementada.

Em nenhum momento foi removido o valor que estava armazenado na posição. Só o topo que
se deslocou.

Sanou sua dúvida agora?


Assim como a função empilhar, a função desempilhar é de fácil implementação e bem
parecida.

Acompanhe a agora, na figura abaixo, supondo que você escolheu desempilhar cinco números

5 Acesso ao topo (Pop)


Atenção

Esta função testa se a Pilha está cheia ou se a Pilha está vazia e, se não for nenhuma das duas
opções, exibirá o total de elementos na Pilha e o espaço disponível.

Você poderá escolher qual dos dois desejará exibir.

Clique aqui para baixar o exemplo em TXT.:

../docs/acessoaotopo.txt

Acredito que após termos visto todas as operações realizadas com a Pilha, poderemos
compreender porque a Estrutura de Dados Pilha é considerada tão simples. Na verdade, suas operações
são de fácil implementação.

Depois de termos visto as chamadas das funções e as funções, vamos ver o código fonte e
acompanhar, rapidamente a execução do programa.
6 Aplicações com Pilhas

Depois de termos entendido o conceito de empilhar e desempilhar, acredito que seja mais fácil
perceber a presença do uso desta estrutura de dados em várias aplicações.

Sobre o assunto de notações, já que não está incluído na ementa, falarei rapidamente sobre ele.

Na aritmética, estamos acostumados com a notação infixa onde o operador é colocado entre
dois operandos.
Esta notação é problemática, pois requer conhecimento da hierarquia das operações, obrigando
o uso dos parênteses para sobrepor esta hierarquia quando necessário.

Existem duas outras notações, a prefixa e a posfixa.

Na notação prefixa(notação polonesa), o operador antecede os operandos enquanto que na


notação posfixa (notação polonesa reversa), os operandos antecedem o operador. A grande vantagem
dessas notações é que elas não precisam de parênteses.

Como a calculadora será posfixa, nosso enfoque ficará com esta notação.

Exemplo 1

Procure, a partir da esquerda para direita, colocar os operandos na ordem em que aparecem na
expressão.

Quanto aos operadores, eles devem estar depois dos seus operandos.
Observe a expressão deste exemplo: tem duas operações onde a primeira tem hierarquia maior
do que a segunda logo, a operação 4 * 3 é executada primeiro:

Depois, o resultado será o primeiro operando da segunda operação.

Observe a tabela abaixo.

Atenção

Muita atenção. Algumas vezes, você poderá ter dois operadores juntos, pois tudo dependerá do
número de operandos e da hierarquia das operações. Fique ligado.

Como a calculadora será posfixa, nosso enfoque ficará com esta notação.

Exemplo 2
Solução
7 Primeira Aplicação: Calculadora posfixa das quatro operações básicas
Qual seria a expressão que representaria as operações realizadas?

Pense, escreva e depois confira passando o mouse no botão solução.

Solução

Considerações sobre o programa

As operações push e pop são as mesmas do programa da pilhaBasica.cpp exceto pelos nomes.

Nesta aplicação, cada vez que um operador é encontrado, dois elementos da Pilha são
desempilhados e operados e o resultado da operação retorna para a Pilha.

Vamos acompanhar o passo a passo da notação reversa polonesa com a expressão abaixo:
8 Segunda Aplicação: Conversão de um número decimal para binário
,
Aula 7 – A estrutura de dados Fila

1 Tema: Organizando a Fila para agilizar

Á medida que vamos conhecendo novas estruturas de dados, passamos a contar com maiores
recursos para elaborarmos nossos algoritmos mais próximos do ideal. Sei que no início pode parecer
que estas estruturas de dados, que estamos estudando, não são necessárias, mas talvez porque nunca
tenhamos construído um grande sistema ou então, nossos sistemas estão restritos a determinadas áreas
onde o "velho triângulo: entrar-armazenar-consultar" é a base do sistema que pode até prescindir
destas estruturas. Então, vou tentar, de forma bem primária, mostrar que as estruturas de dados estão
presentes em várias situações que você já vivenciou e que agora está formalizando os conceitos.
1) Imagine uma turma em uma sala, fazendo prova final no computador.

2) Todas as provas deverão ser impressas e todos os micros estão compartilhando uma
impressora que se encontra na sala. Somente quando o aluno finaliza a prova, ela é corrigida e
enviada para impressão, entrando em uma -> FILA.

3) A nota é lançada automaticamente para que o professor possa visualizá-la, conferi-la e


clicar em um botão para confirmar, sendo então chamada a função verificaSituacao que retorna a
situação final do aluno. Quando a função é chamada, o endereço de retorno é colocado em uma ->
PILHA.
4) Quando o professor repete o procedimento do item três para todos os alunos, solicita que
seja gerado um relatório que constará o nome dos aprovados, caracterizando uma -> LISTA

“Uma fila é um tipo especial de Lista Linear em que as inserções são realizadas num extremo,
ficando as remoções restritas ao outro. O extremo onde os elementos são inseridos é denominado final
da fila, e aquele de onde são removidos é denominado começo da fila.”(PEREIRA, Silvio. L., 2004, p.
57)

A Fila, assim como a Pilha, também é um caso particular de Lista Linear e se diferencia dela
pela forma de acesso porque a inserção sempre acontece em uma extremidade e a remoção, em outra.
O conceito da Fila na programação tem a mesma filosofia que a fila do mundo real onde o
primeiro que chega é o primeiro a ser atendido.

Lembre-se da primeira aula quando Sr. Augusto chegou cedo ao aeroporto e, como foi o
primeiro da Fila, foi o primeiro a sair.

Este método é conhecido como First In, First Out(FIFO).

2 Fila simples

A fila, em inglês queue, possui duas funções básicas: enqueue(enfileira) de


dequeue(desenfileira).

Observe a figura abaixo.

A operação enqueue só poderá ser realizada se a fila não estiver cheia assim como a operação
dequeue, se não estiver vazia. Percebe-se então que, pelo menos, mais duas operações serão
necessárias.
As filas possuem uma operação semelhante a de uma pilha. A seguir, apresentaremos os
nomes pelos quais elas são reconhecidas, porém, você pode usar outra nomenclatura para as funções
que criar em seus programas:

Inicializa() ou Init() -> Inicializa os indicadores de início e fim da Fila

Enfileira() ou Enqueue() -> Insere elemento no final da Fila

Desenfileira() ou Dequeue() -> Remove o elemento do início da Fila

elemPrimeiro () ou firstElem() -> Retorna o primeiro elemento sem retirá-lo da Fila

verificaFilaCheia() ou isFull() -> Verifica se Fila está cheia

verificaFilaVazia() ou isEmpty() ->Verifica se Fila está vazia

3 Implementação
A implementação de filas pode ser feita por arrays (matrizes) ou ponteiros, mas, nesta aula,
porque ainda não tivemos a oportunidade de estudarmos ponteiros, implementaremos com matrizes e
structs.

A vantagem de começarmos usando matriz é sua fácil implementação, mas temos que ter
consciência das desvantagens, uma vez que, na alocação estática, precisaremos definir um espaço
suficientemente grande para armazenar um grande volume de dados. Além disso, indicadores de início
e fim da fila.

Os indicadores serão variáveis que sinalizarão o estado da Fila em relação à possibilidade, ou


não, da inserção de novos elementos.

O indicador de fim de Fila será incrementado toda vez que um elemento é inserido na Fila,
enquanto que o indicador de início de Fila será incrementado quando um elemento for removido,
sendo este um grande problema porque, após sucessivas remoções, teremos uma Fila com vários
nodos disponíveis no início, mas que não poderão ser ocupados.

Sei que você poderia pensar que poderia se fazer um deslocamento para esquerda de todos os
elementos, mas já pensou como seda isso se a Fila fosse enorme?

Este problema será resolvido na aula de hoje quando conseguirmos visualizar o array de forma
circular, mas vamos dar um passo de cada vez porque precisamos primeiro entender as operações
básicas da Fila.

Nós vamos construir trechos para uma Fila que poderá armazenar até 5 números inteiros.

Estarei apresentando, quando existir, o trecho que chama a função e a função.

Antes de apresentar as operações básicas, gostaria de mostrar a variável estrutura que foi
criada para trabalhar com a Fila.
Como no estudo da estrutura Pilha usei uma matriz e uma variável simples, achei que seria
mais produtivo implementar com variável estrutura que apresento abaixo.

4 lnicializar

A inicialização da Fila irá se resumir a comandos de atribuição, preparando-a para receber os


elementos que serão inseridos. Na Fila, os dois extremos são importantes e, por esta razão, estaremos
trabalhando com duas variáveis. Sendo assim, para inicializar uma Fila, precisaremos atribuir valores a
essas duas variáveis. Não se preocupe porque não inicializarei da mesma forma nos exemplos para que
você não ache que só existe uma maneira. Assim como na Pilha, inicializar não significa zerar todos
os elementos da Fila.

Neste exemplo, fila.fim foi inicializada com -1 porque é comum você encontrar assim com a
justificativa que em um primeiro momento fila.inicio seria igual a fila.fim, mas se você fizer os teste
de fila vazia e fila cheia de forma correta, não tem problema começar por 0 as duas varáveis.

Esta é uma operação básica da Fila. Para que possa


ser enfileirado um elemento, é preciso testar se existe lugar na Fila.

Verificar se a Fila está cheia poderia ser feito em separado, mas, assim como fiz na Pilha,
resolvi fazer neste trecho.
Atenção

A função recebe, por referência, o endereço da variável estrutura e como todas as variáveis
necessárias, inclusive o vetor, são membros das estruturas, tudo e passado de uma só vez, aumentando
a legibilidade.

Começa testando se a Fila está cheia e, se não estiver, o valor é solicitado, fil.fim é

incrementado e o valor armazenado na Fila.

É uma função de fácil implementação.


Acompanhe agora, na figura ao lado, supondo que você digitou cinco números para serem
enfileirados.

6 Desenfileirar (Dequeue)

Esta é outra operação básica da Fila. Para que possa ser desenfileirado um elemento, é preciso
testar se a Fila não está vazia.
A função recebe, por referência, o endereço da variável estrutura cujo um dos membros
contem a posição do elemento a ser “desenfileirado”.

Atenção

Observe que depois de exibir o conteúdo do elemento que era o primeiro da Fila, a variável
fil.inicio é incrementada.

Em nenhum momento foi removido o valor que estava armazenado na posição. Só o início que
se deslocou para a direita.

Assim como a função enfileirar, a função de desenfileirar é de fácil implementação e bem


parecida.

Acompanhe a agora, na figura abaixo, supondo que você escolheu desempilhar cinco números.
7 Primeiro elemento (firstElem)

Esta é uma função que possibilita que seja visualizado o primeiro elemento da Fila sem
“removê-lo”. Na verdade, você não precisaria criar esta função tal sua simplicidade. Poderia
simplesmente colocar no case. Só criei para modularizar todos os casos.

Como todas as seis operações já foram apresentadas, vou acrescentar mais uma. Nada
importante, mas que poderá ser útil em algum momento. Assim como a função anterior, você não
precisaria criá-la.
Atenção

Esta função testa se a fila está vazia, se não estiver, exibirá vários dados sobre ela a fim de que
você possa acompanhar melhor a execução do programa. Preste bem atenção às funções apresentadas
e você verá que elas são muito diferentes das que operam a pilha, afinal, ambas as estruturas são casos
particulares da Lista Linear.

Depois de termos visto as chamadas das funções e as funções, vamos analisar o código fonte e
acompanhar, rapidamente a execução do programa.

TELA PRINCIPAL
8 Aplicações com Filas

As Filas têm muitas aplicações e acho que todos concordam que o uso mais comum seja em
Sistemas Operacionais.

Alguns exemplos
Nesta aula, procurei selecionar exemplos interessantes para provar a importância desta
estrutura e simplifiquei as soluções para facilitar o entendimento de cada função. Entretanto, anexei os
originais para aqueles que têm mais experiência.

9 Uma aplicação – o programa da Agenda

O programa que será apresentado a seguir é uma adaptação do código fonte do livro C
Completo e Total de Herbert Schildt, cujo site está indicado no Aprenda Mais desta aula para que você
faça o download dos códigos. É um livro clássico e, nesta aula, selecionei dois programas dele.

Evidentemente, algumas modificações foram necessárias porque os códigos estavam escritos


na linguagem C e usavam matriz de ponteiros assunto que será estudado na próxima aula.

Para este exemplo, mais uma vez, considerei uma matriz de 5 elementos para que você possa
entender o funcionamento da aplicação.

1) A variável estrutura

2)

Como desejei, nesta aula, trabalhar com variável estrutura, me afastei um pouco do código.

As variáveis lpos e rpos, membros da variável estrutura serão explicadas logo a seguir.

3) Inicialização
3) Inserção de eventos na agenda

A função começa testando se a Fila está cheia e, caso não esteja, solicita a entrada do evento e
copia o evento na Fila, incrementando fil.lpos que sinaliza a próxima posição livre.

Não entendi esse return na função void. Será que poderia usar o else e não usar return?

Respondendo: A estrutura do if tem várias etapas na execução e quanto mais rápida for
executada, melhor.

Usando o if simples, em caso de verdade, o retorno é rápido e o fluxo do programa é mais


natural. Essa era uma

preocupação dos programadores antigamente, mas que ressurge. Sempre vale a pena ter um
programa eficiente sendo executado em menor tempo.

4) Remoção de eventos da agenda

Sabe-se que o único evento que pode ser “removido” é o que se encontra no início da Fila.

Coloco entre aspas porque não é, de fato, removido como você pode observar na figura que
desenfileirou, pois, deixei mais claro os blocos, para enfatizar isso.
5) Exibindo os eventos da agenda

Para que você pudesse acompanhar melhor a execução do programa, criei essa função, mas só
estou fazendo isso porque a implementação é com vetor e espero lhe ajudar a compreender melhor esta
estrutura que, como já disse, tem muitas aplicações.
10 Acompanhando a execução do programa

Considerações sobre o programa

Este programa da Agenda de Compromissos é uma boa aplicação para Fila, principalmente,
para quem está começando.

Ele será adaptado, nesta aula para Fila circular onde será resolvido o problema dos espaços
ociosos à medida que os elementos vão sendo “removidos”.
Depois de termos visto as chamadas das funções e as funções, vamos analisar o código fonte e
acompanhar, rapidamente a execução do programa.

11 Fila Circular

Atenção

As operações básicas da Fila Circular são as mesmas da Fila Simples, exceto pela
implementação do código, porque um vetor circular é algo que acontece no imaginário da
programação, não existindo fisicamente na Memória Principal, pois sua organização é linear. Você
poderá observar no código que a solução encontrada se baseou em uma variável, que é incrementada
cada vez que um elemento é inserido na Fila e decrementada cada vez que um elemento é removido.
Desta forma, enquanto esta variável não estiver com o valor máximo, elementos poderão ser inseridos.

1) A variável struct
Atenção

A função começa testando a variável fil.total para verificar se seu valor é igual ao máximo.

Caso não seja, o valor é solicitado e inserido na Fila. Depois, a variável fil.final é
incrementada e testada. Se atingir o valor final, será reinicializada. Lembre-se de que ela começou
com zero.

A variável fil.total é incrementada e, na próxima vez que se tentar inserir um elemento, a


mensagem Atencao. Fila Cheia, irá aparecer até que um elemento seja removido.

4) Desenfileirar (Dequeue)
Esta função também começa testando a variável fil.total para verificar se a Fila está vazia.

A variável fil.inicio é incrementada porque um elemento foi removido e fil.total,


decrementada.

Já falei nesta aula, que o trecho de remoção, não estava removendo valor da Fila, mas neste
exemplo, resolvi, toda vez que fosse removido um elemento, atribuir o valor –999 para que pudesse,
no próximo trecho, listar os elementos da Lista Circular para aqueles que dizem:” Só acredito vendo!

Não é para fazer disso um hábito, mas nunca se sabe se um dia você pode precisar usar algo
parecido, não é?

Meu objetivo foi didático.

5) Função lista

Somente a primeira linha de código do else seria necessária para esta função. Todo o conjunto
de comando foi acrescido para fins didáticos, visto que fica muito difícil visualizar, num primeiro
momento, o comportamento da Fila Circular.
Foi por esta razão que no trecho de remoção que atribui –999 à posição quando era
“removido” um elemento.
Aula 8 - ALOCAÇÃO DINÂMICA / LISTAS ENCADEADAS –
INTRODUÇÃO

1 Alocação Dinâmica

Durante todo esse tempo que estamos estudando Algoritmos e Estruturas de Dados, temos
determinado quanto de memória nossos programas vão usar através das declarações que fazemos das
variáveis locais, globais, matrizes, sejam elas de tipos primitivos ou de structs.

Sabemos que durante a execução de um programa, o Sistema Operacional, aloca espaço na


Memória Principal para as variáveis locais e globais, outro espaço é alocado para armazenar o código
do programa, outro espaço para armazenamento das funções e o que sobra, é chamado de Área de
memória Livre(free store) ou Heap que é usada para alocação dinâmica de memória. É bom frisar que
a área da Pilha cresce em direção ao Heap e o Heap, em direção à área da Pilha.

A figura abaixo representa essas quatro regiões da memória principal e não significa que
sempre será assim como afirma Schildt, H(1996, p.14):

“Embora a disposição física exata de cada uma das quatro regiões possa diferir entre tipos de
CPU e implementações...”
Operadores new e delete

Na linguagem C++, alocar dinamicamente é relativamente simples porque temos somente dois
operadores para alocar e desalocar memória.

Entretanto, é imprescindível o conhecimento sobre ponteiros.

No momento, só falaremos das funções desses operadores porque como usá-los, só depois que
estudarmos ponteiros.

Defendo a teoria de que não se pode saber superficialmente um conceito que permeia a
alocação dinâmica de memória.

Não seremos só programadores e, sendo assim, temos que entender o que se passa na memória
principal para que possamos desenvolver códigos sem problemas e mais eficientes. Se isso não fosse
verdade, nos nossos cursos não teríamos disciplinas tais como Organização de Computadores,
Sistemas Operacionais entre outras.

Não posso me aprofundar no estudo de ponteiros, mas vou tentar explicar de uma forma
simples para que você possa caminhar sozinho, se assim desejar.

A MP consiste de trilhões de posições para armazenamento do Sistema Operacional, dados,


programas, etc. Seu tamanho depende de quanto sua placa mãe suporta porque se foi o tempo em que
o dinheiro era o principal fator para se aumentar a memória, visto que hoje em dia ela está
relativamente barata.

Nós já vimos que, ao declararmos uma variável nas linguagens de alto nível e de nível
intermediário, o "nome" dado é associado a uma posição de memória (endereço). Este tipo de variável
que nós conhecemos, armazena dado.
Hoje vamos conhecer a variável ponteiro. Esta é uma variável que armazena o endereço de
outra variável, possibilitando receber o endereço que é retornado quando se aloca a memória
dinamicamente.

Atenção

Uma boa dica para entender como funciona a variável ponteiro.

Você leu no jornal um anúncio de uma loja que vende notebook, muito facilitado, para alunos
que estão cursando ADS ou Sistemas de Informação.

Ao ligar para a loja, você pede o endereço e anota na sua agenda. Então, sua agenda aponta
para a loja, visto que está anotado o endereço da loja (&loja).

Para dar uma olhada em outros modelos, você resolve ir até a loja(&loja) e chegando lá, viu o
que estava dentro da loja(*loja).

2 Declaração da variável ponteiro

Por que temos que usar o * antes do nome da variável ponteiro?

Porque o asterisco é o caracter(alguns autores chamam de operador) que sinaliza que a


variável está sendo declarada como ponteiro.
Qual a finalidade do caracter & antes de uma variável?

O operador & será usado antes de uma variável quando quisermos o endereço da variável e
não, seu conteúdo.

Como se inicializa uma variável ponteiro?

A inicialização poderá acontecer na declaração da mesma forma que fazemos com as variáveis
simples ou depois da declaração através de um comando de atribuição onde a variável ponteiro
receberá o endereço da variável que ela irá apontar.
Atenção

No exemplo anterior, inicializamos a variável ponteiro na declaração. Clique no link a seguir e


veja um exemplo após a declaração.

O asterisco será usado


em outra ocasião quando trabalharmos com variável ponteiro?

Sim, quando desejarmos saber o conteúdo do endereço apontado por uma variável ponteiro.

Atenção

No exemplo anterior, inicializamos a variável ponteiro na declaração. Clique no link a seguir e


veja um exemplo após a declaração.

3 Ponteiros para Estruturas

Nada nos impede de passarmos uma estrutura para uma função, entretanto isso sobrecarrega a
pilha, aumentando o tempo de transferência dos dados entre as funções.
Para minimizar esse problema e tendo em vista que já estudamos funções, vamos aprender
agora como passar somente um ponteiro como argumento para função, e, através dele, o endereço da
estrutura.

Saibam também que será de muita utilidade nas próximas aulas.

Existem duas formas de se acessar o membro através do ponteiro. Usando o operador -> ou o
*.
1) Na linha 5, foi criada uma estrutura global de nome data com três membros: dia, mes e ano.
2) Na linha 12, foram declaradas duas variáveis do tipo data. Uma de nome data dataNasc e
outra, ponteiro, de nome ptr.

3) Na linha 16, a variável ptr recebeu o endereço da variável dataNasc.

4) Usei a notação da seta para acessar os membros.

Neste próximo exemplo, além do que já foi visto no anterior, passaremos um ponteiro para
função.
4 Ponteiro e Alocação Dinâmica

No início desta aula, vimos que a alocação dinâmica da memória na linguagem C++ é feita
pelo operador new, mas como é retornado o primeiro endereço do bloco alocado, precisamos
armazenar esse endereço e é aí que está a importância, no processo, da variável ponteiro.

Declarando e Inicializando um ponteiro para alocar, dinamicamente, a memória.


5 Ponteiro e matriz de estruturas
6 Listas encadeadas

Nas aulas 5, 6 e 7 tivemos oportunidade de estudar as estruturas de dados lineares Lista, Pilhas
e Filas com seus elementos armazenados na memória de forma contígua e estática.

Aprendemos também que as inserções e remoções nas Pilhas e nas Filas acontecem em
extremidades determinadas enquanto que nas Listas, não existem regras sobre isso.

Na aula de hoje, começaremos a estudar as estruturas dinâmicas e, por essa razão, tivemos que
conhecer a variável ponteiro porque sem ela, seria impossível implementar a alocação dinâmica, visto
que o operador new, retorna, se bem sucedida a alocação, o primeiro endereço do bloco que precisa
ficar armazenado. Em resumo, sem fazer trocadilho, tudo muito bem encadeado: ponteiro -> alocação
dinâmica -> Listas Encadeadas.

Listas de encadeamento simples, normalmente chamadas simplesmente de Listas Encadeadas,


são compostas de elementos individuais, cada um ligado por um único ponteiro. Cada elemento
consiste de duas partes: um membro de dados e um ponteiro.(LOUDON, K, 2000, p.56)

As Listas Encadeadas, também conhecidas como listas ligadas, são consideradas uma das mais
importantes estruturas de dados. Talvez a forma livre de inserir e remover elementos em qualquer
posição e as aplicações importantes que fazem uso dela tenham lhe dado esse status.
Podemos enumerar algumas dessas aplicações:

As Listas Encadeadas são estruturas dinâmicas e, por essa razão, não existe restrição, a não ser
de memória, de aumentar, ou diminuir, de tamanho durante a execução do programa.

As Listas Encadeadas podem ser:


Pela figura, podemos observar que cada nó tem duas partes. Uma, armazena a informação e a
outra, o endereço do próximo

Se olharmos para este nó, podemos entendê-lo como uma estrutura, visto que ele é formado
por dois tipos diferentes, sendo um, necessariamente um ponteiro.

Se formos audaciosos, diríamos que é uma variável struct com dois membros e, para
chegarmos mais próximos do nó da Lista, poderíamos nomear esta estrutura de no.

O tipo do membro que armazena a informação não será difícil de identificarmos porque
dependerá da aplicação, mas qual será o tipo da variável ponteiro?

No nosso breve estudo sobre ponteiros, aprendemos que o tipo do ponteiro tem que ser do
mesmo tipo que o elemento que ele vai apontar.
Concordo que é meio complexo esse questionamento, mas se você fizer algumas leituras desse
parágrafo, vais acabar entendendo que não tinha alternativa já que o ponteiro tinha que ser do mesmo
tipo que o nó.

Acredito que a única dificuldade ficará por conta de permitir que esta definição possa
acontecer dentro dela mesma. Como DROZDEK, A(2002,p.68) diz: “Essa circularidade, no entanto, é
permitida em C++”.

Agora, vamos construir a struct.


Aula 9 -LISTAS ENCADEADAS – FINALIZANDO / PILHAS E
FILAS DINÂMICAS

1 Encadear para Dinamizar

Corrigindo as Tarefas da aula anterior:

Na Aula 8, deixei uma tarefa para você. Gostaria de lembrar que a solução que irei apresentar
não significa a única correta. A sua poderá estar melhor do que a minha. Sempre digo que não existe
uma única solução. Mas, vamos lá.
2 As funções
Aula 10 - LISTAS DUPLAMENTE ENCADEADAS

1 Tema: Duplamente encadear para garantir e flexibilizar

As Listas Duplamente Encadeadas são compostas de nós ligados por dois ponteiros.
Pela figura, podemos observar que o ponteiro ant aponta para o nó que o precede. Enquanto
que o ponteiro prox, aponta para o nó que o sucede. Sendo assim, se desejarmos varrer a lista de trás
para frente, faremos uso do ponteiro ant caso contrário, usaremos o ponteiro prox.

O fato de existir um ponteiro a mais em cada nodo, implicando em um novo membro implica
em um uso maior de memória falarão alguns, mas isso é recompensado porque torna o processamento
da Lista Duplamente Encadeada mais flexível do que a Lista Simplesmente Encadeada.

Uma das maiores dificuldades na Listas Simplesmente Encadeada era a remoção de um nó


porque, como não tínhamos como acessar o nó anterior, era necessário percorrer a lista toda para
localizá-lo.

Com dois ponteiros, podemos, facilmente, sabendo a localização do nó que queremos


remover, acessar o nó anterior e o posterior e, dessa forma, atualizar a Lista, após a remoção como
podemos constatar na função remover do programa que será mostrado.

Mas é bom reforçar que, para sabermos a localização do nó a ser removido, precisaremos fazer
uma busca na lista, implicando na criação de uma função de busca sequencial.

Vamos analisar como seria a remoção de um nó que se encontra no meio através da figura
abaixo e de dois comandos que considero de maior dificuldade na função remover.
1) Através de seu ponteiro prox, p apontava para o próximo nó cuja representação é: p->prox.

2) Esse endereço foi copiado para o ponteiro prox do nó anterior acessado por p->ant->prox.
(linha verde)

3) Sendo assim, após a remoção de p, p->ant->prox apontará para o nó seguinte ao que foi
removido. (seta azul)

O mesmo procedimento se repetiu para ajustar o outro ponteiro.

Atenção

Não se esqueça que, embora não seja comum usar, ao invés de usarmos a seta, poderemos
usar: (`nomeDoPonteiro).membro

Logo: P->prox é equivalente à (*p).prox.

Todas as operações realizadas com as Listas Simplesmente Encadeadas poderão ser realizadas
com as Duplamente Encadeadas e algumas, com mais facilidade.
Como na aula 8, já falamos sobre alocação dinâmica de Memória, sobre Lista Encadeada,
explicamos da necessidade do ponteiro, constato que não temos muito que acrescentar de teoria nesta
aula. Sendo assim, vou apresentar nosso último programa, evidentemente com menu como gosto e
com algumas funções que considero básicas e que já fizemos com Listas simplesmente Encadeadas.

Como o código é muito grande, clique no link a seguir para visualizá-lo:

Você também pode gostar