Você está na página 1de 50

Estrutura de

Dados

Profa. Dra. Vera Prudência Caminha


veraprudencia@id.uff.br

ICEx/UFF

Volta Redonda-RJ
© 2002-2020
Estrutura de Dados

Sumário

Listas Dinâmicas de Dados ..................................................................... 03


Listas Simplesmente Encadeadas .......................................................... 03
Exercícios ………………………………………………………………………. 07
Listas Simplesmente Encadeadas Ordenadas ....................................... 08
Exercícios ………………………………………………………………………. 10
Listas Simplesmente Encadeadas Circulares ........................................ 11
Exercícios ………………………………………………………………………. 17
Listas Duplamente Encadeadas ……………………………………………..18
Exercícios ................................................................................................. 25
Recursividade .......................................................................................... 26
Exercícios ................................................................................................. 31
Divisão e Conquista ................................................................................. 33
Exemplo clássico: Torres de Hanói ........................................................ 37
Ordenação Avançada ............................................................................... 42
Estudo do Mergesort ............................................................................... 42
Estudo do Quicksort…………………………………………………………….45
Exercícios ……………………………………………………………………… 50

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 2
Estrutura de Dados

Listas Dinâmicas (Encadeadas) de Dados

As posições de memória são alocadas ou desalocadas na medida


em que são necessárias. Os nós da lista encontram-se dispostos
aleatoriamente na memória e são interligados por ponteiros, que
indicam a posição do próximo nó da lista.

Listas Simplesmente Encadeadas (LSE)

Nas Listas Simplesmente Encadeadas é necessário o acréscimo


de um campo a cada nó, que vai indicar o endereço do próximo nó da
lista.
Ex:

p info prox

Onde:

- o campo info contém a informação do nó;


- o campo prox contém o endereço do próximo nó da lista;
- p é o ponteiro externo que contém o endereço do primeiro nó da lista.
- o campo prox do último nó da lista aponta para NULL.

Para declarar uma estrutura de LSE é necessário que um dos


membros da estrutura seja uma variável ponteiro para uma estrutura do
mesmo tipo.

Considere:

struct lista{
int info;
struct lista *prox;
};

Vejamos algumas operações que podemos fazer com as LSEs:


© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 3
Estrutura de Dados

1) Função para criar uma LSE com n nós:

struct lista *cria(int n)


{
int i, valor;
struct lista *p,*ini,*ult;
ini=ult=NULL;
for(i=1; i<=n; i++)
{
printf("Informe um valor: ");
scanf("%d", &valor);
p=(struct lista*)malloc(sizeof(struct lista));
p->info=valor;
p->prox=NULL;
if(ult)
ult->prox=p;
else
ini=p;
ult=p;
}
return ini;
}

2) Contar os nós da lista:

int contaNos(struct lista *p)


{
int cont;
if(!p)
return 0;
else{
cont=0;
while(p) {
p=p->prox;
cont++;
}
return cont;
}
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 4
Estrutura de Dados

3) Imprimir a lista:

void imprimir (struct lista *p)


{
while(p)
{
printf("%d\t", p->info);
p=p->prox;
}
}

4) Função para inserir um nó no final da lista:

struct lista *inserirFinal(struct lista *p, int valor)


{
struct lista *ini, *novo;
novo=(struct lista*)malloc(sizeof(struct lista));
novo->info=valor;
novo→prox=NULL;
if(!p)
return novo;
ini=p;
while(p->prox)
p=p->prox;
p->prox=novo;
return ini;
}

5) Função para remover o último nó da lista. Retornar o ponteiro para o


início da lista resultante.

struct lista *removerFinal(struct lista *p, int valor)


{
struct lista *ini, *ult;
if(!p)
return NULL;
if(!p->prox)
{
free(p);
return NULL;
}
ini=p;
while(p->prox) {
ult=p;
p=p->prox;
}
ult->prox=NULL;
free(p);
return ini;
© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 5
Estrutura de Dados

6) Função para remover o k-ésimo nó da lista. Retornar ponteiro para o


início da lista resultante. (k>=1 && k<=n)

struct lista *removeK(struct lista *p, int k)


{
struct lista *t, *q;
int cont;
if(k==1)
{
if(!p->prox)
{
free(p);
return NULL;
}
else {
t=p->prox;
p->prox=NULL;
free(p);
return t;
}
}
else {
cont=1;
t=p;
while(cont != k) {
q=p;
p=p->prox;
cont++;
}
q->prox=p->prox;
free(p);
return t;
}
}

7) Função para concatenar duas LSEs. Retornar ponteiro para o inicio da


lista concatenada.

struct lista *concatena (struct lista *x, struct lista *y)


{
struct lista *z;
if(!x)
return y;
z=x;
while(z->prox)
z=z->prox;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 6
Estrutura de Dados

z->prox=y;
return x;
}

Exercícios:

1) Escreva uma função para criar uma cópia de uma LSE. Retorne ponteiro
para o início da cópia da lista.

2) Escreva uma função para inserir um nó após o k-ésimo nó da LSE.


Retorne ponteiro para o início da lista. (k>=1 && k<=n)

