Buscar

Apostila_AEDsII_ate_lista_encadeada_simples

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

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

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

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

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

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

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

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

AEDsII 
Material de uso restrito para a Disciplina de 
Algoritmos e Estruturas de Dados II do Curso de 
Engenharia Elétrica do UNI-BH 
 
 
 
 
 
 
 
 
 
 
 
Prof. Eduardo de Queiroz Braga 
 
 
 
2 
 
Índice: 
 
Índice: ------------------------------------------------------------------------------------------------------------ 2 
1. Ponteiros ------------------------------------------------------------------------------------------------ 3 
1.1. Operações com Ponteiros ------------------------------------------------------------------------ 4 
1.2. Aritmética de Ponteiros --------------------------------------------------------------------------- 5 
1.3. Acessando os Endereços ------------------------------------------------------------------------ 5 
1.4. Ponteiros e Vetores -------------------------------------------------------------------------------- 8 
1.5. Ponteiros e Matrizes ------------------------------------------------------------------------------ 11 
1.6. Ponteiros e Strings -------------------------------------------------------------------------------- 14 
1.7. Estruturas e Ponteiros para Estruturas ------------------------------------------------------- 15 
1.8. Ponteiros para Funções ------------------------------------------------------------------------- 24 
2. A Função main() e seus argumentos ------------------------------------------------------------ 25 
2.1. A função void main(int argc, char *argv[]) -------------------------------------------------- 25 
3. Alocação Dinâmica de Memória ------------------------------------------------------------------ 26 
4. Algoritmos de Ordenação e Pesquisa----------------------------------------------------------- 28 
4.2. Ordenação Bolha ---------------------------------------------------------------------------------- 28 
4.3. Ordenação por seleção -------------------------------------------------------------------------- 29 
4.4. Ordenação por inserção ------------------------------------------------------------------------- 29 
4.5. Ordenação Quicksort ----------------------------------------------------------------------------- 30 
5. Estruturas de Dados -------------------------------------------------------------------------------- 32 
5.1. Filas -------------------------------------------------------------------------------------------------- 32 
5.2. Filas Circulares ------------------------------------------------------------------------------------ 34 
5.3. Pilhas de dados ------------------------------------------------------------------------------------ 36 
5.4. Listas de dados com encadeamento --------------------------------------------------------- 37 
5.5. Árvores e Árvores Binárias ---------------------------------------------------------------------- 40 
5.6. Matrizes Esparsas -------------------------------------------------------------------------------- 40 
 
 
 
 
 
 
 
3 
 
1. Ponteiros 
Ponteiros são usados em situações em que é necessário conhecer o endereço onde está 
armazenada a variável e não o seu conteúdo. Um ponteiro é uma variável que contém um 
endereço de memória e não o conteúdo da posição. 
A memória de um computador pode ser vista como uma seqüência de bytes, cada um com 
seu próprio endereço. Não há dois bytes com o mesmo endereço. O primeiro endereço 
é sempre 0 e o último geralmente é uma potência de 2. Por exemplo, um computador com 
memória igual a 16 Mbytes tem 16x1024x1024 bytes. 
A Tabela 1 mostra um exemplo de um trecho de memória que contém duas variáveis num e 
res inteiras de tipo longo (4 bytes cada uma). Observar que os endereços estão pulando de 
quatro em quatro já que as variáveis são inteiras de tipo longo. Uma possível declaração 
destas variáveis dentro de um programa C/C++ poderia ser: 
long int num=10, res=120; 
 
Tabela 1: Mapa de Memória 
Endereços (em decimal) Conteúdo Variável 
996 --- --- 
1000 10 num 
1004 120 res 
Uma função pode receber os ponteiros que apontem para os endereços dos parâmetros. 
Assim, esta função pode modificar diretamente os conteúdos destas variáveis. Ou seja, 
ponteiros fornecem os meios pelos quais as funções podem modificar seus argumentos. 
 Uma aplicação importante de ponteiros é apontar para áreas de memória que são 
administradas durante a execução do programa. Com ponteiros é possível alocar as 
posições de memória necessárias para armazenamento de vetores somente quando o 
programa estiver rodando. O programador pode reservar o número exato de posições que 
o programa requer. 
Ponteiros são um dos aspectos mais fortes e mais perigosos de C/C++. Por exemplo, 
ponteiros não inicializados, também chamados de ponteiros selvagens, podem provocar 
uma quebra no sistema. 
 
 
 
 
 
 
 
4 
 
1.1. Operações com Ponteiros 
1.1.1. Declaração de Ponteiros 
Antes de serem usados os ponteiros precisam ser declarados, assim como variáveis. A 
forma geral da declaração de um ponteiro é a seguinte: 
 
tipo *nome; 
 
Onde tipo é qualquer tipo válido em C/C++ e nome é o nome da variável ponteiro. Por 
exemplo: 
 
int *res; /* ponteiro para uma variavel inteira */ 
float *div; /* ponteiro para uma variavel de ponto flutuante */ 
 
 
1.1.2. Os Operadores de Ponteiros 
Existem dois operadores especiais para ponteiros: * e &. Os dois operadores são unários, 
isto é, requerem somente um operando. 
 O operador & devolve o endereço de memória do seu operando. Por exemplo, 
pint = &soma; /* o endereco de soma e carregado em pint */ 
 
No exemplo seguinte considere a Tabela 1. Após a execução do trecho de programa 
abaixo a variável ponteiro p termina com o valor 1000. 
 
p = # 
 
O operador * é o complemento de &. O operador * devolve o valor da variável localizada no 
endereço que o segue. Por exemplo, o comando 
 
num = *p; 
significa que a variável num recebe o valor apontado por p. Estes operadores não devem 
ser confundidos com os já estudados em capítulos anteriores. O operador * para 
ponteiros não tem nada a ver com o operador multiplicação. O operador ponteiro * é 
 
 
5 
 
unário e, como o operador &, tem precedência maior que do que todos os operadores 
aritméticos. 
 
Observação: Nunca acesse o conteúdo de um ponteiro antes de inicializá-lo. 
 
1.2. Aritmética de Ponteiros 
Atribuição de Ponteiros 
 Do mesmo modo que uma variável comum, conteúdo de um ponteiro pode ser passado 
para outro ponteiro do mesmo tipo. As variáveis ponteiros devem sempre apontar para os 
tipos de dados corretos. Uma variável ponteiro declarada como apontador de dados 
inteiros deve sempre apontar para dados deste tipo. 
Observar que em C/C++ possível atribuir qualquer endereço a uma variável ponteiro. Deste 
modo é possível atribuir o endereço de uma variável do tipo float a um ponteiro inteiro. No 
entanto, o programa não irá funcionar da maneira correta. 
Por exemplo, no trecho de programa abaixo o endereço do terceiro elemento do vetor v é 
carregado em p1 e o endereço da variável i é carregado em p2. Além disso, no final o 
endereço apontado por p1 é carregado em p2. Os comandos cout imprimem os valores 
apontados pelos ponteiros respectivos. 
#include<iostream> 
#include<cstdlib> 
 
using namespace std; 
 
int main() 
{ 
 int vetor[] = { 10, 20, 30, 40, 50 }; 
 int *p1, *p2; 
 int i = 100; 
 
 p1 = &vetor[2]; 
 cout<<"*p1 = "<<*p1<<endl; 
 p2 = &i; 
 cout<<"*p2 = "<<*p2<<endl; 
 p2 = p1; 
 cout<<"*p2 = "<<*p2<<endl; 
 
 system("Pause"); 
 return 0; 
} 
1.3. Acessando os Endereços 
O programa abaixo faz com que o endereço da variável x seja carregado no ponteiro p. Em 
seguida o programa imprime o que está apontado por p. 
 
 
6 
 
