n log n = c. Quais são algumas boas aproximações disso?

7

Atualmente, estou analisando a notação Big O e a complexidade computacional.

O problema 1.1 do CLRS faz uma pergunta básica, que é obter uma intuição sobre como as complexidades algorítmicas diferentes crescem com o tamanho da entrada.

A pergunta pergunta:

Para cada função e tempo na tabela a seguir, determine o maior tamanho de um problema que pode ser resolvido no tempo , assumindo que o algoritmo para resolver o problema leve microssegundos.f(n)tntf(n)

Os períodos são de 1 segundo, 1 minuto, 1 hora, 1 dia, 1 mês, 1 ano, 1 século.

As funções são complexidades de tempo aparentemente comuns que surgem frequentemente em algoritmos, sendo a lista:f(n)

log2n,n,n,nlog2n,n2,n3,2nandn!

A maioria são manipulações algébricas bastante diretas. Estou lutando com dois deles, e ambos pelo mesmo motivo:

Se é o tempo em microssegundos, os dois com os quais estou lutando são c

nlog2n=c
n!2πn(ne)n=c

ParaPensei em usar a aproximação de Stirling.n!

Ambos exigem a capacidade de resolver , com Stirling requer um pouco mais de manipulação.nlog2n=c

Questões

  1. Como não é solucionável usando funções elementares (somente Lambert W ), quais são algumas boas maneiras de aproximar ? Ou como implementamos Lambert W?nlog2nnlog2n
  2. Como resolvemos n! = c, necessariamente aproximadamente à medida que n cresce. Stirling é o caminho certo a seguir e, em caso afirmativo, como resolver2πn(ne)n=c

Aqui está um código python que eu montei para completar a tabela com minha saída atual:

EDIT: Com base em algumas respostas, usei um método de pesquisa binária (exceto lg n). Editei o código abaixo para refletir isso:

+---------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
| f(n)    |    1 sec    |    1 min    |    1 Hour   |    1 Day    |   1 Month   |    1 Year   |  1 Century  |
+---------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
| lg n    | 2^(1.0E+06) | 2^(6.0E+07) | 2^(3.6E+09) | 2^(8.6E+10) | 2^(2.6E+12) | 2^(3.2E+13) | 2^(3.2E+15) |
| sqrt(n) |   1.0E+12   |   3.6E+15   |   1.3E+19   |   7.5E+21   |   6.7E+24   |   9.9E+26   |   9.9E+30   |
| n       |   1.0E+06   |   6.0E+07   |   3.6E+09   |   8.6E+10   |   2.6E+12   |   3.2E+13   |   3.2E+15   |
| n log n |    62746    |   2.8E+06   |   1.3E+08   |   2.8E+09   |   7.2E+10   |   8.0E+11   |   6.9E+13   |
| n^2     |     1000    |     7745    |    60000    |    293938   |   1.6E+06   |   5.6E+06   |   5.6E+07   |
| n^3     |     100     |     391     |     1532    |     4420    |    13736    |    31593    |    146645   |
| 2^n     |      19     |      25     |      31     |      36     |      41     |      44     |      51     |
| n!      |      9      |      11     |      12     |      13     |      15     |      16     |      17     |
+---------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+

Código Python:

import math
import decimal
from prettytable import PrettyTable

def binary_search_guess(f, t, last=1000):
    for i in range(0, last):
        guess = pow(2,i)
        if f(guess) > t:
            return binary_search_function(f, pow(2,i-1), guess, t)

    return -1 

def binary_search_function(f, first, last, target):
    found = False

    while first<=last and not found:
        midpoint = (first + last)//2
            if f(midpoint) <= target and f(midpoint+1) > target:
                found = True
            else:
                if target < f(midpoint):
                    last = midpoint-1
                else:
                    first = midpoint+1
    best_guess = midpoint

    return best_guess

def int_or_sci(x):
    if x >= math.pow(10,6):
        x = '%.1E' % decimal.Decimal(x)
    else:
        x = int(x)

    return x

