n log n = c. Was sind einige gute Annäherungen daran?

7

Ich beschäftige mich derzeit mit der Big O-Notation und der Komplexität der Berechnungen.

Problem 1.1 in CLRS stellt die Frage, was eine grundlegende Frage zu sein scheint, nämlich eine Vorstellung davon zu bekommen, wie unterschiedliche algorithmische Komplexitäten mit der Größe der Eingabe wachsen.

Die Frage lautet:

Bestimmen Sie für jede Funktion und Zeit in der folgenden Tabelle die größte Größe eines Problems, die in der Zeit gelöst werden kann , unter der Annahme, dass der Algorithmus zur Lösung des Problems Mikrosekunden benötigt.f(n)tntf(n)

Die Zeiträume sind 1 Sekunde, 1 Minute, 1 Stunde, 1 Tag, 1 Monat, 1 Jahr, 1 Jahrhundert.

Die Funktionen sind scheinbar häufige Zeitkomplexitäten, die in Algorithmen häufig auftreten. Die Liste lautet:f(n)

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

Die meisten sind ziemlich einfache algebraische Manipulationen. Ich habe mit zwei davon zu kämpfen, und beide aus demselben Grund:

Wenn die Zeit in Mikrosekunden ist, sind die beiden, mit denen ich zu kämpfen habe, c

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

FürIch dachte daran, Stirlings Approximation zu verwenden.n!

Beide erfordern die Fähigkeit, zu lösen, Stirling etwas mehr Manipulation erfordert.nlog2n=c

Fragen

  1. Da nicht mit Elementarfunktionen lösbar ist (nur Lambert W ), was sind einige gute Möglichkeiten, um zu approximieren ? Oder wie implementieren wir Lambert W?nlog2nnlog2n
  2. Wie lösen wir n! = c, notwendigerweise ungefähr, wenn n groß wird. Ist Stirling der richtige Weg und wenn ja, wie löse ich2πn(ne)n=c

Hier ist ein Python-Code, den ich zusammengestellt habe, um die Tabelle mit meiner aktuellen Ausgabe zu vervollständigen:

BEARBEITEN: Basierend auf einigen Antworten habe ich eine binäre Suchmethode verwendet (außer lg n). Ich habe den folgenden Code bearbeitet, um dies widerzuspiegeln:

+---------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
| 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     |
+---------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+

Python-Code:

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
quelle
3
Sie können approximieren, indem Sie einfach eine binäre Suche nach dem Wert von oder eine Taylor-Reihe verwenden . Sie können die Fakultätsfunktion auf die Gammafunktion erweitern, um stetig zu sein, und Sie können wahrscheinlich einige Informationen zu ihrer Umkehrung finden - aber Ihr Ansatz mit der Stirling-Näherung scheint auch in Ordnung zu sein. Wnn
Tom van der Zanden
1
Schauen
Sagnik
1
@TomvanderZanden Vergiss - du kannst die ganze Frage nur durch eine binäre Suche approximieren! Wn
David Richerby
Seien Sie vorsichtig mit Brüchen ... Die Verwendung von drei signifikanten Stellen ist sinnvoll, wenn n groß ist. Bei kleinen n sollten Sie jedoch auf die nächste Ganzzahl abrunden.
Ben Voigt
@ DavidRicherby Kannst du das bitte erweitern?
stats_novice_123

Antworten:

8

Die ungefähre Umkehrung von ist . In der Tat haben wir für diesen Wert von Diese Annäherung ist normalerweise gut genug.c=nlognn=c/logcn

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

Sie müssen nichts annähern, um die Übung zu lösen. Alle Funktionen, die Sie erhalten, sind monoton, sodass Sie nur die binäre Suche verwenden können. Das heißt, um für  zu lösen , raten Sie einfach bis Sie das erste  , das . Führen Sie dann eine normale binäre Suche zwischen und  . Wenn die Lösung  , werden ungefähr Auswertungen von .f(n)=cnn=1,2,4,8,k2klog2k>c2k12kx2logxf

David Richerby
quelle
Würde eine binäre Suche auf lg n nicht eine unmögliche Zeit in Anspruch nehmen?
stats_novice_123
Ich habe meinen Code in meiner Frage- und Antworttabelle bearbeitet, um diesen Ansatz widerzuspiegeln. Es scheint jedoch für lg n unpraktisch, würden Sie zustimmen?
stats_novice_123
Nein, es ist in Ordnung, um zu lösen . Sobald Sie Zeiten verdoppelt haben, haben Sie überschritten und führen dann eine binäre Suche zwischen und . Dieser Bereich ist etwa breit, so dass es etwa nimmt Iterationen binärer Suche den richtigen Wert zu finden. logn=cc2c2c2cc
David Richerby
Aber nur für 1 Sekunde, c = 10 ^ 6? 1 Jahr c = 3,2 x 10 ^ 13? als f (n) ist in Mikrosekunden gemäß der Frage
stats_novice_123
1
OK, ich verstehe deinen Standpunkt. Trotzdem benötigen Sie nur etwa 2 Iterationen der Suche, und mit einer effizienten Bignum-Bibliothek würden Iterationen nicht zu lange dauern. (Und ja, wie Sie sagen, ist sowieso trivial invertierbar, so dass Sie das Problem 2clog
umgehen