#include<iostream>#include<cstdlib> 
 
using namespace std; 
 
int main() 
{ 
 float x=3.14, *p; 
 
 p = &x; //Ponteiro que carrega o endereço da variável x 
 cout<<"*p = "<<*p<<endl;; 
 
 system("Pause"); 
 return 0; 
} 
 
 
 
1.3.1. Incrementando e Decrementando Ponteiros 
 O exemplo abaixo mostra que operações de incremento e decremento podem ser 
aplicadas em operandos. O primeiro cout imprime 30 o segundo 40 e o terceiro 50. 
int main() 
{ 
 int vetor[] = { 10, 20, 30, 40, 50 }; 
 int *p1; 
 
 p1 = &vetor[2]; 
 cout<<*p1<<endl; 
 p1++; 
 cout<<*p1<<endl; 
 p1 = p1 + 1; 
 cout<<*p1<<endl; 
 
 system("Pause"); 
 return 0; 
} 
 
Pode parecer estranho que um endereço que aponte para um número inteiro, que é 
armazenado em quatro bytes, seja incrementado por um e passe para apontar para o 
próximo número inteiro. A resposta para isto é que sempre que um ponteiro é 
incrementado (ou decrementado) ele passa a apontar para a posição do elemento seguinte 
(ou anterior). Do mesmo modo somar três a um ponteiro, faz com que ele passe apontar 
para o terceiro elemento após o atual. Portanto, um incremento em um ponteiro que aponta 
para um valor que é armazenado em n bytes faz que n seja somado ao endereço. 
 É possível se usar o seguinte comando 
*(p+1)=10; 
 
 
 
7 
 
Este comando armazena o valor 10 na posição seguinte àquela apontada por p. É possível 
somar-se e subtrair-se inteiros de ponteiros. A operação abaixo faz com que o ponteiro p 
passe a apontar para o terceiro elemento após o atual. 
p = p + 3; 
 
A diferença entre ponteiros fornece quantos elementos do tipo do ponteiro existem entre os 
dois ponteiros. No exemplo abaixo é impresso o valor 3. 
int main() 
{ 
 float vetor[] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; 
 float *p1, *p2; 
 
 p1 = &vetor[2]; // endereco do terceiro elemento 
 p2 = &vetor; // endereco do primeiro elemento 
 cout<<"Diferenca entre ponteiros %d\n"<<p1-p2; 
 
 system("Pause"); 
 return 0; 
} 
 
Observação: Não é possível multiplicar ou dividir ponteiros, e não se pode adicionar ou 
subtrair o tipo float ou o tipo double a ponteiros. 
 
 
 
1.3.2. Comparação de Ponteiros 
É possível comparar ponteiros em uma expressão relacional. Mas, só podemos comparar 
ponteiros de mesmo tipo. O trecho de programa abaixo ilustra um exemplo deste tipo de 
operações. 
char *c, *v; 
 
cin>>*c>>*v; 
if (c == v) // Cuidado com esse tipo de comparação!!! 
 cout<<"As variáveis estao na mesma posicao.\n"; 
else 
 cout<<"As variaveis nao estao na mesma posicao.\n"; 
 
 
1.3.3. Indireção Multipla 
 
 
 
8 
 
Ponteiros podem apontar para outro ponteiro que aponta para o valor final. Essa 
situação é chamada de indireção múltipla, ou também de ponteiros para ponteiros. O 
valor de um ponteiro convencional é o endereço de uma variável que contém um 
determinado valor. Já para o caso de um ponteiro para ponteiro, o primeiro ponteiro 
contém o endereço do segundo ponteiro, que finalmente contém o endereço da variável 
que contém o valor desejado. Observe a figura. 
 
 
Figura 1: Indireção Simples e Múltipla 
 
 
 
Exemplo: Observe o código abaixo: 
 
#include <stdio.h> 
 
Void main(void) 
{ 
 int x, *p, **q; 
 x=10; 
 p=&x; 
 q=&p; 
 cout < “O valor de x eh ”<<**q; 
} 
 
Observamos que o valor 10 é atribuído à variável x. Já p é um ponteiro do tipo int que 
passa a conter o endereço da variável x. Por outro lado, q é um ponteiro do tipo int que 
contém o endereço do ponteiro p. Isso é um caso de indireção múltipla. 
 
1.4. Ponteiros e Vetores 
 
Ponteiros e Vetores estão fortemente relacionados na linguagem C/C++. O nome de um 
vetor é um ponteiro que aponta para a primeira posição do vetor e todas as operações já 
mencionadas para ponteiros podem ser executadas com um nome de vetor. Por exemplo, 
a declaração: 
 
int v[100]; 
 
 
9 
 
 
declara um vetor de inteiros de 100 posições. Se p recebe o ponteiro para a primeira 
posição de v como na declaração abaixo: 
 
int *p=v; 
As seguintes declarações serão idênticas: 
v[i] == *(p+i) 
&v[i] == p+i 
 
Também são idênticas as próximas declarações: 
 