def input_size_calc():
    #Create Pretty Table Header
    tbl = PrettyTable(["f(n)", "1 sec", "1 min", "1 Hour", "1 Day", "1 Month", "1 Year", "1 Century"])
    tbl.align["f(n)"] = "l" # Left align city names
    tbl.padding_width = 1 # One space between column edges and contents (default)

    #Each Time Interval in Microseconds
    tsec = pow(10,6)
    tmin = 60 * tsec
    thour = 3600 * tsec
    tday = 86400 * tsec
    tmonth = 30 * tday
    tyear = 365 * tday
    tcentury = 100 * tyear

    tlist = [tsec,tmin,thour,tday,tmonth,tyear,tcentury]
    #print tlist

    #Add rows   
    #lg n
    f = lambda x : math.log(x,2)
    fn_list = []
    for t in tlist:
        #This would take too long for binary search method
        ans = int_or_sci(t)
        fn_list.append("2^(%s)" % ans)
    tbl.add_row(["lg n",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #sqrt(n)
    f = lambda x : math.pow(x,1/2.0)
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["sqrt(n)",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #n
    f = lambda x : x
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["n",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #n log n
    f = lambda x : x * math.log(x,2)
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["n log n",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #n^2
    f = lambda x : math.pow(x,2)
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["n^2",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #n^3
    f = lambda x : math.pow(x,3)
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["n^3",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #2^n
    f = lambda x : math.pow(2,x)
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["2^n",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    #n!
    f = lambda x : math.factorial(x)
    fn_list = []
    for t in tlist:
        fn_list.append(int_or_sci(binary_search_guess(f, t)))
    tbl.add_row(["n!",fn_list[0], fn_list[1], fn_list[2], fn_list[3], fn_list[4], fn_list[5], fn_list[6]])

    print tbl

#PROGRAM BEGIN
input_size_calc()
stats_novice_123
fonte
3
Você pode aproximar Wn simplesmente fazendo pesquisa binária no valor de nou usando uma série de Taylor . Você pode estender a função fatorial para ser contínua, para a função gama e provavelmente pode encontrar algumas informações sobre sua inversa - mas sua abordagem usando a aproximação de Stirling também parece bem.
Tom van der Zanden
11
confira este pdf: cs-people.bu.edu/lapets/resource/nlogn.pdf
Sagnik
11
@TomvanderZanden Forget - você pode aproximar toda a questão fazendo uma pesquisa binária! Wn
precisa saber é o seguinte
Cuidado com frações ... usar três dígitos significativos é razoável quando n for grande, mas para n pequeno, certifique-se de arredondar para o número inteiro mais próximo.
Ben Voigt
@DavidRicherby Você pode expandir isso, por favor?
stats_novice_123

Respostas:

8

O inverso aproximado de é . De fato, para esse valor de , temos Essa aproximação geralmente é boa o suficiente.c=nlognn=c/logcn

nlogn=clogclogclogc=clogc(logcloglogc)=c(1loglogclogc).
Yuval Filmus
fonte
1

Você não precisa aproximar nada para resolver o exercício. Todas as funções fornecidas são monótonas, para que você possa usar a pesquisa binária. Ou seja, para resolver para  , basta adivinhar até encontrar o primeiro  que . Em seguida, faça uma pesquisa binária comum entre e  . Se a solução for  , são necessárias aproximadamente avaliações de .f(n)=cnn=1,2,4,8,k2klog2k>c2k12kx2logxf

David Richerby
fonte
Uma pesquisa binária em lg n não levaria um tempo inviável?
stats_novice_123
Eu editei meu código na minha tabela de perguntas e respostas para refletir essa abordagem. Parece impraticável para lg n, no entanto, você concorda?
stats_novice_123
Não, é bom resolver . Depois de dobrar o vezes que você , ultrapassou e fez uma pesquisa binária entre e . Essa gama é de cerca de de largura, por isso leva cerca de iterações de busca binária para encontrar o valor correto. logn=cc2c2c2cc
precisa saber é o seguinte
Mas apenas por 1 segundo, c = 10 ^ 6? 1 ano c = 3,2 x 10 ^ 13? como f (n) é em microssegundos de acordo com a pergunta
stats_novice_123
11
OK, entendo o seu ponto. Mas, ainda assim, você precisaria apenas de iterações da pesquisa e, com uma biblioteca de bignum eficiente, as iterações não demorariam muito. (E, sim, como você diz, é assim mesmo trivialmente invertida para que possa side-step a questão.)2clog
David Richerby