Escolar Documentos
Profissional Documentos
Cultura Documentos
Uma tabela em Python é uma lista com um ou mais campos por elemento.
Exemplos:
tabnum = [5, 7, 10, 8, 45, 12, 63, 29, 17, 12, 45, 29]
tabpessoas = [[“João”, 2567613], [“Antonio”, 4455625], [“Rafaela”, 1543675], [“Donald”, 37483930]]
tablivros = [[“Algoritmos”, “A. Santos”, 1973], [“Teoria dos Grafos”, “B. A. Souza”, 1995], [“Algebra Linear”, “K.
Oliveira”, 1983], [“Cálculo Numérico”, “L. A. Medeiros”, 1966], [“Lógica e Algoritmos”, “E. A N.. Amaral”, 1985],
[“Técnicas de Automação”, “N. Tomás”, 2004]]
Sem perda de generalidade, vamos considerar em nossos algoritmos uma tabela contendo apenas um campo por elemento.
A linguagem Python já possui como funções intrínsecas, as funções count e index cujo objetivo é exatamente procurar e
localizar elementos em uma tabela ou lista:
Possui ainda o operador in que verifica se determinado elemento está ou não numa tabela:
if x in lst: . . .
if x not in lst: . . .
while x in list: . . .
O nosso objetivo é entender como essas funções e operadores são implementados. A maneira de fazê-lo é percorrer a tabela
de alguma forma, procurando o elemento. Vamos então verificar com mais profundidade os algoritmos que fazem a busca,
ou os algoritmos usados nestas funções.
Busca em tabelas
Busca sequencial
Consiste em varrer uma tabela a procura de um determinado elemento, verificando ao final se o mesmo foi ou não
encontrado. Interessa apenas a primeira ocorrência do elemento.
A função busca abaixo, procura elemento igual a x numa lista a de n elementos, devolvendo –1 se não encontrou ou o
índice do primeiro elemento igual a x encontrado.
Observe que na expressão booleana i < n and a[i] != x acima, a segunda comparação não é efetuada se a primeira
comparação for falsa. Ou seja, embora o operador and seja comutativo a expressão a[i] != x and i < n está
errada.
Fica como exercício, reescrever a função busca de outras formas. Usando o próprio comando for, usando o comando
while, modificando a comparação, etc.
Quando existem elementos que podem ocorrer com maior frequência, o melhor que podemos fazer para melhorar o
desempenho desse algoritmo é colocá-los no início da tabela, pois serão encontrados com menos comparações. No caso
geral, é razoável supor que será mesmo (n + 1) / 2, pois não temos a informação sobre a frequência com que cada elemento
será procurado.
No entanto, existe um algoritmo muito melhor quando a tabela está ordenada. Lembre-se de como fazemos para procurar
uma palavra no dicionário. Não vamos verificando folha a folha (busca sequencial) até encontrar a palavra procurada. Ao
contrário, abrimos o dicionário mais ou menos no meio e a partir daí só há 3 possibilidades:
a) Encontramos a palavra procurada na página aberta (ou concluímos que ela não está no dicionário, pois se estivesse
estaria nesta página).
b) A palavra está na parte esquerda.
c) A palavra está na parte direita.
Se não ocorreu o caso a), repetimos o mesmo processo com as páginas remanescentes onde a palavra tem chance de ser
encontrada, isto é, continuamos a busca com um número bem menor de páginas.
O programa abaixo exemplifica o uso da função busca_binaria. Para melhorar a visibilidade do funcionamento da
função, colocamos um comando print interno a ela (veja abaixo) que a cada repetição mostra os novos valores das
variáveis inicio, fim e meio. O programa que testa a função, gera uma tabela de valores em ordem crescente e em
seguida faz algumas buscas nesta tabela.
def geravet(n):
''' Gera vetor de n elementos com valores crescentes'''
k = randrange(10)
vet = []
for i in range(n):
vet.append(k)
k = k + randrange(10) + 1
# retorna
return vet
Veja a saída:
[7, 17, 20, 24, 34, 35, 41, 46, 50, 53, 63, 65, 67, 71, 76, 86, 90, 99, 104, 109,
117, 124, 131, 141, 151, 158, 164, 169, 177, 182, 190, 191, 193, 199, 208, 214,
223, 231, 238, 247, 257, 266, 270, 280, 281, 289, 291, 293, 300, 308, 316, 317,
324, 330, 336, 339, 347, 348, 350, 355, 358, 365, 371, 377, 387, 391, 396, 401,
410, 416, 418, 424, 428, 430, 438, 446, 452, 458, 465, 473, 474, 481, 491, 496,
501, 511, 512, 515, 517, 524, 532, 533, 539, 542, 545, 547, 551, 561, 571, 580]
Exercícios:
1) Considere a seguinte tabela ordenada, onde vamos procurar elementos usando a busca binária:
a) Diga quantas comparações são necessárias para procurar cada um dos 7 elementos da tabela?
b) Diga quantas comparações serão necessárias para procurar os seguintes números que não estão na tabela 12, 3,
1, 28
a) Diga quantas comparações serão necessárias para procurar cada um dos 11 elementos da tabela?
b) Diga quantas comparações serão necessárias para procurar os seguintes números que não estão na tabela 8, 43,
62, 1?
Note que a cada iteração a tabela fica dividida aproximadamente ao meio, dependendo se n é par ou ímpar.
Assim, numa aproximação, o tamanho da tabela é: n, n/2, n/4, n/8,...
A busca terminará quando o tamanho da tabela chegar a zero, ou seja, o denominador for maior que n.
Portanto o maior k tal que 2k ≤ n < 2k+1.
Assim, k ≤ log2n < k+1.
Ou, -log2n ≤ -k < -log2n + 1
Ou ainda, log2n ≥ k > log2n – 1
E, log2n - 1 < k ≤ log2n
Dizemos que esse algoritmo é O(log2n), ou ainda O(log n). A notação O(f(n)) denota que o tempo que o algoritmo
gasta é proporcional a f(n).
É um resultado surpreendente. Suponha uma tabela de 1.000.000 de elementos. O número máximo de comparações será
log2 (1.000.000) = 20. Compare com a busca seqüencial, onde o máximo de comparações seria 1.000.000 e o
médio seria 500.000. Veja abaixo alguns valores típicos para tabelas grandes.
n log2 n
100 7
1.000 10
10.000 13
100.000 17
1.000.000 20
10.000.000 23
100.000.000 27
1.000.000.000 30
Será que o número médio é muito diferente da média entre o máximo e o mínimo?
Vamos calculá-lo, supondo que sempre encontramos o elemento procurado. Note que quando não encontramos o elemento
procurado, o número de comparações é igual ao máximo. Assim, no caso geral, a média estará entre o máximo e o número
médio no caso em que sempre vamos encontrar o elemento.
Supondo que temos N elementos e que a probabilidade de procurar cada um é sempre 1/N.
Vamos considerar sem perda de generalidade N = 2k - 1. Caso N não seja dessa forma, considere o N’ o menor número
maior que N.
Como fazemos 1 comparação na tabela de N elementos, 2 comparações em 2 tabelas de N/2 elementos, 3 comparações em 4
tabelas de N/4 elementos, 4 comparações em 8 tabelas de N/8 elementos, e assim por diante.
Portanto outros algoritmos são necessários e que têm que aliar a eficiência da busca binária com inserções e remoções. Não
serão estudados neste curso, mas apenas como comentário, são algoritmos de hash (que dividem a tabela em sub-tabelas
lógicas) ou que usam estrutura de dados ligada (listas ligadas e árvores).
Classificação de tabelas
A linguagem Python já possui uma função intrínseca que classifica uma tabela ou lista (sort).
Supondo:
Outros exemplos:
lst.sort(reverse = True) – devolve a lista lst classificada em ordem decrescente. O valor default do parâmetro
reverse é False.
lst.sort(key = func) – a função func define qual a chave de classificação. Veja o exemplo abaixo:
def func(elem):
return elem[1] # a chave é o segundo item do elemento
Devolve:
Como no caso da busca, estamos interessados em estudar como os algoritmos de classificação funcionam.
O algoritmo deve funcionar para tabelas de qualquer tamanho. Em particular, para tabelas muito grandes. Assim é
conveniente que não usemos tabelas auxiliares e sim que a classificação seja feita pela permutação de posição dos
elementos.
6 2 2 2
8 8 4 4
4 4 8 5
2 6 6 6
def Selecao(a):
n = len(a)
# i = 0, 1, 2, ..., n - 2
for i in range(n - 1):
# determina o índice do menor elemento a partir de i
imin = i
for j in range(i + 1, n):
if (a[imin] > a[j]): imin = j
# troca a posição do menor com a posição de i-ésimo
a[i], a[imin] = a[imin], a[i]
O programa abaixo exemplifica o uso da função Selecao. Gera uma tabela com números randômicos e classifica essa
tabela.
def Selecao(a):
n = len(a)
# i = 0, 1, 2, ..., n - 2
for i in range(n - 1):
# determina o índice do menor elemento a partir de i
imin = i
for j in range(i + 1, n):
if a[imin] > a[j]: imin = j
# troca a posição do menor com a posição de i-ésimo
a[i], a[imin] = a[imin], a[i]
def geratab(n):
# gera tabela com n numeros randômicos entre 0 999
tab = []
for i in range(n):
tab.append(randrange(1000))
return tab
Veja a saída:
tabela original:
[935, 815, 134, 931, 923, 797, 342, 398, 838, 248, 204, 2, 352, 669, 910, 398, 615, 734,
541, 565, 499, 386, 981, 282, 868, 121, 648, 612, 382, 241, 292, 379, 827, 221, 639, 250,
438, 891, 743, 924, 422, 986, 509, 409, 887, 845, 596, 692, 365, 988, 858, 591, 77, 333,
961, 936, 538, 185, 189, 57, 419, 967, 871, 589, 756, 140, 934, 359, 922, 721, 326, 715,
695, 261, 489, 600, 646, 791, 890, 791, 697, 153, 536, 441, 909, 521, 104, 417, 703, 254,
693, 17, 406, 749, 937, 72, 43, 20, 723, 308]
O número de comparações (a[imin] > a[j])é sempre (n-1) + (n-2) + ... + 2 + 1 = n.(n-1)/2.
Esse número é exatamente a quantidade de repetições efetuadas. Portanto o tempo que esse algoritmo leva é proporcional a
esse número, ou seja, o tempo é proporcional a n2.
6 6 4 4 4 2 2 2
8 4 6 6 2 4 4 4
4 8 8 2 6 6 6 5
2 2 2 8 8 8 5 6
5 5 5 5 5 5 8 8
Observe como cada elemento sobe até encontrar o seu lugar. Como uma bolha de ar num recipiente de água. Só que para
quando encontrar o seu lugar, ou seja, encontrou um elemento menor ou igual ou chegou ao início da tabela.
def Bolha(a):
n = len(a)
# i = 1, 2, ..., n - 1
for i in range(1, n):
# sobe com a[i] até encontrar o lugar adequado
j = i
while j > 0 and a[j] < a[j - 1]:
# troca com o seu vizinho
a[j], a[j - 1] = a[j - 1], a[j]
# continua subindo
j = j - 1
O programa abaixo exemplifica o uso da função Bolha. Gera uma tabela com números randômicos e classifica essa tabela.
def Bolha(a):
n = len(a)
# i = 1, 2, ..., n - 1
for i in range(1, n):
# sobe com a[i] até encontrar o lugar adequado
j = i
while j > 0 and a[j] < a[j - 1]:
# troca com o seu vizinho
a[j], a[j - 1] = a[j - 1], a[j]
def geratab(n):
# gera tabela com n numeros randômicos entre 0 999
tab = []
for i in range(n):
tab.append(randrange(1000))
return tab
tabela original:
[277, 99, 95, 476, 315, 887, 699, 522, 694, 40, 656, 794, 211, 33, 426, 6, 136, 22, 750,
715, 85, 95, 17, 325, 207, 278, 194, 455, 34, 861, 910, 200, 22, 769, 416, 648, 148, 914,
544, 251, 380, 750, 617, 443, 163, 957, 839, 221, 745, 227, 801, 265, 959, 253, 731, 809,
742, 969, 470, 917, 791, 750, 356, 111, 495, 119, 857, 621, 639, 323, 163, 766, 657, 722,
595, 732, 14, 672, 812, 535, 539, 555, 499, 133, 500, 236, 555, 980, 328, 224, 441, 618,
750, 694, 145, 627, 939, 795, 28, 710]
tabela classificada:
[6, 14, 17, 22, 22, 28, 33, 34, 40, 85, 95, 95, 99, 111, 119, 133, 136, 145, 148, 163, 163,
194, 200, 207, 211, 221, 224, 227, 236, 251, 253, 265, 277, 278, 315, 323, 325, 328, 356,
380, 416, 426, 441, 443, 455, 470, 476, 495, 499, 500, 522, 535, 539, 544, 555, 555, 595,
617, 618, 621, 627, 639, 648, 656, 657, 672, 694, 694, 699, 710, 715, 722, 731, 732, 742,
745, 750, 750, 750, 750, 766, 769, 791, 794, 795, 801, 809, 812, 839, 857, 861, 887, 910,
914, 917, 939, 957, 959, 969, 980]
Exercícios
1) Encontre algumas que precisem exatamente de 5 trocas para classificá-la pelo método da bolha.
2) Idem para 7 trocas
3) Qual a sequência que possui o número máximo de trocas e quantas trocas são necessárias?
Quantas vezes o comando de troca ( a[j], a[j - 1] = a[j - 1], a[j] ) é executado?
O pior caso ocorre quando a sequência está invertida e os elementos terão que subir sempre até a primeira posição.
Nesse caso, a troca é executada i vezes para cada valor de i = 1, 2, 3, ..., n-1. Portanto 1 + 2 + 3 + ... + n – 1 = n.(n – 1)/2.
Portanto , no pior caso o método da bolha é proporcional a n2. Usando a notação de análise de algoritmos, dizemos que este
método é O(n2).
Vamos calcular.
Inversões
O algoritmo se resume a:
6 8 4 2 5 – elimine as inversões do 8 – 0
6 8 4 2 5 – elimine as inversões do 4 – 2
4 6 8 2 5 – elimine as inversões do 2 – 3
2 4 6 8 5 – elimine as inversões do 5 – 2
2 4 5 6 8
Para calcularmos então o número de trocas do método da bolha, basta calcular o número de inversões da sequência.
É equivalente a calcular o número de inversões de uma permutação de 1 2 ... n.