v[i] == *(v+i) 
&v[i] == v+i 
O exemplo ilustrado abaixo mostra as duas notações sendo usadas para imprimir o mesmo 
vetor. 
int main() 
{ 
 float v[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; 
 int i; 
 for (i=0; i<9; i++) cout<<v[i]; 
 cout<<endl; 
 for (i=0; i<9; i++) cout<<*(v+i); 
 cout<<endl; 
 
 system("Pause"); 
 return 0; 
} 
Existe uma diferença fundamental entre declarar um conjunto de dados como um vetor ou 
através de um ponteiro. Na declaração de vetor, o compilador automaticamente reserva um 
bloco de memória para que o vetor seja armazenado. Quando apenas um ponteiro é 
declarado a única coisa que o compilador faz é alocar um ponteiro para apontar para a 
memória, sem que espaço seja reservado. 
O nome de um vetor é chamado de ponteiro constante e, portanto, não pode ter o seu 
valor alterado. Assim, os comandos abaixo não são válidos: 
 
int main() 
{ 
 int list[5], i; 
 
 list = &i; // O ponteiro list nao pode ser modificado 
 //recebendo o endereco de i 
 
 
10 
 
 
 list++; // O ponteiro list nao pode ser incrementado 
 
} 
Para percorrer um vetor além da maneira mostrada no exemplo é possível usar um 
ponteiro variável como ilustrado no exemplo abaixo. 
int main() 
{ 
 
 float v[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; 
 int i; 
 float *p; 
 
 for(i=0; i<9; i++) cout<<v[i]<<" "; 
 cout<<endl; 
 for(i=0; i<9; i++) cout<<*(v+i)<<" "; 
 cout<<endl; 
 for(i=0, p=v; i<9; i++, p++) cout<<*p<<" "; 
} 
Observe como o ponteiro p recebe seu valor inicial e a maneira que ele é incrementado. 
 
 
 
11 
 
1.5. Ponteiros e Matrizes 
Um ponteiro aponta para uma área de memória que é endereçada de maneira linear. Deste 
modo, é necessário mapear o endereço de cada elemento na matriz, que é dado por linha 
coluna, em um endereço linear. 
Considere uma matriz chamada matriz de tamanho LIN,COL que poderia ser declarada e 
ter um de seus elementos lidos da seguinte maneira: 
#include<iostream> 
#include<cstdlib> 
 
using namespace std; 
 
const int LIN=3; 
const int COL=4; 
 
void leMatriz(float m[LIN][COL]); 
void imprimeMatriz(float m[LIN][COL]); 
 
int main() 
{ 
 float matriz[LIN][COL]; 
 
 cout<<"Entre com os dados da Matriz: \n"; 
 leMatriz(matriz); 
 
 cout<<"\nMatriz digitada: \n"; 
 imprimeMatriz(matriz); 
 cout<<endl; 
 
 system("pause"); 
 return 0; 
} 
void leMatriz(float m[LIN][COL]) 
{ 
 for(int i=0; i<LIN; i++) 
 { 
 for(int j=0; j<COL; j++) 
 { 
 cout<<"Elemento ["<<i<<"]["<<j<<"]: "; 
 cin>>m[i][j]; 
 } 
 } 
} 
 
void imprimeMatriz(float m[LIN][COL]) 
{ 
 for(int i=0; i<LIN; i++) 
 { 
 for(int j=0; j<COL; j++) 
 cout<<m[i][j]<<" "; 
 cout<<endl; 
 } 
 
 
12 
 
} 
Caso o programa utilizasse ponteiros ao invés de notação de matrizes constantes o 
programa ficaria maneira apresentada a seguir. 
Neste exemplo iremos criar um vetor de ponteiros que irá armazenar o endereço inicial de 
cada linha. Portanto, para obter um elemento da matriz primeiro devemos descobrir onde 
está a linha no vetor que armazena os endereços das linhas, em seguida procuramos na 
linha o elemento. A figura 1 ilustra como será feito o armazenamento desta matriz. 
 
Figura 2: Exemplo de implementação de Matriz 
#include<iostream> 
#include<cstdlib> 
 
usingnamespace std; 
 
void alocaMatriz(float **&m, int l, int c); 
void desalocaMatriz(float **&m, int lin); 
void leMatriz(float **m, int l, int c); 
void imprimeMatriz(float **m, int l, int c); 
 
int main() 
{ 
 int lin, col; 
 float **matriz; 
 
 cout<<"Entre com numero de linhas da Matriz: "; 
 cin>>lin; 
 while (lin<=0) 
 { 
 cout<<"Numero invalido! Digite valor maior que zero!"; 
 cout<<"Entre com numero de linhas da Matriz: "; 
 cin>>lin; 
 } 
 
 
 
13 
 
 
 cout<<"Entre com numero de colunas da Matriz: "; 
 cin>>col; 
 while (col<=0) 
 { 
 cout<<"Numero invalido! Digite valor maior que zero!"; 
 cout<<"Entre com numero de colunas da Matriz: "; 
 cin>>col; 
 } 
 
 alocaMatriz(matriz, lin, col); 
 
 cout<<"Entre com os dados da Matriz: \n"; 
 leMatriz(matriz, lin, col); 
 
 cout<<"\nMatriz digitada: \n"; 
 imprimeMatriz(matriz, lin, col); 
 cout<<endl; 
 
 desalocaMatriz(matriz, lin); 
 
 system("pause"); 
 return 0; 
} 
 
 
 
 
void alocaMatriz(float **&m, int l, int c) 
{ 
 //Aloca primeiro as linhas 
 m = new float*[l]; 
 
 //Aloca depois as colunas 
 for (int i=0; i<l; i++) 
 m[i]=new float[c]; 
} 
 
void desalocaMatriz(float **&m, int l) 
{ 
 //Desaloca primeiro as colunas 
 for (int i=0; i<l; i++) 
 delete [] m; 
 //Desaloca as linhas 
 delete [] m; 
 m=NULL; 
} 
 
void leMatriz(float **m, int l, int c) 
{ 
 for(int i=0; i<l; i++) 
 { 
 for(int j=0; j<c; j++) 
 { 
 cout<<"Elemento ["<<i<<"]["<<j<<"]: "; 
 cin>>m[i][j]; 
 
 
14 
 
 } 
 } 
} 
 
 
void imprimeMatriz(float **m, int l, int c) 
{ 
 for(int i=0; i<l; i++) 
 { 
 for(int j=0; j<c; j++) 
 cout<<m[i][j]<<" "; 
 cout<<endl; 
 } 
} 
1.6. Ponteiros e Strings 
Um string constante é escrito como no exemplo: 
 "Este e um string". 
Até agora um dos usos mais comuns de strings constantes tem sido na função cout, como 
no exemplo abaixo 
cout<<"Acabou o programa.\n"; 
 
Quando uma string como este é enviado para a função o que é passado é o ponteiro para 
a string. No exemplo abaixo aparece uma função que conta o número de caracteres de um 
string. Observe o ponteiro para o string constante e na função o ponteiro *(s+tam++) 
apontando caractere a caractere. 
 
#include<iostream> 
#include<cstdlib> 
 
using namespace std; 
 
int strtam(const char *s); // Prototipo de função 
 
int main() 
{ 
 char lista[]="Isto e um teste!"; 
 
 cout<<"O tamanho do string \""<<lista<<"\" e "; 
 cout<<strtam(lista)<<" caracteres.\n"; 
 
 system("pause"); 
 return 0; 
} 
 
int strtam(const char *s) 
{ 
 
 
15 
 
 int tam=0; 
 while(*(s + tam++) != '\0'); 
 return (tam-1); 
} 
 
Outro exemplo é ilustrado abaixo. E mostra uma função que copia uma string para outra. 
#include<iostream> 
#include<cstdlib> 
 
using namespace std; 
 
int strcop(char *d, const char *o); 
 
int main() 
{ 
 char destino[20]; 
 char origem[]="string de origem"; 
 
 strcop(destino, origem);//copia o string origem para o destino 
 cout<<"Origem: "<<origem<<endl; 
 cout<<"Destino: "<<destino<<endl; 
 
 system("pause"); 
 return 0; 
} 
 
 
int strcop(char *d, const char *o) 
{ 
 while ((*d++ = *o++) != '\0'); 
 return 1; 
} 
1.7. Estruturas e Ponteiros para Estruturas 
Uma estrutura agrupa várias variáveis numa só. Imagine uma ficha pessoal que 
contenha nome, telefone, endereço, etc. A ficha é uma estrutura onde cada campo será um 
tipo de dado diferente. A estrutura é um conjunto de dados não homogêneos ou um 
agrupamento de dados não similares, para formar um novo tipo de dados. Estruturas 
constituem um recurso importante para organizar os dados utilizados por um programa 
graças à possibilidade de tratar um grupo de valores como uma única variável. 
1.7.1. Criando uma Estrutura 
Para se criar uma estrutura usa-se o comando struct. Sua forma geral é: 
struct nome_do_tipo_da_estrutura 
{ 
tipo_1 nome_1; 
tipo_2 nome_2; 
... 
 
 
16 
 
tipo_n nome_n; 
}; 
A string após o comando struct representa o nome. Esta não é a variável ainda, 
mas o tipo que ela terá quando for declarada. Ou seja, o nome_do_tipo_da_estrutura é o 
nome que se dará para a estrutura. Um primeiro exemplo: 
struct est 
{ 
int i; 
float f; 
}; 
 
est a, b; 
Neste caso, est é o nome do tipo de uma estrutura de dados que conterá as 
variáveis i e f. Foram também declaradas duas variáveis, a e b. Elas são estruturas do 
tipo est. Isto é, a possui os campos i e f, o mesmo acontecendo com b. 
Vamos criar uma estrutura de endereço: 
struct tipo_endereco 
{ 
 string rua; 
 int numero; 
 string bairro; 
 string cidade; 
 string sigla_estado; 
 long int CEP; 
}; 
Vamos agora criar uma estrutura chamada ficha_pessoal com os dados pessoais: 
struct ficha_pessoal 
{ 
 string nome; 
 long int telefone; 
 tipo_endereco endereco; 
}; 
Observe que foi declarada dentro da estrutura ficha_pessoal os campos nome, 
telefone e endereço. Esta última é uma variável chamada endereço, que é uma estrutura 
do tidp tipo_endereco, ou seja, que contém os campos rua, numero, etc. Vemos que 
uma estrutura pode fazer parte de outra ( a estrutura tipo_endereco é usada pela 
estrutura ficha_pessoal). 
Vamos agora utilizar as estruturas declaradas na seção anterior para escrever um 
programa que preencha uma ficha. 
#include <iostream> 
 
 
17 
 
#include <cstdlib> 
#include <string> 
 
using namespace std; 
 
struct tipo_endereco 
{ 
 string rua; 
 int numero; 
 string bairro; 
 string cidade; 
 string sigla_estado; 
 long int CEP; 
}; 
 
 
 
 
struct ficha_pessoal 
{ 
 string nome; 
 long int telefone; 
 tipo_endereco endereco; 
}; 
 
int main () 
{ 
 ficha_pessoal ficha; 
 ficha.nome="Fulano de Tal"; 
 ficha.telefone=4921234; 
 ficha.endereco.rua="Rua das Flores"; 
 ficha.endereco.numero=10; 
 ficha.endereco.bairro="Cidade Velha"; 
 ficha.endereco.cidade="Belo Horizonte"; 
 ficha.endereco.sigla_estado="MG"; 
 ficha.endereco.CEP=31340230; 
 
cout<<endl<<"\t**** Dados da ficha pessoal ****"; 
cout<<endl<<endl; 
cout<<"Nome:\t"<<ficha.nome<<endl; 
 cout<<"Tel:\t"<<ficha.telefone<<endl; 
 cout<<"Rua:\t"<<ficha.endereco.rua<<endl; 
 cout<<"Numero:\t"<<ficha.endereco.numero<<endl; 
 cout<<"Bairro:\t"<<ficha.endereco.bairro<<endl; 
 cout<<"Cidade:\t"<<ficha.endereco.cidade<<endl; 
 cout<<"Estado:\t"<<ficha.endereco.sigla_estado<<endl; 
 cout<<"CEP:\t"<<ficha.endereco.CEP<<endl<<endl; 
 
 system("pause"); 
 return 0; 
} 
O programa declara a variável ficha do tipo ficha_pessoal e preenche os seus 
campos. O exemplo mostra como podemos acessar um elemento de uma estrutura: basta 
usar o ponto (.). Assim, para acessar o campo telefone de ficha, escrevemos: 
 
 
18 
 
ficha.telefone = 4921234; 
Como a struct ficha pessoal possui um campo, endereco, que também é uma 
struct mas do tipo tipo_endereco, podemos fazer acesso aos campos desta struct interna 
da seguinte maneira: 
ficha.endereco.numero = 10; 
ficha.endereco.CEP = 31340230; 
Desta forma, estamos acessando, primeiramente, o campo endereco da struct ficha 
e, dentro deste campo, estamos acessando os campos numero e CEP. 
1.7.2. Matrizes de estruturas 
Uma estrutura é como qualquer outro tipo de dado no C/C++. Podemos, portanto, 
criar matrizes de estruturas. Vamos ver como ficaria a declaração de umvetor de 100 
fichas pessoais: 
ficha_pessoal fichas [100]; 
Poderíamos então acessar a segunda letra da sigla de estado da décima terceira 
ficha fazendo: 
fichas[12].endereco.sigla_estado[1]; 
Analise atentamente como isto está sendo feito. 
Podemos atribuir duas estruturas que sejam do mesmo tipo. O C irá, neste caso, 
copiar uma estrutura, campo por campo, na outra. Veja o programa abaixo: 
#include <iostream> 
#include <cstdlib> 
#include <string> 
using namespace std; 
 
struct est1 { 
 int i; 
 float f; 
}; 
 
int main () 
{ 
 // Declara primeira e segunda como structs do tipo est1 
 est1 primeira, segunda; 
 
 primeira.i = 10; 
 primeira.f = 3.1415; 
 segunda = primeira; // A segunda struct e' agora igual a primeira 
 cout<<"Os valores armazenasdos na segunda struct sao: "; 
 cout<<segunda.i<<" e "<<segunda.f<<endl; 
 
 system("pause"); 
 
 
19 
 
 return 0; 
} 
São declaradas duas estruturas do tipo est1, uma chamada primeira e outra 
chamada segunda. Atribuem-se valores aos dois campos da struct primeira. Os valores 
de primeira são copiados na estrutura segunda apenas com a expressão de atribuição: 
segunda = primeira; 
Todos os campos de primeira serão copiados na segunda. Note que isto é 
diferente do que acontecia em vetores, onde, para fazer a cópia dos elementos de um 
vetor em outro, tínhamos que copiar elemento por elemento do vetor. Para structs é muito 
mais fácil! 
Porém, devemos tomar cuidado na atribuição de structs que contenham campos 
ponteiros. Veja abaixo: 
/* Este programa é um exemplo INCORRETO para atribuir 
 estruturas que contenham ponteiros */ 
 
#include <iostream> 
#include <cstdlib> 
#include <string.h> 
using namespace std; 
 
//Define a estrutura tipo_end com dois atributos 
struct tipo_end 
{ 
 char *rua; //A struct possui um campo que é um ponteiro 
 int numero; 
}; 
 
//Função Principal 
int main() 
{ 
 tipo_end end1, end2; 
 char buffer[50]; 
 
 cout<<"\nEntre o nome da rua:"; 
 gets(buffer); // Le o nome da rua em uma string de buffer 
 
 /* Aloca a quantidade de memoria suficiente 
 para armazenar a string */ 
 end1.rua = new char[strlen(buffer)+1]; 
 strcpy(end1.rua, buffer); // Copia a string 
 cout<<"\nEntre o numero:"; 
 cin>>end1.numero; 
 
 /* ERRADO end2.rua e end1.rua estao apontando 
 para a mesma regiao de memoria */ 
 end2 = end1; 
 
 cout<<"Depois da atribuicao:\n Endereco em end1 "; 
 cout<<end1.rua<<" "<<end1.numero; 
 
 
20 
 
 cout<<"\n Endereco em end2 "<<end2.rua<<" "<<end2.numero; 
 
 /* Uma modificacao na memoria apontada por end2.rua causara' 
 a modificacao do que e' apontado por end1.rua, o que, 
 esta' errado !!! */ 
 strcpy(end2.rua, "Rua Mesquita"); 
 
 end2.numero = 1100; //Nesta atribuicao nao ha problemas 
 
 cout<<" \n\nApos modificar o endereco em end2:\n "; 
 cout<<"Endereco em end1 "<<end1.rua<<" "<<end1.numero; 
 cout<<"\n Endereco em end2 "<<end2.rua<<" "<<end2.numero; 
 
 cout<<endl<<endl; 
 system("pause"); 
 return 0; 
} 
Neste programa há um ERRO GRAVE, pois ao se fazer a atribuição end2 = end1, o 
campo rua de end2 estará apontando para a mesma posição de memória que o campo rua 
de end1. Assim, ao se modificar o conteúdo apontado por end2.rua estaremos também 
modificando o conteúdo apontado por end1.rua !!! 
1.7.3. Passando estruturas como parâmetros de funções 
No exemplo apresentado no ítem usando, vimos o seguinte comando: 
strcpy (ficha.nome,"Luiz Osvaldo Silva"); 
Neste comando um elemento de uma estrutura é passado para a função strcpy, que 
vai copiar a string “Fulano de Tal” para a variável nome (campo dentro da estrutura ficha). 
Podemos também passar uma estrutura inteira como argumento para uma função. 
Veja a seguinte função: 
void PreencheFicha (ficha_pessoal ficha) 
{ 
... 
} 
Como vemos acima é fácil passar a estrutura como um todo para a função. 
Devemos observar que, como em qualquer outra função no C/C++, a passagem da 
estrutura é feita por valor. A estrutura que está sendo passada, vai ser copiada, campo por 
campo, em uma variável local da função PreencheFicha. Isto significa que alterações na 
estrutura dentro da função não terão efeito na variável fora da função. Mais uma vez 
podemos contornar este detalhe usando ponteiros e passando para a função um ponteiro 
para a estrutura. 
 
 
21 
 
1.7.4. Ponteiros para Estruturas 
Podemos ter um ponteiro para uma estrutura. Vamos ver como poderia ser 
declarado um ponteiro para as estruturas de ficha que estamos usando nestas seções: 
ficha_pessoal *p; 
Os ponteiros para uma estrutura funcionam como os ponteiros para qualquer outro 
tipo de dados no C/C++. Para usá-lo, haveria duas possibilidades. A primeira é apontá-lo 
para uma variável struct já existente, da seguinte maneira: 
ficha_pessoal ficha; 
ficha_pessoal *p; 
p = &ficha; 
A segunda é alocando memória para ficha_pessoal usando, por exemplo, new: 
/* Programa para cadastro de pessoal */ 
#include <iostream> 
#include <cstdlib> 
#include <cstdio> 
#include <string> 
using namespace std; 
 
//Definicao da estrutura para armazenar endereco 
struct tipo_endereco 
{ 
 string rua; 
 int numero; 
 string bairro; 
 string cidade; 
 string sigla_estado; 
 long int CEP; 
}; 
 
//Definicao da estrutura para armazenar dados do pessoal 
struct ficha_pessoal 
{ 
 string nome; 
 long int telefone; 
 tipo_endereco endereco; 
}; 
 
int main () 
{ 
 ficha_pessoal *p; // Faremos a alocacao dinamica de n fichas pessoais 
 int n; 
 
 cout<<"Entre com o numero de fichas:"; 
 cin>>n; 
 fflush(stdin); 
 
 
22 
 
 
 p = new ficha_pessoal[n]; 
 
 for (int i=0; i<n; i++) 
 { 
 cout<<endl<<"\t**** Dados da ficha pessoal "<<i<<" ****"; 
 cout<<endl; 
 cout<<"Nome:\t"; 
 getline(cin,p[i].nome); 
 fflush(stdin); 
 
 cout<<"Tel:\t"; 
 cin>>p[i].telefone; 
 fflush(stdin); 
 
 cout<<"Rua:\t"; 
 getline(cin,p[i].endereco.rua); 
 fflush(stdin); 
 
 cout<<"Numero:\t"; 
 cin>>p[i].endereco.numero; 
 fflush(stdin); 
 
 cout<<"Bairro:\t"; 
 getline(cin,p[i].endereco.bairro); 
 fflush(stdin); 
 
 cout<<"Cidade:\t"; 
 getline(cin,p[i].endereco.cidade); 
 fflush(stdin); 
 
 cout<<"Estado:\t"; 
 cin>>p[i].endereco.sigla_estado; 
 fflush(stdin); 
 
 cout<<"CEP:\t"; 
 cin>>p[i].endereco.CEP; 
 fflush(stdin); 
 } 
 
 for (int i=0; i<n; i++) 
 { 
 cout<<endl<<"\t**** Dados da ficha pessoal "<<i<<" ****"; 
 cout<<endl; 
 cout<<"Nome:\t"<<p[i].nome<<endl; 
 cout<<"Tel:\t"<<p[i].telefone<<endl; 
 cout<<"Rua:\t"<<p[i].endereco.rua<<endl; 
 cout<<"Numero:\t"<<p[i].endereco.numero<<endl; 
 cout<<"Bairro:\t"<<p[i].endereco.bairro<<endl; 
 cout<<"Cidade:\t"<<p[i].endereco.cidade<<endl; 
 cout<<"Estado:\t"<<p[i].endereco.sigla_estado<<endl; 
 cout<<"CEP:\t"<<p[i].endereco.CEP<<endl; 
 } 
 cout<<endl; 
 
 delete [] p; //Atenção a alocação dinamica 
 
 
23 
 
 
 system("pause"); 
 return 0; 
 
} 
Há um detalhe importante. Se apontarmos o ponteiro p para uma estrutura qualquer 
(como fizemos em p = &ficha;) e quisermos acessar um elemento da estrutura poderíamos 
fazer: 
(*p).nome 
Os parênteses são necessários, porque o operador . tem precedência maior que o 
operador * . Porém, este formato não é muito usado. O que é comum de se fazer é 
acessar o elemento nome através do operador seta, que é formado por um sinal de 
"menos"(-) seguido por um sinal de "maior que" (>), isto é: -> . Assim faremos: 
p->nome 
A declaração acima é muito mais fácil e concisa. Para acessarmos o elemento CEP 
dentro de endereco faríamos: 
p->endereco.CEP 
Exercício: 
Seja a seguinte struct que é utilizada para descrever os produtos que estão no 
estoque de uma loja : 
struct Produto 
{ 
 char nome[30]; // Nome do produto 
 int codigo; // Codigo do produto 
 double preco; // Preco do produto 
}; 
 
a) Escreva uma instrução que declare uma matriz de Produto com 10 itens de 
produtos; 
 
b) Atribua os valores "Pe de Moleque", 13205 e R$0,20 aos membros da posição 0 e 
os valores "Cocada Baiana", 15202 e R$0,50 aos membros da posição 1 da matriz 
anterior; 
 