3) Escreva um programa que crie uma LSE com n (lido via teclado) nós de
inteiros. Imprima a lista. Conte os nós da lista e imprima na tela. Crie uma
cópia da lista e imprima a cópia. Remova o k-ésimo (lido via teclado) nó da
lista. Imprima a lista resultante.

4) Escreva um programa, que contém uma estrutura do tipo Lista


Simplesmente Encadeada que armazena números reais e execute opções de:
1- Inserir um nó na lista
2- Buscar um nó na lista
3- Remover um nó da lista
4- Imprimir a lista
5- Sair

- Crie uma função para cada opção do menu, exceto a opção 5.


- O programa termina quando for digitado a opção 5.
- Nas opções 1, 2 e 5, leia um valor e execute a operação.

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 7
Estrutura de Dados

Listas Simplesmente Encadeadas Ordenadas

1) Função para inserir um nó numa LSE ordenada:

struct lista *insereOrd(struct lista *p, int valor)


{
struct lista *t, *q, *r;

q=(struct lista*)malloc(sizeof(struct lista));


q -> info=valor;

if(!p) {
q->prox=NULL;
return q;
}
else{
r=p;
while(p && valor > p->info) {
t=p;
p=p->prox;
}
if(!p)
{
t->prox=q;
q -> prox=NULL;
return r;
}
if(valor<p->info)
{
q->prox=p;
if(p!=r) {
t->prox=q;
return r;
}
return q;
}
}
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 8
Estrutura de Dados

2) Busca na LSE ordenada:

struct lista *buscaOrd(struct lista *p, int valor)


{
struct lista *q;
if(!p)
return NULL;
while(p && valor != p->info)
{
q=p;
p=p->prox;
}
return p;
}

3) Remoção na LSE ordenada:

struct lista *removeOrd(struct lista *p, int valor)


{
struct lista *q, *t;

if(!p)
return NULL;
else {

if(buscaOrd(p,valor)==NULL)
{
printf("\nElemento não existente.");
return p;
}
else {
q=p;
while(q→info < valor)
{
t=q;
q=q->prox;
}
if(p!=q)
t->prox=q->prox;
else
p=p->prox;
free(q);
return p;
}
}
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 9
Estrutura de Dados

Exercícios:

1) Escreva uma função para intercalar duas LSEs ordenadas. Retornar


ponteiro para o início da lista intercalada.
Protótipo: struct lista *intercala(struct lista *p, struct lista *q)

2) Escreva um programa que leia n (lido via teclado) números inteiros e


insira-os numa Lista Simplesmente Encadeada Ordenada. Em seguida,
imprima a lista. Leia um valor e faça a busca. Leia um valor e se o valor
existir na lista, faça a remoção. Mostre a lista resultante.

3) Escreva um programa que leia um valor n e crie uma LSE ordenada


com n nós de inteiros. Imprime a lista. Leia um outro valor para n e crie
uma segunda LSE ordenada com n nós de inteiros. Imprima a lista. Em
seguida, gere uma terceira lista resultante da intercalação da primeira lista
com a segunda lista. Imprima a terceira lista.

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 10
Estrutura de Dados

Listas Simplesmente Encadeadas Circulares (LSEC)

Numa LSEC, a partir de qualquer ponto da lista será possível atingir


qualquer outro ponto.

Não existe mais um primeiro e ultimo nós. Em virtude desse fato, foi
adotada uma convenção que considera como último nó aquele apontado pelo
ponteiro externo.

Considere:

struct lista {

int info;

struct lista *prox;

};

Ex:

info prox
p

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 11
Estrutura de Dados

Vamos estudar algumas operações com as LSECs:

1) Função para criar uma LSEC com n nós:

struct lista *cria (int n)


{ int i, valor;
struct lista *p, *ini, *ult;
ini=NULL;
for(i=1; i<=n; i++) {
printf("\nInforme um valor: ");
scanf ("%d", &valor);
p=(struct lista *) malloc(sizeof(struct lista));
p->info=valor;
if(!ini) {
p->prox=p;
ini=p;
}
else {
p->prox=ini;
ult->prox=p;
}
ult=p;
}
return p;
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 12
Estrutura de Dados

2) Função para contar os nós da LSEC:

int contanos (struct lista *p)

{ struct lista *q;

int cont=1;

if(!p)

return 0;

q=p;

while(p->prox!=q){

cont++;

p=p->prox;

return cont;

3) Função para inserir um nó no inicio da LSEC:

struct lista inserirInicio(struct lista *p, int valor)

struct lista *q;

q=(struct lista*)malloc(sizeof(struct lista));

q->info=valor;

if(!p) {

q->prox=q;

return q;

q->prox=p->prox;

p->prox=q;

return p;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 13
Estrutura de Dados

4) Função para remover o primeiro nó da LSEC:

struct lista *removePrimeiro(struct lista *p)

{ struct lista *q;

if(!p)

return NULL;

if(p == p->prox) {

free(p);

return NULL;

q=p->prox;

p->prox=q->prox;

free(q);

return p;

5) Função para dividir a LSEC em duas, sendo:

- A primeira com o ultimo nó

- A segunda com o restante dos nós

- Retornar referência para a segunda lista.

struct lista *divideLista(struct lista *p)


{
struct lista *q;
if(p == p->prox)
return NULL;
q=p;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 14
Estrutura de Dados

while(q -> prox!=p)


q=q->prox;
q->prox=p->prox;
p->prox=p;
return q;
}

6) Função para remover o k-ésimo nó da LSEC não vazia. Retornar ponteiro


para a lista resultante.

struct lista *removeK(struct lista *p, int k)


{
struct lista *q, *t;
int cont;
if(k==1){
if(p->prox==p) {
free(p);
return NULL;
}
else{
q=p->prox;
p -> prox = q->prox;
free(q);
}
}

else{
cont=1;
q=p->prox;
while(k!=cont) {
t=q;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 15
Estrutura de Dados

q=q->prox;
cont++;
}
t->prox=q->prox;
if(p==q){
free(q);
return t;
}
free(q);
return p;
}
}

7) Função para inserir um nó após o k-ésimo nó na LSEC. Retornar


