Ed1

167
Estrutura de Dados I Docente: Dikiefu Fabiano, Msc

Transcript of Ed1

Page 1: Ed1

Estrutura de Dados I

Docente: Dikiefu Fabiano,

Msc

Page 2: Ed1

Sumário1. Introdução

1.1 Tipo de dados Abstracto -TDA

1.2 Ponteiros

1.3 Funções

1.4 Estruturas e tipos de dados definidos pelo programador

2. Listas Lineares

2. 1 Listas Sequenciais

2.2 Listas ligadas

2.3 Listas Circulares

2.4 Listas duplamente ligadas

2.5 Pilhas

2.5.1Implementação sequencial da pilha

2.5.2Implementação dinâmica da pilha

2.6 Filas

2.6.1Implementação sequencial da fila

2.6.2Implementação dinâmica da fila

Page 3: Ed1

3. Árvores

3.1 Introdução

3.2 Representação de árvores

3.2 Definição

3.3 Terminologia

3.4 Árvores binárias

3.5 Árvores balanceadas

3.6 Definição da estrutura de dados

4. Ordenação

4.1 Ordenação por Selecção

4.2 Ordenação por Inserção

4.3 Shellsort

4.4 Quicksort

4.5 Heapsort

5. Pesquisa

5.1 Pesquisa Sequencial

5.2 Pesquisa Binária

5.3 Árvores de Pesquisa

Page 4: Ed1

Capitulo I: Introdução

Sumário:

1.1Tipo de Dado Abstracto (TDA)

1.2Ponteiros

1.3Funções

1.4Estruturas

1.5 Tipos de dados definidos pelo

programador

Page 5: Ed1

1.1 Tipo de Dados Abstracto -

TDA Tipos de Dados

◦ Tipo de dados de uma variáveis

define o conjunto de valores que a variável pode

assumir.

Especifica a quantidade de bytes que deve ser

reservadas a ela.

◦ Tipos de dados podem ser vistos como métodos

para interpretar o conteúdo da memória do

computador

◦ Podemos ver o conceito de tipo de dados de uma

outra perspectiva: não em termos do que um

computador pode fazer (interpretar os bits …) mas

em termos do que o utilizador desejam fazer

(somar dois inteiros…)

Page 6: Ed1

1.1 Tipo de Dado Abstracto - TDA

Tipo de Dado Abstracto

◦ Agrupa a estrutura de dados juntamente com as operações que podem ser feitas sobre esses dados

◦ O TDA encapsula a estrutura de dados. Os utilizadores do TDA só tem acesso a algumas operações disponibilizadas sobre esses dados

◦ Utilizador do TDA x Programador do TDA

Utilizador só “enxerga” a interface, não a sua implementação

Dessa forma, o utilizador pode abstrair da implementação específica.

Qualquer modificação nessa implementação fica restrita ao TDA

◦ A escolha de uma representação específica é fortemente influenciada pelas operações a serem executadas

Page 7: Ed1

1.1 Tipo de Dado Abstracto -

TDA Estrutura de Dados - ED

◦ É um método particular de se implementar um

TDA

◦ Cada ED é construída dos tipos primitivos

(inteiro, real, char, …) ou dos tipos compostos

(array, registo, …) de uma linguagem de

programação.

◦ Algumas operações realizadas em TDA são:

criação, inclusão, busca e remoção de dados.

Exemplos de TDA◦ Lineares: Não Lineares:

Listas ligadas . Árvores

Pilhas . Grafos

Filas

Page 8: Ed1

TDA-Exemplo

Existem vários métodos para especificar um TDA. O

método que usaremos é semi-formal e faz uso

intensivo da notação de C. Para ilustrar o conceito de

um TDA e método de especificação, examinemos o

TDA RACIONAL que corresponde ao conceito

matemático de um número racional.

Um número racional é o que pode ser expresso como o

quociente de dois inteiros.

As operações sobre números racionais que definimos

são:

◦ Criação de um número racional

◦ Adição

◦ Multiplicação

◦ Teste de igualdade.

Page 9: Ed1

TDA-Exemplo

//definição de valor

abstract typedef <integer, integer> RACIONAL;

condiction RACIONAL [1] <> 0;

//definição de operador

abstract RACIONAL criaracional(a, b)

int a, b;

precondition b <> 0;

postcondition criaracional[0]== a;

criaracional[1]== b;

abstract RACIONAL soma (a, b)

RACIONAL a, b;

postcondition soma[1]== a[1]*b[1];

soma[0]==a[0]*b[1]+b[0]*a[1];

Page 10: Ed1

abstract RACIONAL mult(a, b)

RACIONAL a, b;

postcondition mult[0]==a[0]*b[0];

mult[1]==a[1]*b[1];

abstract RACIONAL igual(a,b)

RACIONAL a, b;

postcondition igual==(a[0]*b[1]==a[1]*b[0]);

Exercício:

Crie o TODA para o conjunto de números complexos com todas

as suas operações básicas.

Page 11: Ed1

1.2 Apontadores

Na realidade, C permite que o

programador referencie a posição de

objectos bem como os próprios

objectos (isto é, o conteúdo de suas

posições)

Page 12: Ed1

1.2 Apontadores

• O objectivo:– Armazenar o endereço de outra variável.

• Sintaxetipo * ptr

• ptr - é o nome da variável do tipo apontador

• tipo – o tipo da variável para qual apontará.

• * - indica que é uma variável do tipo apontador

• Exemplo 1:char *pc;

int *pi;

float *pf

Page 13: Ed1

Um ponteiro é como qualquer outro tipo de dado em C.

O valor de um ponteiro é uma posição de memória da mesma forma que o valor de um inteiro é um número.

Os valores dos ponteiros podem ser atribuídos como qualquer outro valor.

A conversão de pf do tipo “ponteiro para um número de ponto flutuante” para o tipo “ponteiro para um inteiro” pode ser feita escrevendo-se:

pi = (int *) pf;

Se pi é um ponteiro para um inteiro, então pi +1 é o ponteiro para o inteiro imediatamente seguinte ao inteiro *pi em memória, pi–1 é o ponteiro para o inteiro imediatamente anterior a *pi.

1.2 Apontadores

Page 14: Ed1

Apontadores

Por exemplo, suponha que determinada máquina usa endereçamento de bytes, que um inteiro exija 4 bytes e que valor de piseja 100 (isto é, pi aponta para inteiro *pi na posição 100). Sendo assim, o valor de pi–1 é 96, o valor de pi+1 é 104.

De modo semelhante, se o valor da variável pc é 100 e um caractere tem um byte, pc – 1 refere-se a posição 99 e pc+1 à posição 101.

Page 15: Ed1

Apontadores

Uma área na qual os ponteiros de C desempenham um notável papel é passagem de parâmetro para funções.

Normalmente, os parâmetros são passados para uma função em C por valor, isto é, os valores sendo passados são copiados nos parâmetros da função de chamada no momento em que a função for chamada. Se o valor de um parâmetro for alterado dentro da função, o valor no programa chamada não será modificado.

x =5

printf(“%d\n”, x) // imprime 5

func(x); // imprime 6

printf(“%d\n”, x) // imprime 5

func(y)

int y

{

++y;

printf(“%d\n”, y);

}

Page 16: Ed1

x =5printf(“%d\n”, x) // imprime 5func(&x); // imprime 6printf(“%d\n”, x) //imprime 6…func(py)int *py{

++(*py);

printf(“%d\n”, *py);

}

Se precisar usar func para modificar o valor de x, precisaremos passar

o endereço de x

Page 17: Ed1

1.2.1 Inicialização automática de

apontadores

• Um bom hábito para evitar problemas de programação é a inicialização sempre dos apontadores

• A constante simbólica NULL, quando colocada num apontador, indica que este não aponta para nenhuma variável.

int x = 5;

float pi = 3.1415;

int px = &x;

float * ppi = &pi;

Page 18: Ed1

1.3 Funções

Conceito:

◦ É uma unidade autónoma de código do

programa e é desenhada para cumprir

uma tarefa particular.

Sintaxe

tipo nome_da_função (parametros){

comandos;

}

Page 19: Ed1

1.3 Funções• Quando uma função não retorna um valor para função que a

chamou ela é declarada como void.

• Ex:#include<stdio.h>

void linha (char ch, int size){

int i;

for (i=1; i size; i++)

putchar(ch);

}