c) Faça as mudanças que forem necessárias para usar um ponteiro para Produto ao 
invés de uma matriz de Produtos. Faça a alocação de memória de forma que se 
possa armazenar 10 produtos na área de memória apontada por este ponteiro e 
refaça as atribuições da letra b; 
 
d) Escreva as instruções para imprimir os campos que foram atribuídos na letra c. 
 
 
 
24 
 
1.8. Ponteiros para Funções 
Muito embora uma função não seja uma variável, ela ocupa uma posição física na 
memória, e assim, essa posição pode ser atribuída a um ponteiro. O endereço de uma 
função é chamado de ponto de entrada desta função, pois, quando a função é compilada, 
o código-fonte é transformado em código-objeto e um ponto de entrada é estabelecido 
para ela. Um ponteiro para a função pode ser usado para chamar a função, pois ele pode 
apontar para o ponto de entrada desta função. 
O endereço de uma função é obtido usando o nome da função sem parênteses ou 
argumentos. Observe o exemplo: 
 
#include <stdio.h> 
#include <string.h> 
 
void check (char *a, char *b, int (*cmp) ( const char *, const char *)); 
 
void main(void) 
{ 
char s1[80], s2[80]; 
int (*p)(); 
p=srtcmp; //p recebe o ponto de entrada de strcmp 
gets(s1); 
gets(s2); 
check(s1, s2, p); //chamada da função check() 
} 
 