referência para a lista resultante.

struct lista *insereAposK(struct lista *p, int k)

{
struct lista *q, *r;
int cont=1, valor;
printf("\nDigite o valor a ser inserido: ");
scanf("%d", &valor);
r=(struct lista*) malloc(sizeof(struct lista));
r -> info=valor;
q=p->prox;
while(cont!=k) {
cont++;
q=q->prox;
}
r->prox=q->prox;
q -> prox=r;
if(q==p)

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 16
Estrutura de Dados

return r;
else
return p;
}

Exercícios:

1) Escreva um programa que crie uma LSEC com n (lidos via teclado) nós.
Em seguida, crie um menu com as seguintes opções:

1 – Imprimir a lista

2 - Contar os nós da lista

3 - Remover o k-ésimo nó da lista (lido via teclado)

4 – Criar e imprimir a cópia da lista

5 – Mostrar os valores pares e ímpares da lista

5 – Sair

Crie funções para cada opção da lista, exceto a opção 6.

2) Escreva uma que recebe duas LSEC e um inteiro k e divida a lista em


duas:

- A primeira com k nós e a segunda com o restante dos nós.

- Retornar ponteiro para a segunda lista.

3) Escrava uma função para concatenar duas LSEC. Retornar ponteiro para
a lista resultante.

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 17
Estrutura de Dados

Listas Duplamente Encadeadas (LDE)

Nas LSECs não conseguimos percorrer a lista no sentido contrário.


Nem um nó pode ser eliminado com o uso de um único ponteiro para esse
nó.

Para ultrapassar essas deficiências podemos usar as Listas


Duplamente Encadeadas.

Os nós que compõem as LDEs tem dois endereços, ou seja, dois


ponteiros. Um deles aponta para o nó sucessor e o outro para o nó
predecessor.

Nas LDEs pode-se ir para frente ou para trás com igual facilidade.

eprox info
dprox

Considere:

struct dupla{

struct dupla *eprox;

int info;

struct dupla *dprox;

};

Vejamos algumas operações com as LDEs:


© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 18
Estrutura de Dados

1) Função para criar uma LDE com n nós.

struct dupla *criaLDE(int n)

struct dupla *p, *ini, *ult;

int valor, i;

ult=NULL;

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

printf("\nInforme um valor: ");

scanf ("%d", &valor);

p=(struct dupla*)malloc(sizeof(struct dupla));

p -> info = valor;

p -> eprox = NULL;

p -> dprox = NULL;

if(ult)

ult -> dprox = p;

p -> eprox = ult;

else

ini = p;

ult = p;

return ini;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 19
Estrutura de Dados

2) Função para contar os nós da LDE.

int contaNos (struct dupla *p)


{
int cont=0;
while(p)
{
cont++;
p= p -> dprox;
}
return cont;
}

3) Função para imprimir a LDE.

void imprime(struct dupla *p)


