Introdução sobre algoritmos de divisão e conquista¶
Resolvendo recorrências usando expansão de termos¶
Resolvendo recorrências usando árvores de recursão¶
Resolvendo recorrências usando indução matemática (método da substituição)¶
Resolvendo recorrências usando o Teorema Mestre¶
Usado para recorrências do tipo:
O teorema consiste em comparar com a grandeza e o que dominar assintoticamente é a solução da recorrência. A função representa o custo associado às operações que são efetuadas dentro de uma única chamada recursiva, ignorando o custo adicional das outras chamadas recursivas (conquista). Por outro lado, representa o contrário, o custo associado às chamadas recursivas:
se : significa que o custo dominante da recorrência acontece nos filhos das (sub)árvores, isto é, o custo dos níveis da árvore de recursão vai progressivamente aumentando.
se : significa que cada nível contribui igualmente para o custo.
se : significa que o custo dominante da recorrência se concentra nas raízes das (sub)árvores, isto é, o custo dos níveis da árvore de recursão vai progressivamente diminuindo.
Cada uma dessas situações descreve um caso do teorema, que acompanha condições necessárias e também a conclusão a ser tirada sobre a complexidade assintótica da recorrência em cada caso.
Caso 1. Se para , então
Caso 2. Se , então
Caso 3. Se para e atender uma condição de regularização para e a partir de , então
Resolvendo recorrências usando o Akra-Bazzi¶
Se trata de uma generalização do teorema mestre, possibilitando a solução de recorrências do tipo:
Onde: , é uma função assintoticamente positiva. Além disso, seja a única raíz real da equação:
O teorema de Akra-Bazzi conclui um limite assintótico justo para :
Estudo de caso: ordenação por intercalação (merge sort)¶
merge(left, right, a) // |left| = n ; |right| = m ; |a| = n + m
i = 1; j = 1; k = 1
while i <= n and j <= m
if left[i] < right[j]
a[k] = left[i]; i = i + 1
else
a[k] = right[j]; j = j + 1
k = k + 1
while i <= n
a[k] = left[i]; i = i + 1; k = k + 1
while j <= n
a[k] = right[j]; j = j + 1; k = k + 1
merge-sort(a) // |a| = n
if n == 1 or n == 0 return
mid = floor(n / 2)
left = a[1 ... mid]
right = a[mid+1 ... n]
merge-sort(left)
merge-sort(right)
merge(left, right, a)
Estudo de caso: ordenação usando quick sort¶
particiona(a, l, r) // a indexado de l a r, inclusive
pivot = a[r]
i = l - 1
for j = 1 to r-1
if a[j] <= pivot
i = i + 1
swap(a, j, i) // troca elementos de posição
swap(a, i+1, r)
return i+1
Estudo de caso: subarranjo de soma máxima¶
O subarranjo de soma máxima pode estar: (1) na metade esquerda; (2) na metade
direita; (3) cruzando o meio. Observe que, se estamos no caso (3), a restrição
nos ajuda a encontrar o melhor subarranjo: a partir do meio, basta adicionarmos
elementos à esquerda rastreando em que posição a soma fica máxima e o mesmo à
direita. Assim, (1) e (2) são resolvidos diretamente pela recursão durante a
conquista dos subproblemas e (3) é resolvido pelo procedimento
max-sum-crossing-mid
de complexidade linear , onde n é o tamanho do
arranjo.
max-sum-subarray(a, l, r) // a indexado de l a r, inclusive
if l == r return (l, r, a[l])
mid = floor((l + r)/2)
(l1, r1, s1) = max-sum-subarray(a, l, mid)
(l2, r2, s2) = max-sum-subarray(a, mid+1, r)
(l3, r3, s3) = max-sum-crossing-mid(a, l, r, mid)
if s1 > s2 and s1 > s3 return (l1, r1, s1)
else if s2 > s3 return (l2, r2, s2)
else return (l3, r3, s3)
max-sum-crossing-mid(a, l, r, mid) // a indexado de l a r, inclusive
s1 = −inf
s = 0
for i = mid down to l
s = s + a[i]
if s > s1
s1 = s; l1 = i
s2 = −inf
s = 0
for i = mid+1 to r
s = s + a[i]
if s > s2
s2 = s; r1 = i
return (l1 , r1 , s1 + s2 )