void check (char *a, char *v, int (*cmp) (const char *, const char *)) 
{ 
cout <<”Testando igualdade de strings \n”; 
if(!(cmp)(a,b)) cout <<”igual”; //nesse ponto, a função strcmp é chamada 
else cout << “ Diferente” ; 
} 
 
Ao ser chamada a função check(), são passados dois strings s1 e s2 e um ponteiro p para 
função - que aponta para o ponto de entrada da função strcmp() - como argumentos. Na 
função check(), a função strcmp() é chamada através do ponteiro cmp (declarado no 
protótipo dela). Lembre-se de que cmp recebeu o conteúdo de p, que na verdade é o 
endereço da função strcmp() 
 
 
25 
 
2. A Função main() e seus argumentos 
 
2.1. A função void main(int argc, char *argv[]) 
 Muitas vezes é interessante passar parâmetros para um programa ao executá-lo. Esses 
argumentos são passados através da função main(). Um argumento na linha de comando é 
a informação que segue o nome do programa na linha de comando do sistema 
operacional. 
 
Ex: ao executarmos na linha de comando do prompt do Windows, o comando dir, por 
exemplo: 
 
c>\ dir /p 
 
Neste caso, estamos passando o parâmetro /p para o programa dir (lista conteúdo de 
diretório) que fará com haja uma pausa na listagem do conteúdo do diretório (tampem 
chamado de pasta) a cada vez que a tela do prompt é preenchida. 
 