{
while(p)
{
printf(“%d \t”, p -> info);
p= p -> dprox;

4) Função para inserir um nó na frente da LDE. Retornar ponteiro para o


início da lista resultante.

struct dupla *insereFrente(struct dupla *p, int valor)

struct dupla *q;

q=(struct dupla*)malloc(sizeof(struct dupla));

q -> info = valor;


© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 20
Estrutura de Dados

q -> eprox = NULL;

if(!p){

q→dprox=NULL;

return q;

else{

q -> dprox = p;

p -> eprox = q;

return q;

5) Função para inserir um nó no final da LDE. Retornar ponteiro para o


início da lista resultante.

struct dupla *insereFinal(struct dupla *p, int valor)

struct dupla *q, *t;

q=(struct dupla*)malloc(sizeof(struct dupla));

q -> info = valor;

if(!p){

q -> eprox = q -> dprox = NULL;

return q;

else{

t=p;

while(t -> dprox)

t= t -> dprox;

t -> dprox = q;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 21
Estrutura de Dados

q -> eprox = t;

q -> dprox = NULL;

return p;

6) Função para remover o k-ésimo nó da LDE. Retornar o ponteiro para o


início da lista resultante.

struct dupla *removeK(struct dupla *p, int k)

struct dupla *q, *r, *t;

int cont;

if(k == 1)

if(!p →dprox){

free(p);

return NULL;

else{

q=p;

p=p→dprox;

p → eprox = NULL;

free(q);

return p;

else{

cont=1;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 22
Estrutura de Dados

q=p;

while(cont != k)

{ q = q -> dprox;

cont++;

t=q -> dprox;

r=q -> eprox;

r -> dprox = t;

if(t != NULL)

t -> eprox = r;

free(q);

return(p);

7) Função para inserir um nó apontado por p após um nó apontado por x,


onde x aponta para algum nó de uma LDE. Utilize apenas as variáveis p e x
na função.

void insere(struct dupla *p, struct dupla *x)

{ if( x -> dprox != NULL)

x -> dprox -> eprox = p;

p -> dprox = x -> dprox;

p -> eprox = x;

x -> dprox = p;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 23
Estrutura de Dados

8) Função para remover um nó apontado por x de uma LDE, onde y aponta


para o início da lista. Retorne ponteiro para o início da lista resultante.
Utilize apenas as variáveis y e x na função.

struct dupla *removeNo(struct dupla *y, struct dupla *x)

if(y == x)

if(y -> dprox == NULL)

y = NULL;

else{

y = y -> dprox;

y -> eprox = NULL;

else{

if(!x -> dprox)

x -> eprox -> dprox = NULL;

else{

x -> eprox -> dprox = x -> dprox;

x -> dprox -> eprox = x -> eprox;

free(x);

return y;

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 24
Estrutura de Dados

Exercícios:

1) Escreva uma função que recebe duas LDEs e faz a concatenação


das duas listas. Retorne ponteiro para o início da lista concatenada.

2) Escreva um programa, que contém uma estrutura do tipo Lista


Duplamente Encadeada que armazena números inteiros e execute as
seguintes funções:
a) Criar uma LDE com n nós;
b) Imprimir os elementos da lista;
c) Contar os elementos da lista;
d) Remover o k-ésimo nó da lista, o nó k deve ser indicado pelo
usuário (testar se tal elemento existe);
e) Copiar a lista; Imprima a copia da lista;
f) Inserir um nó antes do k-ésimo nó da lista, o nó k deve ser
indicado pelo usuário.

3) Escreva um programa, que contém uma estrutura do tipo Lista


Duplamente Encadeada ordenada que armazena dados inteiros e
execute as seguintes seguintes opções de:
1- Inserção um nó na lista
2- Busca uma chave na lista
3- Remoção um nó na lista
4- Imprimir a lista
5- Sair
Faça chamadas a funções para executar cada opção do menu, exceto
a opção 5.

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 25
Estrutura de Dados

Recursividade

Uma função é dita recursiva quando é definida em termos de si


mesma, isto é, numa função que faz uma chamada a si mesma.
Recursão é uma técnica poderosa de resolução de problemas
matemáticos.

Ex. 1: Números Naturais


- 0 é um número natural
- O sucessor de um número natural é um número natural.

Ex. 2: A função fatorial


0! = 1
n!=n*(n-1)!

Ex. 3: Estrutura de Árvores

Ex. 4: Sequência de Fibonacci


0, 1, 1, 2, 3, 5, 8, 13, 21, ...

Ex. 5: Fractais, etc.

Uma chamada recursiva não faz uma cópia da função, apenas


os argumentos são novos.
Quando uma função chama a si mesma, novos parâmetros e
variáveis locais são alocados em uma pilha do sistema e o código é
executado.
A vantagem é a criação de versões mais claras e simples de
algoritmos.
É importante que o algoritmo recursivo não gere uma
sequência infinita de chamadas a si mesma.
Para acompanhamento de algoritmos recursivos fazemos a sua
árvore de recursão.

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 26
Estrutura de Dados

Ex. 1: Fatorial
Iterativo:

int fatorial (int n)


{
int i, fat = 1;
for(i=1; i<=n; i++)
fat=fat*i;
return fat;
}

Recursivo:

int fatorial(int n)
{
if(n==0) //condição de parada da função
return 1;
return(n*fatorial(n-1)); //chamada recursiva
}

Chamada: z=fatorial(4);

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 27
Estrutura de Dados

Ex. 2: Sequência de Fibonacci


0, 1, 1, 2, 3, 5, 8, 13, 21, ...
Iterativo:

int fibo(int n)
{
int x, y, z, i;
i=1;
x=0;
y=1;
while(i<n) {
z=x+y;
x=y;
y=z;
i++;
}
return x;
}

Recursivo:
F1=0
F2=1
Fn=Fn-1+Fn-2, n>2

int fibo(int n)
{
if(n==1)
return 0;
if(n==2)
return 1;
if(n>2)
return(fibo(n-1) + fibo(n-2));
}

Chamada: y=fibo(6);

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 28
Estrutura de Dados

Como criar uma função recursiva?

1) Ache o passo chave (função recursiva)


Pergunte-se: “Como esse problema pode ser resolvido?”
A resposta deve ser simples.

2) Ache a condição de parada


Ela indica que o problema ou parte dele foi feita.
Essa regra é geralmente pequena e dá a solução trivial sem a
necessidade de recursividade.

3) Monte o seu algoritmo