main(){

char lin;

int tamanho;

printf(“Introduza o tipo de linha”);

scanf(“%c”, &lin);

printf(“Introduza o tamanho da linha”);

scanf(“%d”, &tamanho);

linha(lin, tamanho);

getchar();

}

Page 20: Ed1

1.3 Funções

O tipo de retorno tem que ser declarada.

Ex1 :

int potencia (int base, int expoente){

int i, pot=1;

If (expoente<0)

return;

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

pot=pot*base;

Return pot;

}

1.3.3 Função com retorno

main(){

int b, e;

printf(“Introduza a base e

expoente”);

scanf(“%d%d”, &b, &e);

printf(“valor=%d\n”,

potencia(b,e));

getchar();

}

Page 21: Ed1

Estrutura de dados e tipo de dados definido pelo

utilizador

Estruturas são peças contíguas de armazenamento que contém

vários tipos simples agrupados numa entidade.

Estrutura são manifestadas através da palavra reservada struct

seguida pelo nome da estrutura e por uma área delimitada por

colchetes que contém campos.

struct pessoa {

char nome[30];

int idade;

char sexo;

};

Para criação de novos tipos de estrutura de dados utiliza-se a

palavra-chave: typedeftypedef struct pessoa {

char nome[30];

int idade;

char sexo;

} PESSOA;

Page 22: Ed1

Estrutura

Para obter directamente o valor de um campo, a forma é <nome_da_estrutura>.<campo>.

Para obter o valor de um campo apontado por um ponteiro a forma é <nome_da_estrutura> -><campo>

Ex

…PESSOA func, *estud, &docente = func ;

func.nome=“Adriano”; //atribui o valor Adriano para o campo nome

func.idade=25; //atribui o valor 25 para o campo idade

func.sexo=“M”; //atribui o valor M para o campo sexo

estud=&func; //faz estud apontar para func

estud->nome=“Joana”; //muda o valor do campo nome para Joana

estud ->idade =22; //muda o valor do campo idade para 22

estud ->sexo =“F”; //muda o valor do campo sexo para F

docente.Nome=“Pedro”; //Referências usam a notação de ponto

Page 23: Ed1

Estruturas Recursivas

Estruturas podem ser recursivas, i.e.,

podem conter ponteiros para si

mesmas. Esta peculiaridade ajuda a

definir estruturas do tipo lista.

struct lista{

struct lista *prox;

int dado;

}

Page 24: Ed1

Capitulo II: ListasSumário2. Listas Lineares

2. 1 Listas Sequenciais

2.2 Listas ligadas

2.3 Listas Circulares

2.4 Listas duplamente ligadas

2.5 Pilhas

2.5.1 Implementação sequencial da pilha

2.5.2 Implementação dinâmica da pilha

2.6 Filas

2.6.1 Implementação sequencial da fila

2.6.2 Implementação dinâmica da fila

2.6.3 Fila circular

Page 25: Ed1

Listas

Listas são uma colecção de objectos,

onde os itens podem ser adicionados

em qualquer posição arbitrária.

Page 26: Ed1

Propriedades

• Uma lista pode ter zero ou mais

elementos

• Um novo item pode ser adicionado

a lista em qualquer ponto

• Qualquer item da lista pode ser

eliminado

• Qualquer item da lista pode ser

acessado

Listas

Page 27: Ed1

Listas - Formas de

Representação Sequencial

◦ Explora a sequencialidade da memória do computador, de tal forma que os nós de uma lista sejam armazenados em endereço sequencias. Podem ser representado por um vector na memória principal ou um arquivo sequencial em disco.

L

0 1 2 3 4 5 MAX-1

Ligada

◦ Esta estrutura é tida como uma sequencia de elementos ligados por ponteiros, ou seja, cada elemento deve conter, além do dado propriamente dito, uma referencia para o próximo elemento da lista.

L

Adão Adelin

a

Basto

s

Daniel Josefin

a

Adão Adelina Bastos Daniel Josefina

Page 28: Ed1

Listas sequenciais

Uma lista representada de forma sequencial é um

conjunto de registos onde estão estabelecidas

regras de precedência entre seus elementos.

Implementação de operações pode ser feita

utilizando array e registo, associando o elemento a[i]

com o índice i.

Características

◦ Os elementos na lista estão armazenados

fisicamente em posições consecutivas

◦ A inserção de um elemento na posição a[i] causa

o deslocamento a direita do elemento a[i] ao

último.

◦ A eliminação do elemento a[i] requer o

deslocamento à esquerda do a[i+1] ao último.

Page 29: Ed1

Listas Sequenciais

Vantagens

◦ Acesso directo a qualquer dos elementos da lista.

◦ Tempo constante para aceder o elemento i - (depende

somente de índice)

Desvantagens

◦ Para inserir ou remover elementos temos que deslocar

muitos outros elementos

◦ Tamanho máximo pré-estimado

Quando usar

◦ Listas pequenas

◦ Inserção e remoção no fim da lista

◦ Tamanho máximo bem definido

Page 30: Ed1

Listas sequenciais

Definição#define MAX 50 //tamanho máximo da lista

typedef char elem[20]; // tipo base dos elementos da lista

typedef struct tlista{

elem v[MAX];//vector que contém a lista

int n; //posição do último elemento da lista

};

Tlista

n=5 v

0 1 2 3 4 5 6 MAX – 1

Operações básicas

Adão Alberto Ana Daniela Carmen …

1.Criar uma lista vazia

void criar(tlista *L) {

L -> n = 0;

}

2. Verificar se uma lista está vazia

int vazia(tlista L) {

return (L .n == 0);

}

Page 31: Ed1

Listas sequenciaisOperações básicas

4. Obter o tamanho de uma lista

int tamanho ( tlista L){

return (L.n);

}

3. Verificar se uma lista está cheia

int cheia( tlista L) {

return (L .n == MAX);

}

5. Obter o i-ésimo elemento de uma

lista

int elemento( tlista L, int pos, elem *dado )

{

if ((pos > L.n) || (pos <=0))

return (0);

*dado = L.v [pos - 1];

return 1;

}

6. Pesquisar um dado elemento,

retornando a sua posição.

int posicao( tlista L, elem dado )

{

int i;

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

if(L.v[i-1] == dado)

return i;

return 0;

}

Page 32: Ed1

Listas SequenciaisOperações básicas

7. Inserção de um elemento em uma

determinada posição(requer o deslocamento à direita dos

elementos v(i+1) … v(n))

// retorna 0 se a posição for inválida ou se a

lista

// estiver cheia, caso contrário retorna 1

int inserir( tlista *L, int pos, elem dado )

{

int i;

if (( L->n == MAX) || ( pos > L->n + 1 ))

return (0);

for (i = L->n; i >= pos; i--)

L->v[i] = L->v [i-1];

L->v[pos-1] = dado;

(L->n)++;

return (1);

}

8. Remoção do elemento de uma

determinada posição(requer o deslocamento à esquerda dos

elementos v(p+1) … v(n))

/* o parâmetro dado irá receber o elemento

encontrado.

Retorna 0 se a posição for inválida , caso

contrário

retorna 1 */

int remover( tlista *L, int pos, elem *dado )

{

int i;

if ( ( pos > L->n) || (pos <= 0) )

return (0);

*dado = L-> v[pos-1];

for (i = pos; i <= (L->n) - 1; i++)

L->v[i-1] = L->v [i];

(L->n)--;

return (1);

}

Page 33: Ed1

Listas Ligadas

Uma lista ligada ou lista encadeada é uma estrutura de dados

linear e dinâmica. Ela é composta por células que apontam

para o próximo elemento da lista.

Para "ter" uma lista ligada/encadeada, basta guardar seu

primeiro elemento, e seu último elemento aponta para uma

célula nula.

Vantagens

◦ A inserção ou a retirada de um elemento na lista não implica na mudança de lugar de

outros elementos;

◦ Não é necessário saber, anteriormente, o número máximo de elementos que uma lista

pode ter, o que implica em não desperdiçar espaço na Memória Principal (Memória

RAM) do computador.

Desvantagens

◦ 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.

Page 34: Ed1

Listas ligadas

As listas ligadas são conjunto de elementos ligados, onde cada elemento contem uma ligação com um ou mais elementos da lista.