Observe o código abaixo: 
 
#include <stdio.h> 
#include <stdlib.h> 
 
void main ( int argc, char *argv[]) 
{ 
 if (argc=!2){ 
 cout << “Voce esqueceu de digitar o seu nome. \n”; 
 exit (1) 
 } 
cout<<”Ola “<<argv[1]; 
} 
 
Se o nome do programa fosse chamada.exe e seu nome fosse Carlos, então, para rodar o 
programa, você digitaria chamada Carlos e a resposta seria: 
 
Ola Carlos 
2.1.1. O char argv[ ] e o int argc 
 
Os colchetes vazios indicam que a matriz é de tamanho indeterminado. Cada item da 
matriz de strings pode ser acessado indexando o argv. Ou seja, argv armazena cada um 
dos strings separados por espaço como um elemento desta matriz. 
Já o int argc armazena quantos strings foram digitados. O menor valor para argc é 1, que 
representa o nome do programa chamado. No caso do exemplo anterior, para imprimir Ola 
fulano, argc seria igual a 2 (nome do programa mais a string fulano) 
 
 
26 
 
3. Alocação Dinâmica de Memória 
As funções básicas de alocação dinâmica de memória em C++ são new e delete. 
O exemplo abaixo ilustra o uso das função new e delete. 
#include<iostream> 
#include<cstdlib> 
 
using namespace std; 
 
int main() 
{ 
 float *v; 
 int i, tam; 
 
 cout<<"Qual o tamanho do vetor? "; 
 cin>>tam; 
 v = new float[tam]; //Aloca a quantidade pedida pelo usuario 
 for (i=0; i<tam; i++) 
 { 
 cout<<"Elemento "<<i<<" ?"; 
 cin>>v[i]; 
 cout<<"Li valor "<<*(v+i)<<"\n"; 
 } 
 delete [] v; //Para todo new deve haver sempre um delete! 
 
 system("pause"); 
 return 0; 
} 
Outro exemplo, empregando a função new está mostrado no exemplo abaixo. Observe que 
neste exemplo também mostramos situações onde um endereço de variável foi passado 
para uma função de modo que a função main() possa receber um valor (vezes). 
 
#include<iostream> 
#include<cstdlib> 
 
using namespace std; 
 
void leVetor (float *v, int tam); 
float procuraMaior (float *v, int tam, int *vezes); 
 
int main() 
{ 
 float *v, maior; 
 int i, tam, vezes; 
 
 cout<<"Qual o tamanho do vetor? "; 
 cin>>tam; 
 v = new float[tam]; 
 
 leVetor(v, tam); 
 
 
27 
 
 maior = procuraMaior (v, tam, &vezes); 
 cout<<"O maior elemento e "<<maior; 
 cout<<" e aparece "<<vezes<<" vezes.\n\n"; 
 delete [] v; 
 
 system("pause"); 
 return 0; 
} 
 
void leVetor (float *v, int tam) 
{ 
 int i; 
 
 for (i=0; i<tam; i++) 
 { 
 cout<<"Elemento "<<i<<": "; 
 cin>>*(v+i); 
 cout<<"Li valor "<<*(v+i)<<"\n"; 
 } 
} 
 
 
float procuraMaior (float *v, int tam, int *vezes) 
{ 
 int i; 
 float maior; 
 
 maior = v[0]; *vezes = 1; 
 for (i=1; i<tam; i++) 
 { 
 if (v[i] > maior) 
 { 
 maior = v[i]; 
 *vezes = 1; 
 } 
 else if (maior == v[i]) 
 *vezes=*vezes+1;; 
 } 
 return maior; 
} 
 
Cuidado para não confundir a notação: 
int *p = new int(5); // aloca espaço para um int e armazena nele o 
 //valor 5 
int *q = new int[5]; // aloca espaço para um vetor com 5 
 //elementos do tipo int 
 
 
 
28 
 
4. Algoritmos de Ordenação e Pesquisa 
 
Duas das tarefas mais fundamentais e extensivamente utilizadas em relação às 
estruturas de dados são: a ordenação e a pesquisa. Algoritmos para essas tarefas são 
utilizadas em praticamente todos os programas de bancos de dados, bem como 
compiladores, interpretadores e sistemas operacionais. 
Ordenação é o processo de arranjar um conjunto de informações semelhantes 
em uma ordem crescente ou decrescente, utilizando para isso, uma parte da 
informação chamada chave de ordenação. Por exemplo, em uma lista postal, pode-se 
escolher uma ordenação pelo CEP ou por ordem alfabética, bastando para isso 
escolher como chave o campo CEP ou o campo nome. 
 
4.1.1. Tipos de algoritmos de ordenação 
 
 
 
4.2. Ordenação Bolha 
Observe o algoritmo abaixo: 
 
//Ordenação Bolha 
void bolha (char *item, int count) 
{ 
register int a, b; 
register char t; 
 
for (a=1; a<count; ++a) { 
for (b=count-1; b>=a; b--){ 
if (item[b-1] > item[b]) { 
t = item[b-1]; 
item[b-1]=item[b]; 
item[b] = t; 
} 
} 
} 
} 
 
Neste código, item é um ponteiro para uma matriz de caracteres a ser ordenanda e count 
é o númerode elementos da matriz. Há dois laços. O laço mais externo faz a matriz ser 
varrida cout-1 vezes. Isso garante que, na pior das hipóteses, todo elemento estará na 
posição correta quando a função terminar. No laço interno são feitas as comparações e 
trocas. 
Neste algoritmo é feito sempre o mesmo número de comparações e trocas, 
independentemente de os dados estarem mais ou menos ordenados ou totalmente 
desordenados. Isso é o ponto fraco desse algoritmo. Ou seja, independente de os dados 
estarem ordenados ou não, o número de passos do algoritmo continua o mesmo. Há 
outros algoritmos cujo esforço computacional é, na pior das hipóteses igual ao Bolha. 
 
 
 
29 
 