Combine a condição de parada e a função recursiva usando
uma estrutura condicional.

4) Verifique o término
É importante verificar que a recursividade terminará.
Comece com uma situação geral e verifique se em um número
finito de passos, a condição de parada será satisfeita e a
recursividade irá terminar.

5) Desenhe a árvore de recursão


É a ferramenta chave para analisar algoritmos recursivos.

Ex. 3: Função recursiva para calcular o produto entre dois números


inteiros (a≥0 e b≥0).
Ideia: o produto se distribui na soma.

int produto(int a, int b)


{
if(b==0)
return 0;
return(a+produto(a, b-1));
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 29
Estrutura de Dados

Ex. 4: Função recursiva para calcular a potência entre dois números


inteiros (a≥0 e b≥0).
Ideia: a potência se distribui no produto.

int potencia(int a, int b)


{
if(b==0)
return 1;
return(a*potencia(a, b-1));
}

Ex. 5: Função recursiva para calcular a divisão inteira entre dois


números (a≥0 e b>0).

int divisao(int a, int b)


{
if(a<b)
return 0;
return(1 + divisao(a-b, b));
}

Ex. 6: Função recursiva para calcular o resto da divisão inteira entre


dois números inteiros (a>0 e b>0).

int resto(int a, int b)


{
if(a<b)
return a;
return (resto(a-b, b));
}

Ex. 7: Função recursiva para contar os caracteres de uma string.

int contaCaractere(char str[], int pos)


{
if(str[pos] == ‘\0’)
return pos;
return(contaCaractere(str, pos+1));
}

Chamada: z = contaCaractere(str, 0);

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 30
Estrutura de Dados

Ex. 8: Função recursiva para fazer o somatório de 0 até n.

int somatorio(int n)
{
if(n==0)
return 0;
return(n + somatorio(n-1));
}

Exercícios:

1) Escreva uma função recursiva para contar o número de vezes que


uma determinada chave aparece no vetor de inteiros.
Protótipo: int contaChave(int v[], int i, int chave, int n);

2) Escreva uma função recursiva para somar os elementos de um


vetor de inteiros.
Protótipo: int somaVetor(int v[], int n);

3) Escreva uma função recursiva para inverter a ordem dos elementos


num vetor de inteiros.

4) Escreva uma função recursiva para contar o número de dígitos de


um número inteiro.

5) Escreva a versão recursiva para as funções abaixo:

a) int buscaSeq(int v[], int n, int chave)


{
int i;
for(i=0;i<n;i++) {
if(chave==v[i])
return i;
}
return -1
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 31
Estrutura de Dados

b) int buscaBin(int v[], int ini, int fim, int chave)


{
int meio;
while(ini <= fim)
{
meio=(ini+fim)/2;
if(chave == v[meio])
return meio;
if(chave < v[meio])
fim=meio-1;
else
ini=meio+1;
}
return -1;
}

6) A função de Ackerman é definida recursivamente sobre os inteiros


não negativos da seguinte forma:
a(m, n) = n+1, se m=0
a(m, n) = a(m-1, 1), se m ≠ 0 e n = 0
a(m, n) = a(m-1, a(m, n-1)), se m ≠ 0 e n ≠ 0

- Mostre que a(2, 2) = 7 através da árvore de recursão.


- Escreva a função de Ackerman em C .

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 32
Estrutura de Dados

Divisão e Conquista

Definição: é um método para resolver problemas. Consiste em dividir


um problema complexo P em subproblemas menos complexos P1,
P2, ..., Pn, de mesma natureza.

P1 P2 Pn
...

...
...
..
P11 P12 P1n P21
P22 P2n Pn1 Pn2 Pnn
.

Os subproblemas P1, P2, ..., Pn são resolvidos usando também a


divisão e conquista e suas soluções são combinadas para resolver P. O
processo de subdivisões se encerra quando chegamos a problemas simples
que podem ser resolvidos imediatamente sem a necessidade de subdivisão.

Ex. 1: Achar o menor elemento em um vetor de inteiros não-ordenado:

Ideia:
menor (L)
Se L tem um só elemento
o menor é ele.
Caso contrário:
Divide L em L1 e L2
N1=menor(L1);
N2=menor(L2);
decidir entre N1 e N2

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 33
Estrutura de Dados

Em C:

int menor(int v[], int ini, int fim)


{
int meio, n1, n2;
if(ini == fim)
return v[ini];
meio = (ini+fim)/2;
n1=menor(v,ini,meio);
n2=menor(v,meio+1,fim);
if(n1<n2)
return n1;
return n2;
}

Chamada: x=menor(v, 0, TAM-1);


Ex:
v
4 1 7 6 2
menor(v, 0, 4)
meio = 2
n1=menor(v, 0, 2) =1 menor(v, 0, 2)
n2=menor(v, 3, 4) = 2 meio=1
if(1<2) n1=menor(v, 0, 1)=1 menor(v, 0, 1)
return 1 n2=menor(v, 2, 2)=7 meio=0
if(1<7) n1=menor(v,0,0)=4
return 1 n2=menor(v,1,1)=1
if(4<1)F
return 1
menor(v, 3, 4)
meio=3
n1=menor(v, 3, 3)=6
n2=menor(v, 4, 4)=2
if(6<2)
return 2

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 34
Estrutura de Dados