Uma lista ligada tem necessariamente uma variável ponteiro apontando para o seu primeiro elemento. Essa variável será utilizada sempre, mesmo que esteja vazia, e deverá apontar sempre para o início da lista.

Cada elemento da lista ligada será composta por duas/três partes principais: uma parte conterá informações e a(s) outra(s) as conexões com elementos.

Page 35: Ed1

Listas Ligadas - Propriedades

Uma lista pode ter zero ou mais

elementos

Um novo item pode ser adicionado a

lista em qualquer ponto

Qualquer item da lista pode ser

eliminado

Qualquer item da lista pode ser

acessado.

Page 36: Ed1

Listas Ligadas

Uma lista ligada é composta por

elementos alocadas em memória.

Utiliza-se duas funções para alocar

(desalocar) memória dinamicamente:

◦ malloc()

◦ free()

Ex:

L = (lista) malloc(sizeof(lista));

free(L);

Page 37: Ed1

Lista Simplesmente Ligada

Cada elemento da lista possui uma única conexão. Assim, cada elemento será formado por um bloco de dados e um ponteiro para próximo elemento. Chamaremos cada elemento nó.

Um nó é um conjunto de informações de tipos diferentes, portanto ela é uma estrutura definida pelo programador:

struct no{

int dado;

struct no *prox; //ponteiro para o próximo nó

}

Page 38: Ed1

Listas Simplesmente Ligadas

Sendo que uma memória alocada

dinamicamente não possui o nome

como no caso das variáveis

declaradas no programa, toda lista

deverá ter uma variável que apontará

para o seu primeiro elemento.struct no *head; //cabeça da lista

head=null; //inicia a lista com nulo

Page 39: Ed1

Listas Simplesmente Ligadas

Podemos nos referenciar a uma lista ligada através do seu primeiro nó.

Entretanto, toda vez que realizarmos uma inserção no início da lista teremos que garantir que todas as partes do programa que referenciam a lista tenha suas referencias actualizadas. Para evitar este problema, é comum usar um nó especial chamado nó cabeça, e um ponteiro para o primeiro elemento da lista.

Page 40: Ed1

Listas Simplesmente Ligadas

A implementação em C de uma lista

simplesmente ligada com nó cabeça.

Precisamos de dois tipos de dados:

◦ Um tipo nó, representa cada elemento da

lista.

◦ e um tipo lista que contém um ponteiro

para nó cabeça.

Page 41: Ed1

Listas Simplesmente Ligadas

typedef struct no{

int dado; // o dado poderia ser de qualquer tipo

struct no *prox; // ponteiro para próximo

elemento

} NO

typedef struct lista{

NO *head;

}LISTA

Page 42: Ed1

Listas Simplesmente Ligadas As operações básicas sobre a estrutura são: criação da lista e de nó.

1. Criação de lista

LISTA *criaLista(){

LISTA *L;

L=(LISTA *)malloc(sizeof(LISTA));

L->head = NULL;

return L;

}

2. Criação de nó

NO *criaNo(int dado){

NO *no;

no = (NO *)malloc(sizeof(NO));

no->dado = dado;

no->prox = NULL;

return no;

}

Page 43: Ed1

Listas Simplesmente Ligadas Ao contrário dos vectores, onde acessamos qualquer

elemento usando um índice numérico, com uma lista ligada temos apenas acesso sequencial, por exemplo, para chegar ao decimo elemento temos que passar por todos nove elementos anteriores.

Para percorrer uma lista, precisamos obter seu primeiro elemento e então obtemos os elementos subsequentes seguindo os ponteiros de próximo elemento em cada nó.

NO *primeiro(LISTA *L){

return (L->head);

}

NO *proximo(NO *no){

return (no->prox);

}

Page 44: Ed1

Listas Simplesmente Ligadas Andar para trás em uma lista simplesmente ligada não é tão

simples, requer que a lista seja percorrida desde o início verificado se, para cada elemento x, proximo(x) é o elemento do qual procuramos o elemento anterior.

NO *anterior(LISTA *L, NO *no){

NO *p;

if(no==L->head)

return NULL;

p = primeiro(L);

while(proximo(p)!=NULL){

if(proximo(p) == no)

return p;

p = proximo(p);

}

return NULL;

}

Page 45: Ed1

Listas Simplesmente Ligadas

Obter o último elemento também requer que toda a lista seja percorrida.

NO *ultimo(LISTA *L){

NO *p;

p = primeiro(L);

if (p == NULL)

return NULL;

while(proximo(p)!=NULL)

p = proximo(p);

return p;

}

Page 46: Ed1

Listas Simplesmente Ligadas Podemos querer 3 tipos de inserções em uma lista:

◦ no início,

◦ no fim,

◦ ou em uma posição arbitrária.

Na lista simplesmente ligada a inserção no início é trivial: Fazemos o próximo do novo elemento apontar para o primeiro elemento da lista e fazemos com que o novo elemento seja o primeiro elemento (cabeça) da lista.

A inserção em posição arbitrária pode ser feito em tempo constante se tivermos um ponteiro para o elemento que será o elemento anterior ao novo elemento:

void insere_apos(NO *novo, NO *anterior){

novo->prox = anterior->prox;

antrior->prox = novo;

}

A inserção no final da lista exige que encontremos seu último elemento.

Page 47: Ed1

Listas Simplesmente Ligadas Remoção de um elemento arbitrário requer que

encontremos o elemento anterior ao removido.

void remove(LISTA *lista, NO *no){

NO *ant;

if(no == lista->head){

lista->head = no->prox;

} else{

ant = anterior(lista, no)

if(ant!=NULL)

ant->prox = no->prox;

}

free(no);

}

Page 48: Ed1

Listas duplamente ligadas

Listas duplamente ligadas são usadas

frequentemente nos problema que

exija uma movimentação eficiente

desde um nó quer para o seu

antecessor, quer para o seu sucessor.

frent

eNULL

NULL

Atrásvalor valor valor

Page 49: Ed1

Listas duplamente ligadas

Uma das virtudes de listas

duplamente ligadas, é o facto de nos

podemos deslocar para o nó anterior

ou para nó seguinte, a um nó dado

com facilidade.

Uma lista duplamente ligada permite-

nos remover um nó qualquer da lista

em tempo constante, usando apenas

um ponteiro para essa célula.

Page 50: Ed1

Listas duplamente ligadas -

operações anterior(): devolve o elemento anterior a

um determinado elemento.

cria(): devolve uma lista com um

elemento

insere(): insere um determinado

elemento na lista.

mostra(): mostra o conteúdo da lista.

procura(): devolve a posição de um

determinado elemento.

remove(): remove o elemento desejado

da lista.

Page 51: Ed1

Lista duplamente Ligada -

operações

#include<stdio.h>

#include<stdlib.h>

typedef struct listadl{

LDL *prox; //próximo elemento da lista

LDL *ant; //elemento anterior da lista

int dado;

}LDL;

Page 52: Ed1

Lista duplamente Ligada -

operaçõesDevolve uma lista com elemento elem.

LDL *cria(int elem){

LDL*tmp;

tmp =(LDL *)malloc(sizeof(tmp));

tmp->dado = elem;

tmp->prox = NULL;

tmp->ant = NULL;

return tmp;

}

Page 53: Ed1

Lista duplamente Ligada -

operaçõesInsere o elemento elem na lista L.

void *insere (int elem, LDL *L){

LDL*tmp;

tmp=L;

while((tmp->prox ==NULL)==0)

tmp = tmp->prox;

tmp->prox = cria(elem);

(tmp->prox)-> ant = tmp;

}

Page 54: Ed1

Lista duplamente Ligada -

operaçõesImprime todos os elementos da

lista.

void *mostra(LDL *L){

LDL*tmp, *no1, *no2;

tmp=L;

while((tmp->prox

==NULL)==0){

no1 = tmp->prox;

no2 = tmp->ant;

printf(“O elemento: %d”,

tmp->dado);

if((no1 == NULL)==0)

printf(“O proximo:%d”,

no1->dado);

else

printf(“Não possui

próximo.”);

if((no2 == NULL)==0)

printf(“O anterior:%d”,

no2->dado);

else printf(“Não possui

anterior.”);

tmp= tmp->prox;

}

}

Page 55: Ed1

Lista duplamente Ligada -

operaçõesRemove o elemento elem da lista L.

