CURSO DE PÓS-GRADUAÇÃO EM CIÊNCIA DA COMPUTAÇÃO
PROJETO E ANÁLISE DE ALGORITMOS
Aluno: Eduardo Freire Nakamura
e-mail: nakamura@dcc.ufmg.br

![]()
Sn=n
para a = 1.

assim,
![]() |
(1) |
e
![]() |
(2) |

ou seja,


![]()
![]()

![]()
![]()
PROCEDURE FazAlgo ( n : integer);
VAR i, j, k, x : integer;
BEGIN
x := 0;
FOR i := 1 TO n - 1 DO
FOR j := i + 1 TO n DO
FOR k := 1 TO j DO x := x + 1;
END;
Analisando o número de vezes que a variável x é incrementada, a um custo O(1), tem-se



![]()
![]()
![]()

fazendo
[Knu73], tem-se
![]()
![]()
![]()
![]()
PROCEDURE Pesquisa ( n : integer);
BEGIN
IF n > 1
THEN BEGIN
Inspecione n*n*n elementos; // custo n^3
Pesquisa (n/3);
END;
END;
Neste algoritmo é necessário analisar o número de inspeções feitas. Assim, considerando que a inspeção de um único elemento tem custo O(1), a seguinte relação de recorrência pode ser montada:

![]()
![]()
![]()
![]()

Como T(1)=0, fazendo n=3x tem-se
então

![\begin{displaymath}
T(n)=n^{3}\left[\frac{\left(3^{-3}\right)^{x}-1}{3^{-3}-1}\right]\end{displaymath}](img42.gif)
![]()
![]()
![]()
![]()
PROCEDURE Sort ( VAR A: ARRAY[1..n] OF integer;
i, j: integer );
{ n uma potencia de 2 }
BEGIN
IF i < j
THEN BEGIN
m := (i + j - 1)/2;
Sort(A,i,m); // custo = T(n/2)
Sort(A,m+1,j); // custo = T(n/2)
Merge(A,i,m,j); // custo = n-1 comparacoes no
// pior caso
{ Merge intercala A[i..m] e A[m+1..j] em A[i..j] }
END;
END;
Para este algoritmo é importante observar o número de comparações feitas para ordenar o vetor. Observe que este algoritmo divide o vetor em duas partes, ordena e combina estas partes através do procedimento Merge que faz no pior caso n-1 comparações. Assim, a relação de recorrência para este algoritmo é:

![]()
![]()
![]()
![]()
![]()

![]()
Considerando que n é uma potência de 2, ou seja, n=2x, tem-se que
![]()
![]()
PROCEDURE Proc( n : integer );
BEGIN
IF n = 0 THEN RETURN 1
ELSE RETURN Proc(n-1) + Proc(n-1)
END;
Neste algoritmo a medida de tempo pode ser considerada como o número de vezes em que é feita a soma da recursão Proc(n-1) + Proc(n-1). Portanto, a relação de recorrência para este algoritmo é:

T(n)=2T(n-1) + 20
2T(n-1)=22T(n-2) + 21
22T(n-2)=23T(n-3) + 22
23T(n-3)=24T(n-4) + 23
24T(n-4)=25T(n-5) + 24
![]()
2n-1T(1)=2nT(0) + 2n-1