Ex. 2: Função para verificar se uma lista está ordenada ou não.


Ideia:
estaOrdenada(L)
Se L é vazia ou unitária
a resposta é SIM
Caso contrário
Divida L em L1 e L2
p1=estaOrdenada(L1)
p2=estaOrdenada(L2)
Se p1 for SIM, p2 for SIM e p1 for menor que p2
a resposta é SIM
senão
a resposta é NÃO

Em C:

int estaOrdenada(int v[], int ini, int fim)


{
int p1, p2, meio;
if(ini >= fim)
return 1;
meio = (ini + fim)/2;
p1 = estaOrdenada(v, ini, meio);
p2 = estaOrdenada(v, meio+1, fim);
if(p1 && p2 && v[meio] < v[meio+1])
return 1;
return 0;
}

Ex: Faça a árvore de recursão para o vetor abaixo:


v
1 2 3 0 5 6

z= estaOrdenada(v, 0, 5)

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 35
Estrutura de Dados

Conclusão: Após o acompanhamento do algoritmo devemos observar


que teremos que abrir toda a árvore de recursão para concluir que o
vetor não está ordenado.
Devemos pensar numa forma de melhorar o algoritmo para que as
chamadas recursivas sejam feitas apenas se o teste v[meio] <
v[meio+1] seja verdadeiro. Assim, podemos reescrever a função da
seguinte forma:

int estaOrdenada(int v[], int ini, int fim)


{
int meio;
if(ini >= fim)
return 1;
meio = (ini + fim)/2;
if(v[meio] > v[meio+1])
return 0;
if(estaOrdenada(v, ini, meio))
if(estaOrdenada(v, meio+1, fim))
return 1;
return 0;
}

Exercício:

1) Escreva uma função, usando divisão e conquista, para calcular e


retornar a posição do maior elemento presente num vetor de inteiros
não ordenado.

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 36
Estrutura de Dados

Exemplo Clássico - Jogo Torres de Hanói

A tarefa envolve três hastes de metal, onde a primeira haste


possui n discos. Os n discos aumentam de tamanho de cima para
baixo, sendo o disco do top o o menor de todos.
O problema consiste em relocar os n discos na haste 3 movendo
um disco por vez e nenhum disco maior pode ficar sobre um disco
menor.
A cada vez que um disco é movido pode ser colocado apenas
numa haste vazia ou no topo de uma pilha com n discos de tamanho
maior.
Ex:

Considere:
i => haste inicial → no. 1 ou A
a => haste auxiliar → no. 2 ou B
f => haste final → no. 3 ou C

Ideia:
move(n, i, a, f)
se n=1
mova disco do topo da haste i para a haste f
caso contrário
aplique move(n-1, i, f, a)
mova disco do topo da haste i para a haste f
aplique move(n-1,a, i, f)

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 37
Estrutura de Dados

Em C:

void move(int n, int i, int a, int f)


{
if(n == 1)
printf(“Mova disco da haste %d para a haste %d \n”, i, f);

else{
move(n-1, i, f, a);
printf(“Mova disco da haste %d para a haste %d \n”, i, f);

move(n-1, a, i, f);
}
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 38
Estrutura de Dados

Ex: Faça a árvore de recursão para as seguintes chamadas:


a) move(3, 1, 2, 3)
b) move(4, 1, 2, 3)

a) move(3, 1, 2, 3)
move(2, 1, 3, 2) move(2, 1, 3, 2)
1→ 3 (IV) move(1, 1, 2, 3) move(1, 1, 2, 3)
move(2, 2, 1, 3) 1→ 2 (II) 1 → 3 (I)
move(1, 3, 1, 2) move(1, 3, 1, 2)
3→ 2 (III)

move(2, 2, 1, 3)
move(1, 2, 3, 1) move(1, 2, 3, 1)
2→ 3 (VI) 2 → 1 (V)
move(1, 1, 2, 3) move(1, 1, 2, 3)
1 → 3 (VII)

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 39
Estrutura de Dados

Análise da Complexidade: Jogo Torres de Hanói

Para analisar o consumo de tempo de um algoritmo recursivo é


necessário resolver uma recorrência. Uma recorrência pode ser vista
como um algoritmo recursivo que calcula uma função a partir de um
valor inicial.

Assim, podemos fazer a análise do algoritmo do jogo Torres de


Hanói da seguinte forma:

Seja T(n) o tempo de execução para uma chamada recursiva de


tamanho n. Logo,
T(0) = 0
T(n) = 2.T(n-1) + 1
Ou seja, é o tempo para duas chamadas recursivas de tamanho n-1
mais o movimento do disco.

Desenvolvendo a equação:

T(n) = 2.T(n - 1) + 1 (1)

T(n-1) = 2.T(n-1-1) + 1 = 2.T(n - 2) + 1

Substituindo na equação (1), temos:

T(n) = 2.(2.T(n-2) + 1) + 1=
T(n) = 4.T(n-2) + 3 (2)
T(n) = 22.T(n - 2) + 22- 1