LDL *remove(int elem, LDL *L){

LDL*tmp, *proxno, *noant;

tmp=L;

while((tmp->prox ==NULL)==0){

proxno = tmp->prox;

noant = tmp->ant;

if((tmp->dado == elem)

if((noant == NULL)==0)

if((proxno == NULL)==0){

noant->prox =proxno;

proxno->ant =noant;

free(tmp);

return L;

}

else{

noant->prox=NULL;

free(tmp);

return L;

}

else if((proxno == NULL)==0){

proxno->ant =NULL;free(tmp);L = proxno;return L;

}else {

L = NULL;free(tmp);return L;

}else{

tmp = tmp -> prox;}

printf(“O elemento %d não se encontra na lista\n”, elem);

}

Page 56: Ed1

Lista duplamente Ligada -

operaçõesDevolve a posição do elemento elem.

int *procura(int elem, LDL *L){

LDL*tmp;

int pos = 0;

tmp=L;

while((tmp->prox ==NULL)==0)

if((tmp->dado) == elem){

printf(“A posição do elemento %d é %d\n”, elem, pos);

return pos;

}

else{

pos++;

tmp = tmp->prox;

}

printf(“O elemento não se encontra na lista”);

}

Page 57: Ed1

Lista duplamente Ligada -

operaçõesDevolve o elemento anterior a elem.

void *anterior(int elem, LDL *L){

LDL*tmp, *noant;

tmp=L;

while((tmp==NULL)==0)

if((tmp->dado) == elem){

noant = tmp->ant;

printf(“O elemento anterior a %d é %d\n”, elem, noant->dado);

else

printf(“Não existe anterior”);

exit();

}

else

tmp = tmp->prox;

printf(“O elemento não se encontra na lista”);

}

Page 58: Ed1

Lista Circular Ligada

As listas circulares são do tipo simples ou

duplamente ligadas, em que o nó final da lista a

ponta para o nó inicial. Este tipo de estrutura

adapta-se a situações do tipo FIFO (First In First Out).

dado dado dado dado

dado dado dado dado

Fig. 1 Lista circular ligada ( lista simplesmente ligada)

Fig. 2 Lista circular ligada ( lista duplamente ligada)

Page 59: Ed1

Lista circular Ligada -

operaçõestypedef struct no{

int dado;

struct no *prox;

}NO, LCSL;

NO cabeca;

1. Inicializa a lista circular

int inicializa(LCSL *cabeca)

{

*cabeca=NULL;

return 0;

}

2. Verifica se a lista está cheia

int vazia(LCSL lista){

return (lista==NULL)? 1:0;

}

3. Aloca e desaloca nó.

int aloca(LCSL *lista, int valor){

LCSL *tmp;

tmp= (LCSL) malloc(sizeof(no));

if (tmp==NULL)

return 1; // Erro …

*lista = tmp;

tmp->dado = valor;

tmp->prox = NULL;

return 0;

}

void liberta(LCSL *lista){

free(*lista);

*lista=NULL;

}

Page 60: Ed1

Lista circular Ligada -

operações4. Inserção de um item

int insere(LCSL *lista, int valor)

{

LCSL tmp;

if (aloca(&tmp, valor)== 1)

return 1; // Erro

if (vazia(*lista) == 1){ //True=1

tmp->prox = tmp;

*lista=tmp;

} else {

tmp->prox = *lista->prox;

*lista->prox = tmp;

}

return 0;

}

5. Adiciona um item ao fim da lista.

int adiciona(LCSL *lista, int valor){

LCSL tmp;if (aloca(&tmp, valor)== 1)

return 1; // Erroif (vazia(*lista) == 1) //True=1

tmp->prox = tmp;

else{

tmp->prox = *lista->prox;*lista->prox = tmp;}

*lista = tmp;return 0; //Ok

}

Page 61: Ed1

Lista circular Ligada -

operações6. Eliminação de um nó

int apagaNo(LCSL *lista, LCSL no)

{

LCSL tmp;if(vazia(*lista) == 1)

return 1;if(no==no->prox)*lista=NULL;

else {for(tmp=*lista->prox; tmp!=*lista && tmp->prox!=no; tmp=tmp-

>prox) ;if(tmp->prox !=no)return 1;

tmp->prox = no->prox;if(no== *lista)

*lista=tmp;

}

free(&no);

return 0;

}

Page 62: Ed1

Pilha e Fila

1. Pilha

• Implementação sequencial da pilha

2. Fila

I. Operações básicas

II. Implementação sequencial da fila

Page 63: Ed1

Pilhas

Pilhas são listas onde a inserção ou a

remoção de um item é feita no topo.

Definição:

dada a pilha P= (a[1], a[2], …, a[n]),

dizemos que a[1] é o elemento da base da

pilha; a[n] é o elemento topo da pilha; e

a[i+1] está acima de a[i].

Pilhas são conhecidas como listas LIFO

(last in first out)

Page 64: Ed1

Implementação de pilhas

Como lista sequencial ou ligada?◦ No caso geral de listas ordenadas, a maior

vantagem da alocação dinâmica sobre asequencial, se a memória não for problema, éa eliminação de deslocamento na inserção oueliminação dos elementos. No caso das pilhas,essas operações de deslocamento nãoocorrem. Portanto, alocação sequencial é maisvantajosa na maioria das vezes.

◦ Como o acesso a pilha é limitado ao últimoelemento inserido, todos os elementos do meiodo vector devem estar preenchidos. Hádesvantagens quando a quantidade de dadosa ser armazenados não é conhecidoantecipadamente.

Page 65: Ed1

Pilha

Page 66: Ed1

Implementação Sequencial

#define MAXPILHA 50

#define tpilha(p) (p->topo – p->base)

typedef struct pilha{

int base[MAXPILHA];

int *topo;

}PILHA;

Page 67: Ed1

Implementação Sequencial1. Inicializa a pilha vazia

int inicializa(PILHA *pp){

pp->topo = pp->base;

return 1;

}

2. Verifica se pilha está vazia

int vazia(PILHA *pp){

return (pp->topo == pp->base) ? 1 : 0;

}

3. Coloca dado na pilha. Apresenta erro se não haver espaço.

int push(PILHA *pp, int dado){

if(tpilha(pp) == MAXPILHA)

return 0;

*pp->topo=dado;

pp->topo++;

return 1;

}

Page 68: Ed1

Implementação Sequencial4. Retira o valor do topo da pilha a atribui a dado.

int pop (PILHA *pp, int dado){

if(vazia(pp) == 1)

return 0;

pp->topo--;

*dado = *pp->topo

return 1;

}

5. Retorna o valor do topo da pilha se removê-lo.

int topo(PILHA *pp, int *dado){

if(pop(pp, dado) == 0)

return 0;

return push (pp, dado)

}

Page 69: Ed1

Fila

Uma Fila é uma estrutura de dados do

tipo FIFO (First In First Out), cujo

funcionamento é inspirado no de uma

fila “natural”, na qual o primeiro

elemento a ser inserido é sempre o

primeiro elemento a ser retirado.

Page 70: Ed1

Fila

Uma fila tem por norma as seguintes funcionalidade:

Colocar e retirar dados da fila:◦ insere – guardar um elemento na fila

◦ remove – retirar um elemento da fila

◦ topo – retornar o elemento do topo da fila.

Testar se a fila está vazia ou cheia:◦ cheia – Verificar se a fila está cheia (não pode

guardar mais elementos).

◦ vazia – Verificar se a fila está vazia (não contém elementos)

Inicializar ou limpar:◦ inicializa – Colocar a fila num estado “pronto” a ser

utilizada

Page 71: Ed1

Fila – Outras Operações

Buscar por elementos que coincidam com certo

padrão (casamento de padrão)

Ordenar uma lista

Intercalar duas listas

Concatenar duas listas

Dividir uma lista em duas

Fazer cópia da lista

Page 72: Ed1

Fila

A implementação de uma fila pode ser

efectuada através da utilização de

diferentes estruturas de dados

(vectores, listas ligadas, árvores, etc.).

De seguida, apresenta-se duas

implementação de uma fila através da

utilização de vectores e listas ligadas.

Page 73: Ed1

Fila

Características das filas:

◦ Os dados são armazenados pela ordem de

entrada

Tipos de filas:

◦ Filas de espera (queues)

com duplo fim (deque “double-ended queue)

◦ Filas de espera com prioridades (priority

queues)

Implementação das filas:

◦ usando vectores/arrays (circulares ou não)

◦ utilizando um apontador para nós de

informação (lista ligada)

Page 74: Ed1

Fila

Implementação em C usando arrays

Vantagens:◦ Facilidade de implementação.

Desvantagens:◦ Vectores possuem um espaço limitado para

armazenamento de dados.

◦ Necessidade de definir um espaço grande o suficiente para a fila

◦ Necessidade de um indicador para o inicio e para o fim da fila

Método de Implementação◦ A adição de elementos à fila resulta no incremento do

indicador do fim da fila

◦ A remoção de elementos da fila resulta no incremento do indicador do inicio da fila

Page 75: Ed1

Implementação de Filas em C

Usamos o vector para armazenar os elementos da fila e duas variáveis, inic e fim, para armazenar as posições dentro do vector do primeiro e último elementos da fila:

#define MAXFILA 100

struct fila{int elem[MAXFILA];

int inic, fim;

}f;

É evidente que usar vector para armazenar uma fila introduz a possibilidade de estouro, caso a fila fique maior que o tamanho do vector.

Page 76: Ed1

Capitulo III: Árvores

Docente:

Dikiefu Fabiano, Msc

Page 77: Ed1

Sumário

3.1 Introdução

3.2 Tipos de árvores

3.3 Árvores binárias

3.3.1 Estrutura de uma árvore binária

3.3.2 Descrição

3.3.3 Altura

3.3.4 Representação em C

3.4 Árvores Genéricas

3.4.1 Estrutura de uma árvore genérica

3.4.2 Representação em C

3.4.3 Problemas com Representação

Page 78: Ed1

Introdução

As estruturas anteriores são chamadas

de unidimensionais (ou lineares)

◦ Exemplo são listas sequenciais e ligadas

Não podem ser usadas como

hierarquias.

◦ Exemplo: árvore de directórios

Árvore é uma estrutura adequada para

representar hierarquias

A forma mais natural para definirmos

uma estrutura de árvore é usando

recursividade

Page 79: Ed1

Estrutura de Árvores

Uma árvore é composta por um conjunto de nós.

Existe um nó r, denominado raiz, que contém zero ou mais sub-árvores, cujas raízes são ligadas a r.

Esses nós raízes das sub-árvores são ditos filhos do nó pai, no caso r.

Nós com filhos são chamados nós internos enós que não têm filhos são chamados folhas, ou nós externos.

É tradicional desenhar as árvores com a raiz para cima e folhas para baixo, ao contrário do que seria de se esperar.

Page 80: Ed1

Estrutura de Árvores

Por adoptarmos essa forma de representação gráfica, não representamos explicitamente a direcção dos ponteiros, subentendendo que eles apontam sempre do pai para os filhos.

Page 81: Ed1

Tipos de Árvores

O número de filhos permitido por nó e as informações armazenadas em cada nó diferenciam os diversos tipos de árvores existentes.

Existem dois tipos de árvores:◦ árvores binárias, onde cada nó tem, no

máximo, dois filhos.

◦ árvores genéricas, onde o número de filhos é indefinido.

Estruturas recursivas serão usadas como base para o estudo e a implementação das operações com árvores.

Page 82: Ed1

Árvore Binária

Um exemplo de utilização de árvores

binárias está na avaliação de

expressões.

Como trabalhamos com operadores

que esperam um ou dois operandos, os

nós da árvore para representar uma

expressão têm no máximo dois filhos.

Nessa árvore, os nós folhas

representam operandos e os nós

internos operadores.

Page 83: Ed1

Árvores Binárias

Esta árvore representa a expressão:(3+6)*(4-1)+5

Page 84: Ed1

Estrutura de uma AB

Numa árvore binária, cada nó tem

zero, um ou dois filhos.

De maneira recursiva, podemos

definir uma árvore binária como

sendo:

◦ uma árvore vazia; ou

◦ um nó raiz tendo duas sub-árvores,

identificadas como a sub-árvore da direita

(sad) e a sub-árvore da esquerda (sae).

Page 85: Ed1

Estrutura de uma AB

• Os nós a, b, c, d, e, f formam uma árvore binária da seguinte maneira:

• A árvore é composta do nó a, da subárvore à esquerda formada por b e d, e da sub-árvore à direita formada por c, e e f.

• O nó a representa a raiz da árvore e os nós b e c as raízes das sub-árvores.

• Finalmente, os nós d, e e f são folhas da árvore.

Page 86: Ed1

Descrição de AB

Para descrever árvores binárias,

podemos usar a seguinte notação

textual:

◦ A árvore vazia é representada por <>,

◦ e árvores não vazias por <raiz sae sad>.

Com essa notação, a árvore da

Figura anterior é representada por:

◦ <a<b<><d<><>>> <c<e<><>><f<><>>>>.

Page 87: Ed1

Descrição de AB

Uma sub-árvore de uma árvore

binária é sempre especificada como

sendo a sae ou a sad de uma árvore

maior, e qualquer das duas sub-

árvores pode ser vazia.

As duas árvores da Figura abaixo são

distintas.

Page 88: Ed1

Altura de uma AB

Uma propriedade fundamental de todas as árvores é que só existe um caminho da raiz para qualquer nó.

Podemos definir a altura de uma árvore como sendo o comprimento do caminho mais longo da raiz até uma das folhas.

A altura de uma árvore com um único nó raiz é zero e, por conseguinte, dizemos que a altura de uma árvore vazia é negativa e vale -1.

Page 89: Ed1

Representação em C

Podemos definir um tipo para representar uma árvore binária.

Vamos considerar que a informação a ser armazenada são valores de caracteres simples.

Vamos inicialmente discutir como podemos representar uma estrutura de árvore binária em C.

Que estrutura podemos usar para representar um nó da árvore?

Cada nó deve armazenar três informações: a informação propriamente dita, no caso um caractere, e dois ponteiros para as sub-árvores, à esquerda e à direita.

Page 90: Ed1

Representação em C

struct arv {

char info;

struct arv* esq;

struct arv* dir;

};

typedef struct arv Arv;

Da mesma forma que uma lista encadeada é representada por um ponteiro para o primeiro nó, a estrutura da árvore como um todo é representada por um ponteiro para o nó raiz.

Page 91: Ed1

Criando Árvores

Arv* inicializa(void)

{

return NULL;

}

Arv* cria(char c, Arv* sae, Arv* sad)

{Arv* p=(Arv*)malloc(sizeof(Arv));

p->info = c;

p->esq = sae;

p->dir = sad;

return p;

}

Page 92: Ed1

Árvore Vazia

int vazia(Arv* a)

{

return a==NULL;

}

Page 93: Ed1

Exemplo

Exemplo: Usando as operações

inicializa e cria, crie uma estrutura que

represente a seguinte árvore.

Page 94: Ed1

Exemplo

/* sub-árvore com 'd' */

Arv* a1= cria('d',inicializa(),inicializa());

/* sub-árvore com 'b' */

Arv* a2= cria('b',inicializa(),a1);

/* sub-árvore com 'e' */

Arv* a3= cria('e',inicializa(),inicializa());

/* sub-árvore com 'f' */

Arv* a4= cria('f',inicializa(),inicializa());

/* sub-árvore com 'c' */

Arv* a5= cria('c',a3,a4);

/* árvore com raiz 'a'*/

Arv* a = cria('a',a2,a5 );

Page 95: Ed1

Exemplo

Alternativamente, a árvore poderia ser criada

recursivamente com uma única atribuição,

seguindo a sua estrutura.

Como isso pode ser feito?

Page 96: Ed1

Exemplo

Arv* a = cria('a',

cria('b',

inicializa(),

cria('d', inicializa(), inicializa())

),

cria('c',

cria('e', inicializa(), inicializa()),

cria('f', inicializa(), inicializa())

)

);

Page 97: Ed1

Exibe Conteúdo da Árvore

void imprime (Arv* a)

{

if (!vazia(a)){

printf("%c ", a->info); /* mostra raiz */

imprime(a->esq); /* mostra sae */

imprime(a->dir); /* mostra sad */

}

}

Como é chamada essa forma de exibição?

E para exibir na forma in-fixada? E na pós-fixada?

Page 98: Ed1

Liberando Memória

Arv* libera (Arv* a){

if (!vazia(a)){

libera(a->esq); /* libera sae */

libera(a->dir); /* libera sad */

free(a); /* libera raiz */

}

return NULL;

}

Page 99: Ed1

Criação e Liberação

Vale a pena notar que a definição de

árvore, por ser recursiva, não faz

distinção entre árvores e sub-árvores.

Assim, cria pode ser usada para

acrescentar uma sub-árvore em um

ramo de uma árvore, e libera pode ser

usada para remover uma sub-árvore

qualquer de uma árvore dada.

Page 100: Ed1

Criação e Liberação

Considerando a criação da árvore feita anteriormente,

podemos acrescentar alguns nós, com:

a->esq->esq = cria('x',

cria('y',inicializa(),inicializa()),

cria('z',inicializa(),inicializa())

);

E podemos liberar alguns outros, com:

a->dir->esq = libera(a->dir->esq);

Deixamos como exercício a verificação do resultado

final dessas operações.

Page 101: Ed1

Buscando um Elemento

Essa função tem como retorno um valor booleano (um ou zero) indicando a ocorrência ou não do carácter na árvore.

int busca (Arv* a, char c){

if (vazia(a))

return 0; /* não encontrou */

else

return a->info==c ||busca(a->esq,c) ||busca(a->dir,c);

}

Page 102: Ed1

Buscando um Elemento

Podemos dizer que a expressão:return c==a->info || busca(a->esq,c) ||

busca(a->dir,c);

é equivalente a:if (c==a->info)

return 1;

else if (busca(a->esq,c))

return 1;

else

return busca(a->dir,c);

Page 103: Ed1

Árvores Genéricas

Como vimos, numa árvore binária o número de filhos dos nós é limitado em no máximo dois.

No caso da árvore genérica, esta restrição não existe.

Cada nó pode ter um número arbitrário de filhos.

Essa estrutura deve ser usada, por exemplo, para representar uma árvore de directórios.

Supor que não há árvore vazia.

Page 104: Ed1

Exemplo

Page 105: Ed1

Representação em C

Devemos levar em consideração o número de filhos que cada nó pode apresentar.

Se soubermos que numa aplicação o número máximo de filhos é 3, podemos montar uma estrutura com 3 campos apontadores, digamos, f1, f2 e f3.

Os campos não utilizados podem ser preenchidos com o valor nulo NULL, sendo sempre utilizados os campos em ordem.

Assim, se o nó n tem 2 filhos, os campos f1 e f2 seriam utilizados, nessa ordem, ficando f3 vazio.

Page 106: Ed1

Representação em C

Prevendo um número máximo de filhos igual a 3,e considerando a implementação de árvores paraarmazenar valores de caracteres simples, a declaração do tipo que representa o nó da árvore poderia ser:

struct arv3 {

char val;

struct arv3 *f1, *f2, *f3;

};

Page 107: Ed1

Exemplo

Page 108: Ed1

Problemas com

Representação Como se pode ver no exemplo, em cada um dos

nós que tem menos de três filhos, o espaço correspondente aos filhos inexistentes é desperdiçado.

Além disso, se não existe um limite superior no número de filhos, esta técnica pode não ser aplicável.

O mesmo acontece se existe um limite no número de nós, mas esse limite será raramente alcançado, pois estaríamos tendo um grande desperdício de espaço de memória com os campos não utilizados.

Há alguma solução para contornar tal problema?

Page 109: Ed1

Solução

Uma solução que leva a um aproveitamento melhor do

espaço utiliza uma “lista de filhos”: um nó aponta apenas

para seu primeiro (prim) filho, e cada um de seus filhos,

excepto o último, aponta para o próximo (prox) irmão.

A declaração de um nó pode ser:struct arvgen {

char info;

struct arvgen *prim;

struct arvgen *prox;

};

typedef struct arvgen ArvGen;

Page 110: Ed1

Exemplo da Solução

Page 111: Ed1

Exemplo da Solução

Uma das vantagens dessa representação é que podemos percorrer os filhos de um nó de forma sistemática, de maneira análoga ao que fizemos para percorrer os nós de uma lista simples.

Com o uso dessa representação, a generalização da árvore é apenas conceitual, pois, concretamente, a árvore foi transformada em uma árvore binária, com filhos esquerdos apontados por prime direitos apontados por prox.

Page 112: Ed1

Criação de uma ÁrvoreGen

ArvGen* cria (char c)

{

ArvGen *a =(ArvGen*)malloc(sizeof(ArvGen));

a->info = c;

a->prim = NULL;

a->prox = NULL;

return a;

}

Page 113: Ed1

Inserção

Como não vamos atribuir nenhum significado especial para a posição de um nó filho, a operação de inserção pode inserir a sub-árvore em qualquer posição.

Vamos optar por inserir sempre no início da lista que, como já vimos, é a maneira mais simples de inserir um novo elemento numa lista ligada.

void insere (ArvGen* a, ArvGen* f)

{

f->prox = a->prim;

a->prim = f;

}

Page 114: Ed1

Exemplo Criação ÁrvoreGen

Com as funções cria e insere como

construir a árvore abaixo?

Page 115: Ed1

Exemplo Criação ÁrvoreGen

/* cria nós como folhas */

ArvGen* a = cria('a');

ArvGen* b = cria('b');

ArvGen* c = cria('c');

ArvGen* d = cria('d');

ArvGen* e = cria('e');

ArvGen* f = cria('f');

ArvGen* g = cria('g');

ArvGen* h = cria('h');

ArvGen* i = cria('i');

ArvGen* j = cria('j');

...

/* monta a hierarquia */

insere(c,d);

insere(b,e);

insere(b,c);

insere(i,j);

insere(g,i);

insere(g,h);

insere(a,g);

insere(a,f);

insere(a,b);

Page 116: Ed1

Impressão (pré-ordem)

void imprime (ArvGen* a)

{

ArvGen* p;

printf("%c\n",a->info);

for (p=a->prim; p!=NULL; p=p-

>prox)

imprime(p);

}

Page 117: Ed1

Busca de Elemento

int busca (ArvGen* a, char c)

{

ArvGen* p;

if (a->info==c)

return 1;

else {

for (p=a->prim; p!=NULL; p=p->prox) {

if (busca(p,c))

return 1;

}

}

return 0;

}

Page 118: Ed1

Liberação de Memória

O único cuidado que precisamos

tomar na programação dessa função

é a de liberar as subárvores antes

de liberar o espaço associado a um

nó (isto é, usar pós-ordem).

Page 119: Ed1

Liberação de Memória

void libera (ArvGen* a){

ArvGen* p = a->prim;

while (p!=NULL) {

ArvGen* t = p->prox;

libera(p);

p = t;

}

free(a);

}

Page 120: Ed1

Capitulo IV - Ordenação

Docente: Dikiefu Fabiano, Msc

Page 121: Ed1

Sumário

121

4. Ordenação Interna

4.1 Ordenação por Selecção

4.2 Ordenação por Inserção

4.3 Shellsort

4.4 Quicksort

4.5 Heapsort

Page 122: Ed1

Conceitos Básicos

122

Ordenar: processo de rearranjar um conjunto de

objectos em uma ordem ascendente ou descendente.

A ordenação visa facilitar a recuperação posterior de

itens do conjunto ordenado.

A maioria dos métodos de ordenação é baseada em

comparações das chaves.

Existem métodos de ordenação que utilizam o princípio

da distribuição.

.

Page 123: Ed1

Conceitos Básicos

Notação utilizada nos algoritmos:

• Os algoritmos trabalham sobre os

registos de um arquivo.

• Cada registo possui uma chave

utilizada para controlar a ordenação.

• Podem existir outros componentes

em um registo

Page 124: Ed1

Conceitos básicos

124

Um método de ordenação é estável se a ordem

relativa dos itens com chaves iguais não se altera

durante a ordenação.

• Alguns dos métodos de ordenação mais eficientes

não são estáveis.

• A estabilidade pode ser forçada quando o método é

não-estável.

• Sedgewick (1988) sugere agregar um pequeno índice

a cada chave antes de ordenar, ou então aumentar a

chave de alguma outra forma.

Page 125: Ed1

Classificação dos métodos de

ordenação

125

1. Ordenação interna: arquivo a ser ordenado cabe todo

na memória principal.

2. Ordenação externa: arquivo a ser ordenado não cabe

na memória principal.

Diferenças entre os métodos:

Em um método de ordenação interna, qualquer

registo pode ser imediatamente cessado.

Em um método de ordenação externa, os registos

são cessados sequencialmente ou em grandes blocos.

Page 126: Ed1

Classificação dos métodos de ordenação

interna

126

Métodos simples:

• Adequados para pequenos arquivos.

• Produzem programas pequenos.

Métodos eficientes:

• Adequados para arquivos maiores.

• Usam menos comparações.

• As comparações são mais complexas nos

detalhes.

Métodos simples são mais eficientes para pequenos

arquivos.

Page 127: Ed1

Ordenação por Selecção

127

Um dos algoritmos mais simples de ordenação.

Algoritmo:

Seleccione o menor item do vector.

Troque-o com o item da primeira posição do vector. Repita essas duas operações com os n − 1 itens restantes, depois

com os n − 2 itens, até que reste apenas um elemento.

• As chaves em negrito sofreram uma troca entre si.

Page 128: Ed1

Ordenação por Selecção

128

Vantagens:

Custo linear no tamanho da entrada para o número

de movimentos de registos.

É o algoritmo a ser utilizado para arquivos com

registos muito grandes.

É muito interessante para arquivos pequenos.

Desvantagens:

O facto de o arquivo já estar ordenado não ajuda em

nada, pois o custo continua quadrático.

O algoritmo não é estável.

Page 129: Ed1

Ordenação por Selecção

129

void selectionSort(int vector[],int tam) {

int i, j, min, aux;

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

min = i;

aux = vector[i];

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

if (vector[j] < aux) {

min=j; aux=vector[j];

}

}

aux = vector[i];

vector[i] = vector[min];

vector[min] = aux;

}

}