4.3. Ordenação por seleção 
No algoritmo de ordenação por seleção, o elemento, cuja chave possua o menor valor, é 
selecionado e trocado pelo primeiro elemento da lista. Então, para os n-1 elementos 
restantes, é encontrado o elemento de menor chave, que é trocado pelo segundo. E assim 
vai até que todos os n elementos estejam ordenados. Observe o algoritmo abaixo: 
 
//Ordenação por Seleção 
void selecao (char *item, int count) 
{ 
register int a, b, c; 
int Exchange; 
char t; 
 
for (a=0; a<count; ++a) { 
 exchange = 0; 
 c = a; 
 t = item[a]; 
 for(b=a+1; b<count; ++b){ 
 if(item[b]<t) { 
 c = b; 
 t = item[b]; 
 exchange = 1; 
 } 
 } 
 if (exchange) { 
 item[c] = item[a]; 
 item[a] = t; 
 } 
 } 
} 
 
Neste caso, embora o número de comparações seja o mesmo que para o Bolha, o número 
de trocas no caso médio ( dados mais ou menos ordenados...) é muito menor. 
 
4.4. Ordenação por inserção 
Neste algoritmo, primeiramente são ordenados os dois primeiros elementos da lista. Em 
seguida, insere-se o terceiro elemento em sua ordenação em relação aos dois primeiros. 
Posteriormente, insere-se o quarto elemento em relação aos três primeiros, e assim vai até 
que todos os elementos estejam inseridos e ordenados. Observe o código abaixo: 
 
//Ordenação por Insersão 
void insercao (char *item, int count) 
{ 
register int a, b; 
register char t; 
 
for (a=1; a<count; ++a) { 
 t = item[a]; 
for (b=a-1; b>=0 && t<item[b]; b--){ 
item[b+1]=item[b]; 
item[b+1] = t; 
 
 
30 
 
} 
} 
} 
} 
 
Ao contrário dos algoritmos anteriores, o número de comparações depende de como a lista 
está inicialmente ordenada. Se a lista estiver em ordem, o número de comparações será n-
1. Já se estiver fora de ordem, será um número muito grande, se aproximando do pior caso 
que é a Ordenação Bolha. 
 
4.5. Ordenação Quicksort 
 A ordenação quicksort foi inventada por C.A.R. Hoare1 e é superior a todas as 
anteriores. Geralmente é considerado o melhor algoritmo de ordenação de propósito geral 
atualmente disponível. O quicksort é baseado na idéia de partição. O procedimento geral 
é selecionar um valor, chamado de comparando e, então, fazer a partição da lista (ou 
matriz) de dados em duas seções: a primeira com todos os elementos cuja chave seja 
menor que a chave do comparando e a segunda, com todos os elementos cuja chave seja 
maior que a do comparando. Esse processo é repetido para cada seção recursivamente, 
até que toda a lista esteja ordenada. Observe o exemplo abaixo: 
 
// Ordenação Quicksort 
void quick(char *item, count-1) 
{ 
 qs(item, 0, count-1) 
} 
 
//O quicksort propriamente dito 
void qs (char *item, int left, int rigth) 
{ 
 register int i, j; 
 char x, y; 
 i = left; j = rigth; 
 x = item[(left+rigth)/2]; 
 
 do{ 
 while (item[i]<x && i<rigth) i++; 
 while (x<item[j] && j>left) j--; 
 
 if(i<=j) { 
 y = item[i]; 
 item[i] = item[j]; 
 item[j] = y; 
 i++; j--; 
 } 
 } while (i<=j); 
 
 if (left < j) qs(item, left, j); 
 if (i < rigth) qs(item, i, rigth); 
} 
 
1
 Sir Charles Antony Richard Hoare (Tony Hoare ou C.A.R. Hoare, nascido em 11 de janeiro de 1934) é um cientista 
da computação britânico, mais conhecido pelo desenvolvimento do Quicksort em 1960, o algoritmo de ordenação mais 
utilizado no mundo, e muito provavelmente o algoritmo mais usado dentre todos os tipos existentes 
 
 
 
31 
 
 
A função quick() executa a chamada à função de ordenação principal qs(). Após a primeira 
chamada de qs(), esta é chamada recursivamente até que todas as sub-seções sejam 
feitas e os dados sejam ordenados. em todos os exemplos, a chave é um caractere que 
está em uma determinada posição da matriz do tipo char apontada pelo ponteiro item. 
 
 
 
 
 
 
32 
 
5. Estruturas de Dados 
5.1. Filas 
As filas Uma fila (queue) é uma seqüência de n nodos de um determinado tipo, cujo 
comportamento simula uma fila de registros. Normalmente representamos uma lista por 
uma seqüência de elementos separados por vírgula x1, x2, x3, ...., xn. Onde n >= 0. 
O número n de elementos é o comprimento da fila. Se n > 0 então dizemos que x1 é o 
primeiro elemento da fila e xn é o último elemento. Sendo que x1 foi primeiro elemento e o 
xn é o último elemento a chegar. 
Se n = 0 então a fila está vazia. Uma propriedade importante da fila é que os elementos 
são inseridos no final e removidos no início. O primeiro registro a ser inserido é o primeiro a 
sair :FIFO (first in first out). . 
Também chamada de lista linear de informações, que é acessada na seguinte ordem: 
 
• Primeiro elemento a entrar, primeiro elemento a sair ( FIFO:First in, First Out). 
 
Ou seja, o primeiro colocado na fila é o primeiro a sair; o segundo colocado é o segundo a 
sair, etc. Uma maneira de visualizar uma fila é imaginá-la como uma fila de banco, onde a 
última pessoa a entrar será a última pessoa a sair: 
 
 
Figura 3: Modelo do funcionamento de uma Fila 
 
Imaginemos na figura acima como uma fila de Banco, onde cada célula representa um cliente 
a ser atendido. Os clientes, naturalmente, são ordenados na ordem de chegada (aqui não 
estamos colocando casos especiais, como gestantes, etc.). É de se esperar que os clientes 
sejam atendidos na mesma ordem quem que chegaram, ou seja, o primeiro, em seguida, o 
segundo, até o último. Caso não tenha mais nenhum elemento (cliente) na fila, dizemos que a 
fila está vazia. Caso o número de elementos ultrapasse a capacidade da fila, dizemos que a 
fila está cheia e incapaz de armazenar elementos seguintes. 
 
Uma fila tem por norma as seguintes funcionalidade: 
 
• add ou store – guardar um elemento na fila 
• remove ou retrieve– retirar um elemento da fila 
• top – retornar o elemento do topo da fila 
 
 
33 
 
• full – Verificar se a fila está cheia (não pode guardar mais elementos). 
• empty – Verificar se a fila está vazia (não contém elementos) 
• construct – Colocar a fila num estado “pronto” a ser utilizada (Prepará-la) 
 
Observe um modelo de fila simples (Schildt, 1997) onde são manipulados três elementos: 
A, B e C: 
 
Figura 4: Dinâmica dos ponteiros rpos e spos 
A função qstore() armazena os elementos A e B, em seguida, a função qretrieve() retira os dois 
elementos, e por fim, a função qstore() armazena o elemento C. Observe nessa 
implementação, que há um apontador spos que é responsável por indicar a primeira posição 
vazia apta a armazenar um elemento. Há também o apontador rpos que indica a posição do 
próximo elemento a ser retirado da fila. Um vetor de elementos poderia ser utilizado para a 
construção desta fila. É bom salientar que o elemento pode ser qualquer tipo de dados, ou 
seja, poderia ser char, int, float, uma estrutura, etc. 
 