T(n-2) = 2.T(n-3) + 1

Substituindo na equação (2), temos:

T(n) = 4.(2.T(n-3) + 1) + 3
T(n) = 8.T(n-3) + 7 (3)
T(n) = 23.T(n - 3) + 23- 1

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 40
Estrutura de Dados

T(n-3) = 2.T(n - 4) + 1

Substituindo na equação (3), temos:

T(n) = 8.(2.T(n - 4) + 1) + 7
T(n) = 16.T(n - 4) + 15 (4)
T(n) = 24.T(n - 4) + 24 - 1

Podemos observar que a equação segue o mesmo comportamento.


Assim, podemos generalizar a equação:

T(n) = 2k.T(n - k) + 2k- 1

Mas, n – k ≈ 0 => n – k = 0 => n = k

Substituindo na equação generalizada, temos:


T(n) = 2n.T(n - n) + 2n- 1
T(n) = 2n.T(0) + 2n- 1
T(n) = 2n. 0 + 2n- 1
T(n) = 2n- 1
Logo, a complexidade é O(2n).

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 41
Estrutura de Dados

Ordenação Avançada

Vamos trabalhar com dois métodos de ordenação que usam a técnica


de divisão e conquista.

1) Mergesort
É uma ordenação por intercalação. O mergesort é uma ordenação que
é diferente dos outros métodos. Ele combina dois vetores ordenados em um
só.

Ideia:
mergesort(L)
Se L é vazia ou unitária
a resposta é L
Caso contrario
Divida L em L1 e L2
L1= mergesort(L1)
L2 = mergesort(L2)
a resposta é o merge(junção) de L1 < L2

Ex:

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 42
Estrutura de Dados

Em C:

void mergesort(int v[], int ini, int fim)

int meio;

if(ini == fim)

return;

else{

meio = (ini + fim)/2;

mergesort(v, ini, meio);

mergesort(v, meio+1, fim);

intercala(v, ini, meio+1, fim);

Exercício:

1) Implemente a função intercala de forma não recursiva.

Análise da Complexidade: Mergesort

Assuma que n é uma potência de 2.


Para n=1, o tempo do mergesort é constante.

T(1) = 1

O tempo para ordenar n números via mergesort é igual ao tempo


para duas chamadas recursivas de tamanho n/2 mais o tempo da
intercalação que é linear.

T(n) = 2.T(n/2) + n

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 43
Estrutura de Dados

Desenvolvendo a equação, temos:


T(n) = 2.T(n/2) + n (1)

T(n/2) = 2.T(n/2/2) + n/2 = 2.T(n/4) + n/2

Substituindo na equação (1), temos:

T(n) = 2.(2.T(n/4) + n/2) + n


T(n) = 4.T(n/4) + 2n (2)
T(n) = 22.T(n/22) + 2n

T(n/4) = 2.T(n/4/2) + n/4 = 2.T(n/8) + n/4

Substituindo na equação (1), temos:

T(n) = 4.(2.T(n/8) + n/4) + 2n


T(n) = 8.T(n/8) + 3n (3)
T(n) = 23.T(n/23) + 3n

T(n/48) = 2.T(n/8/2) + n/8 = 2.T(n/16) + n/8

Substituindo na equação (3), temos:

T(n) = 8.(2.T(n/16) + n/8) + 3n


T(n) = 16.T(n/16) + 4n (4)
T(n) = 24.T(n/24) + 4n

Podemos observar que a equação segue o mesmo comportamento.


Assim, podemos generalizar a equação:

T(n) = 2k.T(n/2k) + kn

n/2k ≈ 1 => n/2k = 1 => n = 2k <=> k=log2n

Logo, substituindo na equação generalizada, temos:

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 44
Estrutura de Dados

T(n) = n.T(n/n) + log2n n


T(n) = n.T(1) + n log2n
T(n) = n.1 + n log2n
T(n) = n log2n + n

Logo, a complexidade do mergesort no pior caso é O(n log2n ).

2) Quicksort

O quicksort (comumente conhecido como ordenação rápida) designa


um algoritmo extremamente eficiente e rápido de ordenação de dados
desenvolvido por Charles Antony Richard Hoare em 1960.

A estratégia básica do quicksort é a de “dividir para conquistar”.

O Quicksort é o algoritmo mais eficiente na ordenação por trocas.


Nele se escolhe um elemento chamado de pivô, a partir disto é organizada a
lista para que todos os números anteriores a ele sejam menores que ele, e
todos os números posteriores a ele sejam maiores que ele. Ao final desse
processo o número pivô já está em sua posição final. Os dois grupos
desordenados recursivamente sofreram o mesmo processo até que a lista
esteja ordenada.

Ideia:
- Escolhe-se qualquer elemento para ser o pivô;
- Reorganiza-se a lista tal que os elementos fiquem da seguinte forma:

pivô

elementos elementos
menores que maiores que
o pivô o pivô

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 45
Estrutura de Dados

Quicksort(L)

Se L é vazia o unitária

a resposta é L

Caso contrário

Escolha um pivô qualquer

Construa L1 com os elementos de L menores que o pivô

Construa L2 com os elementos de L maiores que o pivô

L1 = Quicksort(L1)