Page 130: Ed1

Código da ordenação SelectionSort com strings

void ordenar_seleccao() {

int j;

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

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

if(strcmpi(vector[i], vector[j])>0) {

strcpy(aux_char, vector[i]);

strcpy(vector[i], vector[j]);

strcpy(vector[j], aux_char);

}

}

}

}

Page 131: Ed1

Bubble Sort

O bubble sort, ou ordenação por flutuação (literalmente "por bolha"), é um algoritmo de ordenação dos mais simples. A ideia é percorrer o vector diversas vezes, a cada passagem fazendo flutuar para o topo o maior elemento da sequência. Essa movimentação lembra a forma como as bolhas em um tanque de água procuram seu próprio nível, e disso vem o nome do algoritmo

Page 132: Ed1

void bubble( int v[], int qtd ) {

int i; int j, aux, k = qtd - 1 ;

for(i = 0; i < qtd; i++) {

for(j = 0; j < k; j++) {

if(v[j] > v[j+1]) {

aux = v[j];

v[j] = v[j+1];

v[j+1]=aux;

}

}

}

}

Page 133: Ed1

void swapbubble( int v[], int i ) {

aux = v[i];

v[i] = v[i+1];

v[i+1]=aux;

}

void bubble( int v[], int qtd ) {

int i,j;

for( j = 0; j < qtd; j++ ) {

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

if( v[i] > v[i+1] ) {

swapbubble( v, i );

}

}

}

}