![]()
T(n)=2n - 1 = O(2n)
Algoritmo
Elemento nEsimoMaiorElementoR(Arranjo A,
Arranjo B,
Indice inicioA,
Indice fimA,
Indice inicioB,
Indice fimB)
{
(1) int tamanhoAB, pivoA, pivoB;
(2) tamanhoAB = fimA - inicioA + 1;
(3) pivoA =(tamanhoAB != 1)?(tamanhoAB/2 + inicioA - 1):inicioA;
(4) pivoB =(tamanhoAB != 1)?(tamanhoAB/2 + inicioB - 1):inicioB;
/* A condicao de parada e quando restam dois ou menos
elementos a serem comparados */
(5) if( tamanhoAB == 1 ) {
(6) return (A[pivoA] > B[pivoB]) ? A[pivoA] : B[pivoB];
(7) }
(8) if( A[pivoA] > B[pivoB] ) {
/* Elimina os ganhadores de A e os perderores de B */
(9) fimB -= pivoA - inicioA + 1;
(10) inicioA = pivoA + 1;
(11) } else {
/* Elimina os ganhadores de B e os perderores de A */
(12) fimA -= pivoB - inicioB + 1;
(13) inicioB = pivoB + 1;
(14) }
/* Aplica novamente o algoritmo na metade restante do
problema */
(15) return nEsimoMaiorElementoR(A,B,inicioA,fimA,inicioB,fimB);
}
Análise de complexidade
Para a análise do custo será considerado o número de comparações entre os elementos de A e de B. Assim, pela linha (6) temos que T(1) = 1. Na linha (8) é feita uma única comparação e, nas linhas (9) e (10) metade dos elementos de A e B são descartados. A seguir, na linha (15) o algoritmo é aplicado novamente nas metades restantes de A e B de maneira que T(n) = 1 + T(n/2) para n>1. Desta forma a relação de recorrência para o algoritmo é

![]()
![]()
![]()
![]()
![]()

![]()
Assumindo que n é uma potência de 2, ou seja, n = 2x (resultando em
), temos que
| |
(3) |
Descrição geral e decisões de projeto
Para facilitar a legibilidade e a análise da complexidade, o algoritmo foi escrito de forma recursiva.
O ambiente de desenvolvimento e de testes utilizado foi uma estação UltraSparc da SUN com sistema
operacional Unix. A linguagem de implementação foi o C utilizando o compilador gcc.
Com o objetivo de simplificar a implementação respeitando de forma fiel à especificação do problema (especificação da questão 3) os arranjos foram gerados com N+1 elementos sendo considerado apenas os elementos de índice 1 a N.
Descrição dos módulos
O programa implementado é constituído de dois módulos:
O módulo rand é utilizado somente pelo módulo nmaior. O módulo principal (main) faz uso direto apenas do módulo nmaior.
Tipos de dados utilizados
No arquivo nmaior.h são definidos os seguintes tipos de dados e constantes:
Descrição do formato de entrada
Os arranjos A e B são gerados automaticamente basta definir o valor da constante N no arquivo nmaior.h.
Descrição do formato de saída
A saída é textual seguindo o formato abaixo.
Arranjo A: 4065 2031 1011 499 245 113 51 21 5 1 Arranjo B: 6318 3158 1570 782 388 190 94 42 12 0 O 10o. maior elemento em O(lg(N)) eh: 245
Utilização do programa
Após compilado, basta executar a seguinte linha de comando: ``./main''.
Listagem de Testes
A seguir são listados alguns testes executados.
Arranjo A: 4065 2031 1011 499 245 113 51 21 5 1 Arranjo B: 6318 3158 1570 782 388 190 94 42 12 0 O 10o. maior elemento em O(lg(N)) eh: 245
Arranjo A: 100 95 90 85 80 Arranjo B: 10 9 8 7 5 O 5o. maior elemento em O(lg(N)) eh: 80
Arranjo A: 100 95 89 85 Arranjo B: 101 90 81 75 O 4o. maior elemento em O(lg(N)) eh: 90
Arranjo A: 87 8 7 5 1 Arranjo B: 777 654 92 89 75 O 5o. maior elemento em O(lg(N)) eh: 87
![]() |
Para encontrar o limite inferior para este problema podemos utilizar a árvore de
decisão [CLR90].
Montagem da árvore de decisão
A árvore de decisão
representa o número de comparações entre elementos de A e de B quando o algoritmo opera sobre uma entrada
de tamanho n. Na árvore de decisão, cada nodo é denotado por (A[i]:B[j]) para algum
e
. Cada nó-folha da árvore representa uma das possíveis soluções (figura 1) com os n
maiores elementos de A e B e são denotados por
ou
onde
, A[k] ou B[k] é o n-ésimo maior elemento e ``?''
representam os elementos maiores do que ele. Assim, o total de nós-folhas é 2n. Como todos os elementos de
A e B são distintos, nos interessa somente a comparação A[i]<B[j] ou A[i]>B[j]. A execução do algoritmo
ótimo corresponde a um caminho qualquer
do nó-raiz a um nó-folha. Em cada nodo a comparação A[i]<B[j] é feita. A sub-árvore da esquerda indica as
possíveis comparações subseqüentes para A[i]<B[j]. A sub-árvore da direita indica as possíveis comparações
subseqüentes para A[i]>B[j]. Note que montando a árvore de decisão
desta maneira, ela é mínima, ou seja, não existe duas computações que possam levar a respostas equivalentes.
Ou de maneira mais formal:
Sejam Alg o algoritmo usado para montar a árvore de decisão acima, Si = Alg(A,B) a solução do algoritmo,
Ci o caminho percorrido para encontrar Si e n o tamanho de A e B. A árvore montada acima possui as
seguintes características:
| (4) |
ou seja,
![]() |
(5) |
Conforme o custo obtido em (3) o algoritmo apresentado no item (a) da questão 3 é ótimo.
Considere o algoritmo para pesquisar e inserir registros em uma árvore binária de pesquisa sem balanceamento. Devido à sua simplicidade e eficiência a árvore binária de pesquisa é considerada uma estrutura de dados muito útil. Considerando-se que a altura da árvore corresponde ao tamanho da pilha necessária para pesquisar na árvore é importante conhecer o seu valor. Assim sendo,
Para determinar a altura da árvore de forma empírica, foram utilizados valores
de n (número de nós) tais que n = 2i com
. Para cada
valor de n foram geradas 50 árvores randômicas calculando-se a altura média
he(n) (tabela 1). Admitindo que a altura esperada é
foi calculado o valor de k0 para cada n. Pelos
valores de k0 exibidos na tabela 1 e a curva da
figura 5, percebemos que k0 cresce com o valor n
contudo, quanto maior o valor de n menor é o crescimento de k0 sugerindo
que quando n tende a infinito, o valor de k0 é realmente constante. Assim,
o valor de k0 torna-se significativo quando n cresce, tendendo ao infinito.
Para os valores simulados podemos calcular um k0-medio da seguinte forma:

onde k0(i) são os valores da tabela 1.
| n | he(n) | hb(n) | hv(n) | k0(n) |
| 2 | 1 | 1 | 3 | 1,000 |
| 4 | 3 | 2 | 6 | 1,085 |
| 8 | 4 | 3 | 9 | 1,260 |
| 16 | 6 | 4 | 12 | 1,488 |
| 32 | 9 | 5 | 15 | 1,662 |
| 64 | 11 | 6 | 18 | 1,738 |
| 128 | 14 | 7 | 21 | 1,913 |
| 256 | 17 | 8 | 24 | 2,001 |
| 512 | 19 | 9 | 27 | 2,051 |
| 1024 | 21 | 10 | 30 | 2,095 |
| 2048 | 24 | 11 | 33 | 2,165 |
| 4096 | 27 | 12 | 36 | 2,193 |
| 8192 | 30 | 13 | 39 | 2,287 |
| 16392 | 33 | 14 | 42 | 2,294 |
| 32784 | 35 | 15 | 45 | 2,325 |
| 65536 | 38 | 16 | 48 | 2,364 |
| 131072 | 42 | 17 | 51 | 2,429 |
| 262144 | 44 | 18 | 54 | 2,428 |
| 524288 | 47 | 19 | 57 | 2,426 |
| 1048576 | 50 | 20 | 60 | 2,467 |
![]() |
Algoritmo de inserção
void Insere(Registro x, Apontador *p)
{
if (*p == NULL) {
*p = (Apontador) malloc(sizeof(Nodo));
(*p)->Reg = x;
(*p)->Esq = NULL;
(*p)->Dir = NULL;
return;
}
if (x.Chave < (*p)->Reg.Chave) {
Insere(x, &(*p)->Esq);
return;
}
if (x.Chave > (*p)->Reg.Chave) Insere(x, &(*p)->Dir);
else printf(" Erro : Registro %d ja existe na arvore\n", x.Chave);
} /* Insere */
Pior caso
Analisando o algoritmo de inserção,o pior caso é quando os valores inseridos na árvore em ordem crescente ou decrescente resultando em uma lista linear, ou seja, h = n. Quando em ordem crescente, os elementos são inseridos sempre a esquerda da árvore. Quando em ordem decrescente, os elementos são inseridos sempre a direita da árvore. A figura 3 mostra um exemplo do pior caso da árvore binária randômica sem balanceamento.
Melhor caso
Novamente recorrendo ao algoritmo de inserção, o melhor caso
é quando os elementos são inseridos de forma
que a árvore resultante seja balanceada, neste caso, a relação
da altura com o número de nós é mostrada na tabela 2
de onde podemos concluir que n = 2h+1-1, ou seja,
. A figura 4
mostra um exemplo do melhor caso quando a árvore fica balanceada.
| h (altura) | n (número de nós) |
| 1 | |
| 1 | 3 |
| 2 | 7 |
| 3 | 15 |
Em [Dev86,Drm01,Drm02] é dito que quando
n tende a infinito a altura
esperada é
ou
. Veja que os valores
simulados sugerem um crescimento da constante proporcional ao valor de n, contudo,
podemos observar que quanto maior o valor de n menor é o crescimento da constante
k0 de maneira que quando n tende a infinito, o valor de k0 torna-se
independente do valor de n. Assim, os valores obtidos em simulação são coerentes
com o valor encontrado na literatura (figura 5).
A figura 5 mostra o comportamento da constante (obtido em simulação)
em relação ao valor 4.31107.
![]() |
Definições
Para demonstrar o número esperado de comparações da busca com sucesso serão necessárias
algumas definições enunciadas em [Knu73].
Nó Interno (Internal Node). Um nó que não é raiz nem folha da árvore binária.
Nó Externo (External Node). Um nó folha da árvore binária.
Comprimento de Caminho Externo (External Path Length) doravante referenciado
por
. Constitui a soma de todos os caminhos do nó-folha de uma árvore binária
para cada nó-externo.
Comprimento de Caminho Interno (Internal Path Length) doravante referenciado
por
. Constitui a soma de todos os caminhos do nó-folha de uma árvore binária
para cada nó-interno.
A relação entre o Comprimento de Caminho Externo e o Comprimento de Caminho Interno de uma árvore binária é
| |
(6) |
onde n é o número de nós da árvore.
Considerando Cn o número médio de comparações em uma busca com sucesso e
Cn' o número médio de comparações em uma busca sem sucesso.
Em uma busca por uma chave, assumindo que cada um dos n nós pode conter a chave procurada
com a mesma probabilidade. Podemos definir:
| |
(7) |
| |
(8) |
| |
(9) |
Pelas e as equações (6), (7) e (8) obtemos
| |
(10) |
Pelas equações (7) e (9) obtemos
| |
(11) |
| |
(12) |
Subtraindo (12) por
obtemos
| |
(13) |
Como C0'=0 podemos facilmente chegar em
| Cn' = 2Hn - 2 | (14) |
Substituindo (14) em (10) obtemos
| |
(15) |
onde Hn é o número harmônico. Pelo exercício 1, item (b),
, então
![]()
considerando n grande (tendendo a infinito), temos
![]()
mudando o logaritmo para base 2
![]()
This document was generated using the LaTeX2HTML translator Version 97.1 (release) (July 13th, 1997)
Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
The command line arguments were:
latex2html -split 0 tp1.
The translation was initiated by Eduardo Freire Nakamura on 9/26/2002