L2 = Quicksort(L2)

a resposta é L1 && pivô && L2

Ex:
Quicksort(L)
3 2 5 1 7 6

(1 2 3 e 5 e 7 6)
1 2 3 5 6 7

Pivô = 5
L1 = 3 2 1 L2 = 7 6
L1 = 1 2 3 L2 = 6 7

L= 3 2 1 (1 e 2 e 3) L = 7 6 ( 6 e 7)
pivô = 2 pivô = 6
L1 = 1 L2=3 L1 = L2 = 7
L1 =1 L2=3 L1= L2=7

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 46
Estrutura de Dados

Em C:

void quicksort(int v[], int esq, int dir)


{
//esq é o índice mais a esquerda
//dir é o índice mais a direita

int i, j, x;

i=esq;
j=dir;

x=v[(esq+dir)/2];
do{
while(v[i] < x && i<dir)
i++;
while(x<v[j] && j> esq)
j--;
if(i<=j)
{
troca(&v[i], &v[j]); //mesma função usada na ordenação simples
i++;
j--;
}
}while(i<=j);
if(esq<j)
quicksort(v,esq, j);
if(i<dir)
quicksort(v,i, dir);
}

Exercício:

1) Ordene o vetor abaixo fazendo o acompanhamento do algoritmo do


quicksort:
v
6 5 4 1 3 2

quicksort(v, 0, 5)

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 47
Estrutura de Dados

Análise da Complexidade: Quicksort

Imagine que o pivô foi escolhido aleatoriamente. Neste caso, temos a


seguinte relação de recorrência:

T(0) = T(1) = 1
T(n) = T(i) + T(n - i -1) + c.n

Suponha que o pivô está dividindo duas partes, sendo uma delas a
sequência Si. Então, vamos entender cada termo da equação:

T(i) → é o tempo para ordenar Si, ou seja, os elementos menores que o pivô.

T(n - i – 1) → é o tempo total para os n elementos, menos os elementos de S i


menos o pivô.

Conclusão:

T(n) → é o tempo de execução de 2 chamadas recursivas mais o tempo


linear gasto na partição.

Qual é o pior caso do quicksort?

R: O pior caso é ter o pivô como menor elemento o tempo todo.

Logo,

Se i=0 e ignorando T(0) = 1, temos:

T(n) = T(0) + T(n-0-1) + c.n

T(n) = T(n-1) + c.n

Vamos desenvolver a equação:

T(n) = T(n-1) + c.n

T(n - 1) = T(n – 1 -1) + c.(n – 1) = T(n – 2) + c.(n – 1)

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 48
Estrutura de Dados

T(n - 2) = T(n – 2 -1) + c.(n – 2) = T(n – 3) + c.(n – 2)

T(n - 3) = T(n – 3 -1) + c.(n – 3) = T(n – 4) + c.(n – 3)

… …

T(3) = T(2) + c.3

T(2) = T(1) + c.2

Realizando o somatório, temos:

T(n) = T(n - 1) + c.n


T(n) = T(n - 2) + c.(n – 1) + c.n
T(n) = T(n - 3) + c.(n -2) + c.(n – 1) + c.n
T(n) = T(n - 4) +c.(n -3) + c.(n -2) + c.(n – 1) + c.n

T(n) = T(1) + c.2 + c.3 + c.4 + … + c.(n -3) + c.(n -2) + c.(n – 1) + c.n

T(n) = T(1) + c(2 + 3 + 4 + … + (n -3) + (n -2) + (n – 1) + n)

T(n) = T(1) + c.Ʃ2n i= 2 + 3 + 4 + … + (n -3) + (n -2) + (n – 1) + n

Sn= (a1 + an) .n


2

Sn= (2 + n) . (n-1)
2

S n= n 2 + n - 2
2
Assim,

T(n) = T(1) + c. n2 + n - 2
2
T(n) ≈ 1 + c. (n2 + n – 2)

Logo, a complexidade do quicksort no pior caso é O(n2).

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 49
Estrutura de Dados

Exercícios:

1) Dado o vetor X = [8, 4, 1, 0, 6, 5, 2, 9, 3, 7], ordená-lo, detalhadamente,


pelos métodos:

a) QuickSort
b) MergeSort

2) Desenvolva as equações de recorrência abaixo e dê a complexidade:


a) T(n) = T(n-1) + n
T(1) = 1

b) T(n) = 2T(n/2) + n
T(1) = 1

c) T(n) = T(n-1) + 1
T(0) = 1

3) Calcule a complexidade no pior caso das funções abaixo:

a) int buscaBin(int v[], int ini, int fim, int chave)


{
int meio;
if(ini>fim)
return -1;
meio=(ini+fim)/2;
if(v[meio]==chave)
return meio;
if(chave<v[meio])
return(buscaBin(v, ini, meio));
return(buscaBin(v, meio+1, fim));
}

b) int fatorial(int n)
{
if(n==0)
return 0;
return(n*fatorial(n-1));
}

© 2002– 2020 Prof. Dra. Vera Lúcia Prudência dos Santos Caminha – VFI/ICEx/UFF – Volta Redonda-RJ 50

Você também pode gostar