Page 134: Ed1

Método de ordenação Bolha com ordenação de strings.

void bubble(int v[], int qtd){

int i, trocou;

char aux;

do {

qtd--;

trocou = 0;

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

if(strcasecmp(v[i],v[i + 1])>0) {

strcpy(aux, v[i]);

strcpy(v[i], v[i + 1]);

strcpy(v[i + 1], aux);

trocou = 1;

}

}while(trocou==1);

}

Page 135: Ed1

Ordenação por Inserção

135

Método preferido dos jogadores de cartas.

Algoritmo:• Em cada passo a partir de i =2 faça:

• Seleccione o i-ésimo item da sequência fonte.

• Coloque-o no lugar apropriado na sequência destino de

acordo com o critério de ordenação.

• As chaves em negrito representam a sequência destino.

Page 136: Ed1

Ordenação por Inserção

136

void insertionSort(int vector[], int tam) {

int i, j; int key;

for (j = 1; j < tam; ++j) {

key = vector[j];

i = j - 1;

while (vector[i] > key && i >= 0) {

vector[i+1] = vector[i];

--i;

}

vector[i+1] = key;

}

}

A colocação do item no seu lugar apropriado na

sequência destino é realizada movendo-se itens com

chaves maiores para a direita e então inserindo o item

na posição deixada vazia.