Observe este modelo para as funções de retirada e inserção de elementos na fila: 
 
 
Função de inserção de um elemento na fila onde MAX representa o tamanho da fila. No caso, 
o elemento a ser inserido é um ponteiro *q que recebe o endereço de um vetor de caracteres( 
 
 
34 
 
ex: um nome). Chamamos a atenção para o fato de que só podemos inserir um elemento se a 
fila não estiver cheia ( spos não pode apontar para MAX). 
 
 
Na função qretrieve() os elementos são retirados.É claro que só será retirado elementos se 
houver elementos na fila. Daí o teste se rpos==spos. Observe que neste modelo, como rpos 
aponta para o próximo elemento a ser retirado, o elemento a ser retornado está indicado pela 
posição dada por rpos-1. 
 
5.2. Filas Circulares 
Observe o modelo de fila circular ilustrado abaixo (Schildt, 1997). Neste tipo de fila, o termo 
circular tem uma conotação de fila sem fim, ou seja, é como se as pontas (início e fim) da fila 
ficassem ligadas, fechando um ciclo sem fim. 
 
 
Figura 5: Modelo de fila circular 
 
Vamos tomar com exemplo, os apontadores spos e rpos utilizados na fila simples do item 
anterior. Vamos imaginar que a fila tenha 12 posições como na figura acima. Neste caso, se 
chegássemos na posição MAX, mas anteriormente, tivéssemos retirado uns três elementos do 
início da fila, por que não reutilizar os espaços que vagos? É aí que entra a fila circular. Nela, 
enquanto houver espaço devido à recuperação dos dados e conseqüente atualização de rpos , 
que mostra a posição do próximo elemento a ser retirado, spos poderia avançar para reutilizar 
estes espaços. 
Em outras palavras, a fila circular somente estaria cheia se os índices de armazenamento e de 
recuperação de dados forem iguais. Caso contrário, a fila terá espaços vagos. 
 
 
35 
 
 
 
Na função abaixo, há uma diferença no comando IF, se comparado com o da função qstore() 
anterior. Neste, são avaliados se spos+1 é igual a rpos ou se spos+1 é igual a Max e 
simultaneamente, se rpos é nulo. Isso é para garantir que a posição a inserir um dado nunca 
tenha um dado já armazenado e que não tenha sido ainda retirado da fila circular. Além disso, 
há outro if para reiniciar a inserção do elemento ajustando a para o início da fila. 
 
 
 
Observe que nesse algoritmo, p é um ponteiro de ponteiro, ou seja, ele armazena um vetor de 
ponteiros cujo tamanho é definido por MAX. Assim, na linha p[spos]=q; o que está sendo 
armazenado na posição spos de p é o endereço da variável que o ponteiro q aponta. 
 
Na última linha do código acima, o valor de spos é rejustado para 0, a fim de reiniciar a fila 
circular. 
 
 
 
 
 
36 
 
5.3. Pilhas de dados 
A pilha é uma estrutura de dados em que tanto a inserção quanto a remoção de elementos 
de uma sequência se faz pela mesma extremidade, geralmente designada por topo da 
pilha. Nela, o último elemento a ser inserido será o primeiro elemento a ser retirado. Este 
conceito é conhecido como LIFO = Last-in First-out. 
 
Figura 6: Funcionamento de uma Pilha de Dados 
 
Na seqüencia de códigos abaixo, temos duas funções para a pilha. A primeira é a função 
push(), usada para a inserção de um novo elemento. Para ser inserido, deve-se verificar 
se a pilha não está cheia. Para isso é executado o teste if sobre o ponteiro tos ( top of 
stack). A segunda função é a pop(). Nela o ponteiro p é ajustado para a posição do 
elemento a ser retirado. Isso por que p aponta sempre para a primeira posição livre da 
pilha. Antes de a função retornar o valor de p, deve-se verificar se já não chegou na base 
da pilha, ou seja “pilha vazia”. Para isso é feito o teste if sobre o ponteiro bos (base of 
stack). 
 
 
 
Um PC moderno usa pilhas ao nível de arquitetura, as quais são usadas no design básico 
de um sistema operacional para manipular interrupções e chamadas de função do sistema 
operacional. Outro exemplo do uso de pilhas está na Máquina virtual Java; a própria 
linguagem Java possui uma classe denominada "Stack". Um terceiro exemplo está na 
programação de micro-controladores em assembler, onde o controle do sp (stack pointer) é 
fundamental para os desvios e chamadas de funções. 
 
 
37 
 
5.4. Listas de dados com encadeamento 
 
5.4.1. Listas Encadeadas 
Uma lista encadeada é uma estrutura de dados linear e dinâmica. Ela é composta por 
células onde cada uma aponta para a próxima célula da lista. Um exemplo de célula: 
 
 
 
Para inserir dados ou remover dados é necessário ter um ponteiro que aponte para o 1º 
elemento e outro que aponte para o fim, porque se queremos inserir ou apagar dados que 
estão nessas posições, a operação é rapidamente executada. Caso seja necessário editar 
um nó que esteja no meio da lista haverá uma busca pela posição desejada: 
 
• O link entre uma célula ou elemento para outro é feito através de um ponteiro. 
• Há um ponteiro que aponta para o início da lista e outro que aponta para a última 
célula da lista. 
• Na última célula, o seu ponteiro ponteiro aponta para NULL. 
 
 
Figura 7: Modelo de uma lista simplesmente encadeada 
 
Ao inserir um novo elemento em uma lista com encadeamento simples, teremos uma das 
seguintes situações: 
 
• Inserindo um elemento no início da lista. Neste caso, deve-se fazer um ajuste nos 
ponteiros. O ponteiro inicio deverá ser ajustado para apontar para este novo 
elemento que estamos inserindo. Além disso, o ponteiro deste elemento inserido 
deverá apontar para o antigo primeiro elemento. 
struct funcionario 
{ 
 char inscricao[10]; 
 char nome[50]; 
 float salario; 
 funcionario *proximo; 
 }; 
 
 
 
 
38 
 
 
Figura 8: Inserindo um novo primeiro elemento 
 
• Inserindo um elemento no meio da lista: Neste caso, o ponteiro do elemento anterior 
a este (de acordo com o critério de ordenação adotado) deverá apontar para aquele 
que está sendo inserido. O ponteiro deste deverá apontar para o próximo elemento. 
 
 
Figura 9: Inserindo um elemento no meio da lista 
 
• Inserindo um elemento no fim da lista. Neste caso, devemos ajustar dois ponteiros. 
O ponteiro ultimo deverá apontar para este que está sendo inserido. O ponteiro 
deste elemento deverá apontar para NULL, a fim de indicar que ele é o último 
elemento da lista. 
 
 
Figura 10: Inserindo um elemento no fim da lista 
 
Algumas vantagens da lista encadeada são: 
 
• A inserção ou remoção de uma célula, ou elemento na lista não implica a mudança 
de lugar de outros elementos; 
• Não é necessário definir, no momento da criação da lista, o número máximo de 
elementos que esta poderá ter. Ou seja, é possível alocar memória "dinâmica-
mente", apenas para o número de nós necessários. 
 
 
 
39 
 
Algumas desvantagens da lista simplesmente encadeada: 
• A manipulação torna-se mais "perigosa" uma vez que, se o encadeamento (ligação) 
entre elementos da lista for mal feito, toda a lista pode ser perdida; 
• Para acessar o elemento na posição n da lista, deve-se percorrer os n - 1 anteriores.

Outros materiais