Page 137: Ed1

Ordenação por Inserção

137

Considerações sobre o algoritmo:

O processo de ordenação pode ser

terminado pelas condições:

• Um item com chave menor que o item

em consideração é encontrado.

• O final da sequência destino é atingido à

esquerda.

Solução:

• Utilizar um registo sentinela na posição

zero do vector.

Page 138: Ed1

Ordenação por Inserção

138

O número mínimo de comparações e movimentos

ocorre quando os itens estão originalmente em

ordem.

O número máximo ocorre quando os itens estão

originalmente na ordem reversa.

É o método a ser utilizado quando o arquivo está

“quase” ordenado.

É um bom método quando se deseja adicionar

uns poucos itens a um arquivo ordenado, pois o

custo é linear.

O algoritmo de ordenação por inserção é estável.

Page 139: Ed1

Shellsort

139

Proposto por Shell em 1959.

É uma extensão do algoritmo de ordenação por

inserção.

Problema com o algoritmo de ordenação por

inserção:

• Troca itens adjacentes para determinar o ponto

de inserção.• São efectuadas n − 1 comparações e

movimentações quando o menor item está na

posição mais à direita no vector.

O método de Shell contorna este problema

permitindo trocas de registos distantes um do

outro.

Page 140: Ed1

Shellsort

140

Os itens separados de h posições são

rearranjados. Todo h-ésimo item leva a uma sequência

ordenada. Tal sequência é dita estar h-ordenada.

Exemplo de utilização:

• Quando h = 1 Shellsort corresponde ao algoritmo de

inserção.

Page 141: Ed1

Shellsort

141

• Como escolher o valor de h:

Sequência para h:

h(s) = 3h(s − 1) + 1, para s > 1

h(s) = 1, para s = 1.

Knuth (1973, p. 95) mostrou experimentalmente

que esta sequência é difícil de ser batida por mais

de 20% em eficiência. A sequência para h corresponde a 1, 4, 13, 40, 121,

364, 1.093, 3.280, . . .

Page 142: Ed1

Shellsort

142

void shellsort ( Int v [ ] , int n) {

int i,j,x,h = 1;

do h = h *3 + 1;

while (h < n) ;

do {

h /= 3;

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

x = v[ i ] ; j = i ;

while (v[ j − h] > x > 0) {

v[ j ] = v[ j − h] ; j −= h;

i f ( j <= h) break;

}

v[ j ] = x;

}

} while (h != 1) ;

}

A implementação do Shellsort não utiliza registos

sentinelas. Seriam necessários h registos sentinelas, uma para

cada h-ordenação.

Page 143: Ed1

Shellsort

143

Vantagens:

• Shellsort é uma óptima opção para

arquivos de tamanho moderado.

• Sua implementação é simples e requer uma

quantidade de código pequena.

Desvantagens:

• O tempo de execução do algoritmo é

sensível à ordem inicial do arquivo.

• O método não é estável,

Page 144: Ed1

Quicksort

144

Proposto por Hoare em 1960 e publicado em

1962.

É o algoritmo de ordenação interna mais rápido

que se conhece para uma ampla variedade de

situações.

Provavelmente é o mais utilizado.

A ideia básica é dividir o problema de ordenar um

conjunto com n itens em dois problemas menores.

Os problemas menores são ordenados

independentemente.

Os resultados são combinados para produzir a

solução final.

Page 145: Ed1

Quicksort

145

A parte mais delicada do método é relativa ao método

partição. O vector v[esq..dir ] é rearranjado por meio da escolha

arbitrária de um pivô x.

O vector v é particionado em duas partes:

• A parte esquerda com chaves menores ou iguais a x.

• A parte direita com chaves maiores ou iguais a x.

Page 146: Ed1

Quicksort

146

Algoritmo para o particionamento:1. Escolha arbitrariamente um pivô x.

2. Percorra o vector a partir da esquerda até que v[i]

x.

3. Percorra o vector a partir da direita até que v[j] x.

4. Troque v[i] com v[j].

5. Continue este processo até os apontadores i e j se

cruzarem.• Ao final, o vector v[esq..dir ] está particionado de

tal forma que:• Os itens em v[esq], v[esq + 1], . . . , v[j] são

menores ou iguais a x.

• Os itens em v[i], v[i + 1], . . . , v[dir ] são

maiores ou iguais a x.

Page 147: Ed1

Quicksort

147

• Ilustração do processo de partição:

O pivô x é escolhido como sendo v[(i+j) / 2].

Como inicialmente i = 1 e j = 6, então x = v[3] = D.

Ao final do processo de partição i e j se cruzam em i =

3 e j = 2.

Page 148: Ed1

void swap(int* a, int* b) {

int tmp;

tmp = *a;

*a = *b;

*b = tmp;

}

int partition(int vec[], int left, int right) {

int i, j;

i = left;

for (j = left + 1; j <= right; ++j) {

if (vec[j] < vec[left]) {

++i;

swap(&vec[i], &vec[j]);

}

}

swap(&vec[left], &vec[i]);

return i;

}

Quicksort

148

Page 149: Ed1

Quicksort

149

Método ordena e algoritmo Quicksort :

void quickSort(int vec[], int left, int right) {

int r;

if (right > left) {

r = partition(vec, left, right);

quickSort(vec, left, r - 1);

quickSort(vec, r + 1, right);

}

}

Page 150: Ed1

Implementação usando 'fat pivot':

void sort(int array[], int begin, int end) {

int pivot = array[begin];

int i = begin + 1, j = end, k = end, t;

while (i < j) {

if (array[i] < pivot) i++;

else if (array[i] > pivot) {

j--;

k--;

t = array[i];

array[i] = array[j];

array[j] = array[k];

array[k] = t;

Page 151: Ed1

} else {

j--;

swap(array[i], array[j]);

}

}

i--;

swap(array[begin], array[i]);

if (i - begin > 1)

sort(array, begin, i);

if (end - k > 1)

sort(array, k, end);

}

Page 152: Ed1

Lembrando que quando você for chamar

a função recursiva terá que chamar a

mesma da seguinte forma

ordenar_quicksort(0,n-1). O 0(zero)

serve para o início receber a posição

zero do vector e o fim será o tamanho

do vector -1.

Page 153: Ed1

void ordenar_quicksort(int ini, int fim) {

int i = ini, f = fim;

char pivo[50];

strcpy(pivo,vector[(ini+fim)/2]);

if (i<=f) {

while (strcmpi(vector[i],pivo)<0)

i++;

while (strcmpi(vetor[f],pivo)>0)

f--;

if (i<=f) {

strcpy (aux_char,vetor[i]);

strcpy (vetor[i],vetor[f]);

strcpy (vetor[f],aux_char);

i++; f--;

}

}

Page 154: Ed1

if (f>ini)

ordenar_quicksort_nome(ini,f);

if (i<fim)

ordenar_quicksort_nome(i,fim);

}

Page 155: Ed1

Exemplo do estado do vector em cada chamada

recursiva do procedimento Ordena:

• O pivô é mostrado em negrito.

Quicksort

155

Page 156: Ed1

Quicksort

156

Vantagens:

• É extremamente eficiente para ordenar

arquivos de dados.

• Necessita de apenas uma pequena pilha como

memória auxiliar.

Desvantagens:

• Sua implementação é muito delicada e difícil:

Um pequeno engano pode levar a efeitos

inesperados para algumas entradas de dados.

• O método não é estável.

Page 157: Ed1

Heapsort

157

Possui o mesmo princípio de funcionamento da

ordenação por selecção.

Algoritmo:

1. Seleccione o menor item do vector.

2. Troque-o com o item da primeira posição do

vector.3. Repita estas operações com os n − 1 itens

restantes, depois com os n − 2 itens, e assim

sucessivamente.

O custo para encontrar o menor (ou o maior) item entre n itens é n − 1 comparações.

Isso pode ser reduzido utilizando uma fila de

prioridades.

Page 158: Ed1

Heaps

158

É uma sequência de itens com chaves c[1], c[2], . . .

, c[n], tal que:

c[i] c[2i],

c[i] c[2i + 1], para todo i = 1, 2, . . . , n/2.

A definição pode ser facilmente visualizada em uma

árvore binária completa:

árvore binária completa:• Os nós são numerados de 1 a n.

• O primeiro nó é chamado raiz.• O nó k/2 é o pai do nó k, para 1 < k n.

• Os nós 2k e 2k + 1 são os filhos à esquerda e à

direita do nó k, para 1 k k/2

Page 159: Ed1

Heaps

159

As chaves na árvore satisfazem a condição do

heap.

As chaves em cada nó são maiores do que as

chaves em seus filhos.

A chave no nó raiz é a maior chave do conjunto.

Uma árvore binária completa pode ser

representada por um arranjo:

A representação é extremamente compacta.

Permite caminhar pelos nós da árvore facilmente. Os filhos de um nó i estão nas posições 2i e 2i + 1.

O pai de um nó i está na posição i/2.

Page 160: Ed1

Heaps

160

Na representação do heap em um arranjo, a

maior chave está sempre na posição 1 do vector.

Os algoritmos para implementar as operações

sobre o heap operam ao longo de um dos

percursos da árvore.

Um algoritmo elegante para construir o heap foi

proposto por Floyd em 1964.

O algoritmo não necessita de nenhuma memória

auxiliar. Dado um vector v[1], v[2], . . . , v[n].

Os itens v[n/2 + 1], v[n/2 + 2], . . . , v[n] formam um

heap:• Neste intervalo não existem dois índices i e j

tais que j = 2i ou j = 2i + 1.

Page 161: Ed1

Heapsort

161

void heapsort(tipo a[], int n) {

int i = n/2, pai, filho;

tipo t;

for (;;) {

if (i > 0) {

i--;

t = a[i];

} else {

n--;

if (n == 0)

return;

t = a[n];

a[n] = a[0];

}

Page 162: Ed1

pai = i;

filho = i*2 + 1;

while (filho < n) {

if ((filho + 1 < n) && (a[filho + 1] >

a[filho]))

filho++;

if (a[filho] > t) {

a[pai] = a[filho];

pai = filho;

filho = pai*2 + 1;

} else break;

}

a[pai] = t;

} } 162

Page 163: Ed1

Heapsort

163

Algoritmo:

Os itens de v[4] a v[7] formam um heap.

O heap é estendido para a esquerda (esq = 3), englobando o item

v[3], pai dos itens v[6] e v[7].

A condição de heap é violada:

• O heap é refeito trocando os itens D e S. O item R é incluindo no heap (esq = 2), o que não viola a condição de

heap. O item O é incluindo no heap (esq = 1).

A Condição de heap violada:

• O heap é refeito trocando os itens O e S, encerrando o

processo.

Page 164: Ed1

Heapsort

164

Exemplo da operação de aumentar o valor da chave do item na posição i:

O tempo de execução do procedimento AumentaChave em um item do heap é O(log n).

Page 165: Ed1

Construção de Heap

Algoritmo:

1. Construir o heap.2. Troque o item na posição 1 do vector (raiz do heap)

com o item da posição n.

3. Use o procedimento Refaz para reconstituir o heap para os itens v[1], v[2], . . . , v[n − 1].

4. Repita os passos 2 e 3 com os n − 1 itens restantes,

depois com os n − 2, até que reste apenas um item.

Page 166: Ed1

Heapsort

Exemplo de aplicação do Heapsort :

1 2 3 4 5 6 7

S R O E N A D

R N O E D A S

O N A E D R

N E A D O

E D A N

D A E

A D

• O caminho seguido pelo procedimento Refaz para reconstituir a

condição do heap está em negrito.

• Por exemplo, após a troca dos itens S e D na segunda linha da

Figura, o item D volta para a posição 5, após passar pelas posições 1

e 2.

Page 167: Ed1

Referências

1. Levitin, Anany “Introduction to the design and analysis of algorithm”

Addison-Wesley 2003

2. Pedro Neto, João “Programação, algoritmo e estrutura de dados”

escola editora 2008

3. Damas, Luís “Programação em C” …

4. Ziviani, Nivio e Botelho, Fabiano Cupertino, “Projecto de Algoritmos

com implementação em Pascal e C” editora Thomson, 2007

5. http://www.icmc.usp.br/~sce182/arvore.html

6. http://w3.ualg.pt/~hshah/ped

7. http://www.liv.ic.unicamp.br/~bergo/mc202/