Funkcja Ackermanna – funkcja matematyczna odkryta przez Wilhelma Ackermanna w 1928 roku. Cechą charakterystyczną tej dwuargumentowej funkcji jest jej nadzwyczaj szybki wzrost. Funkcja Ackermanna jest prostym przykładem funkcji rekurencyjnej, niebędącej funkcją pierwotnie rekurencyjną. Funkcje pierwotnie rekurencyjne to większość znanych funkcji, między innymi dodawanie, funkcja wykładnicza itp.

Funkcja Ackermanna często jest używana do testowania jakości optymalizacji kompilatorów – stosunki czasu wykonania pomiędzy różnymi kompilatorami tego samego języka czasami sięgają tysiąca. Jakość kodu generowana dla tego typu funkcji jest szczególnie ważna w językach funkcyjnych, gdzie używa się rekursji.

Inną funkcją o własnościach podobnych do funkcji Ackermanna (tzn. będąca rekurencyjną i nie pierwotnie rekurencyjną) jest funkcja Sudana.

Definicja

edytuj

Matematyczna definicja funkcji opisana jest wzorem

Własności

edytuj

Funkcja Ackermanna jest rekurencyjna.

Schemat dowodu twierdzenia funkcja Ackermanna nie jest pierwotnie rekurencyjna: definiuje się rodzinę funkcji Ackermanna (każda z tych funkcji jest pierwotnie rekurencyjna). Z tego wynika, że każda funkcja pierwotnie rekurencyjna jest majoryzowana przez pewną funkcję Ackermanna. Następnie dowodzi się, że wszystkie jednoargumentowe funkcje Ackermanna będą z kolei majoryzowane przez funkcję Wynika z tego, że nie jest pierwotnie rekurencyjna.

Dowód przez indukcję:

  • Niech m = 0. Wtedy:

zgodnie z definicją dla funkcji posiadającej 0 jako pierwszy argument. z kolei jest zawsze większe od 0, ponieważ już w przypadku n = 0, n + 1 = 0 + 1 = 1.

  • (Hipoteza 1) Zakładając, że obowiązuje:

pokazujemy poprzez indukcję na argumencie n, że

  • W tym celu niech n = 0. Zgodnie z hipotezą 1 obowiązuje

i poprzez to

wedle definicji funkcji dla argumentu n = 0.

  • (Hipoteza 2) Zakładając, że obowiązuje:

pokazujemy, że

  • Ponieważ wedle hipotezy 2 zachodzi: musi obowiązywać:

a następnie zgodnie z hipotezą 1:

oraz wedle definicji funkcji dla argumentów różnych od 0:

Łącząc te trzy relacje otrzymujemy dowód twierdzenia:

Tabela wartości

edytuj
m\n 0 1 2 3 4 n
0 1 2 3 4 5
1 2 3 4 5 6
2 3 5 7 9 11
3 5 13 29 61 125
4 13 65533 265536 – 3 (3, 265536 – 3) (3, (4, 3))
5 65533 (4, 65533) (4, (5, 1)) (4, (5, 2)) (4, (5, 3))
6 (5, 1) (5, (5, 1)) (5, (6, 1)) (5, (6, 2)) (5, (6, 3))

Wartość (4,2) jest dużo większa niż liczba cząsteczek w obserwowalnym wszechświecie podniesiona do dwusetnej potęgi.

Przykłady

edytuj

Pseudokod (oraz kod w języku Python) algorytmu obliczającego funkcję Ackermanna:

def Ack(m, n):
   if m==0: return n+1
   if m>0 and n==0: return Ack(m-1, 1)
   if m>0 and n>0: return Ack(m-1, Ack(m, n-1))

Kod metody obliczającej funkcję w przykładowym języku funkcyjnym, np. Erlangu:

ack(0,N) -> N+1;
  ack(M,0) -> ack(M-1, 1);
  ack(M,N) -> ack(M-1, ack(M, N-1)).

Trudność przy obliczeniach funkcji Ackermanna leży głównie w złożoności wywołań rekurencyjnych, które łatwo mogą doprowadzić do przepełnienia stosu (ang. stack overflow). Podczas wykonywania obliczenia dochodzi wielokrotnie do wywołań funkcji z identycznymi argumentami. W poniższym przykładzie między innymi A(1, 1), A(1, 0), A(0, 2) zostają wywołane dwukrotnie. Odpowiednia implementacja funkcji może wykorzystać powtarzające się instrukcje do znacznego skrócenia procesu obliczania. Przykładowo wartość A(2, 0) jest równa 3, co jest widoczne w wierszu 6. Podobnie wartość A(1, 2) wynosi 4, co daje się odczytać w przedostatnim wierszu. Oprócz tego wartość funkcji wywołanej z argumentami (2, 1) odpowiada wartości funkcji wywołanej z argumentami (1, 3) lub (0, 4). Różnica polega na tym, że ostatnia kombinacja argumentów może zostać obliczona w czasie krótszym (1 wywołanie funkcji) niż druga (8 wywołań funkcji) lub pierwsza (14 wywołań).

A(2, 1) = A(1, A(2, 0))
        = A(1, A(1, 1))
        = A(1, A(0, A(1, 0)))
        = A(1, A(0, A(0, 1)))
        = A(1, A(0, 2))
        = A(1, 3)
        = A(0, A(1, 2))
        = A(0, A(0, A(1, 1)))
        = A(0, A(0, A(0, A(1, 0))))
        = A(0, A(0, A(0, A(0, 1))))
        = A(0, A(0, A(0, 2)))
        = A(0, A(0, 3))
        = A(0, 4)
        = 5

Języki programowania i kompilatory często stosują tzw. optymalizację wywołań ogonowych, które ułatwiają, przyśpieszają obliczenia funkcji rekurencyjnych podobnych do funkcji Ackermana, i używają znacznie mniej pamięci na stosie.

Inną metodą stosowaną przez pewne kompilatory i języki programowania, jest tzw. spamiętywanie (tablicowanie, memoizacja, forma pamięci podręcznej) wartości funkcji – raz obliczona wartość funkcji A(m, n), jest zapamiętywana w specjalnej tablicy, którą można oznaczyć na przykład przez A[m, n]; przy następnych wywołaniach zamiast wykonywać całość obliczeń z nią związanych odczytuje się wcześniej obliczony wynik bezpośrednio z tablicy w jednym kroku. Przykład w języku Python:

Ack_tab = {}
def Ack(m, n):
   if (m, n) in Ack_tab:
      return Ack_tab[(m, n)]
   else:
     if m==0: r = n + 1
     if m>0 and n==0: r = Ack(m-1, 1)
     if m>0 and n>0: r = Ack(m-1, Ack(m, n-1))
     Ack_tab[(m, n)] = r
     return r

albo:

def ack(m, n, cache={}):
    if (m, n) in cache:
        return cache[(m, n)]
    elif m==0:
        cache[(m, n)] = n + 1
    elif m>0 and n==0:
        cache[(m,n)] = ack(m-1, 1, cache)
    elif m>0 and n>0:
        cache[(m, n)] = ack(m-1, ack(m, n-1, cache), cache)
    return cache[(m,n)]

W porównaniu do poprzedniego przykładu (14 wywołań), ta wersja wykona 10 wywołań rekurencyjnych, przy czym jedno z nich – A(1,1) zostanie spamiętane i wykorzystane w 11 wywołaniu funkcji do bezpośredniego zwrócenia wyniku. Przy większych n oraz m zysk jest również większy, dla przykładu A(3,3) wymaga 2432 wywołań, a wersja ze spamiętywaniem 186 wywołań, zapamiętując w tablicy 154 różne pary (m, n) wraz z odpowiadającymi im wartościami.

Odwrotna funkcja Ackermanna

edytuj

Jeśli oznaczymy to możemy rozpatrywać również jej odwrotność Jest to funkcja niesłychanie wolno rosnąca (asymptotycznie wolniej niż logarytm, a nawet logarytm iterowany). We wszystkich wyobrażalnych praktycznych zastosowaniach można uznać tę funkcję za stałą, nie większą niż 5, ponieważ wynosi około Używając funkcji można wyznaczyć, że tempo wzrostu w szybko rosnącej hierarchii można zapisać jako:

Definiuje się również dwuargumentową odwrotność funkcji Ackermanna:

Pojawia się ona czasami przy badaniu złożoności obliczeniowej (np. algorytm Union-Find).

Linki zewnętrzne

edytuj
  • Eric W. Weisstein, Ackermann Function, [w:] MathWorld, Wolfram Research [dostęp 2020-12-12] (ang.).
  • publikacja w otwartym dostępie – możesz ją przeczytać Ackermann function (ang.), Encyclopedia of Mathematics, encyclopediaofmath.org [dostęp 2024-10-05].

📚 Artikel Terkait di Wikipedia

Microsoft Azure

2017-09-25]  (pol.). What is the Azure Guest operating system? [online], Stack Overflow [dostęp 2021-04-03] . Cesar de la Torre – BLOG : Microsoft Azure

Oracle Database

Dictionary Cache – bufor słowników bazy danych Database Buffer Cache – obszar bufora danych zorganizowany w listy LRU Redo Log Buffer Cache – obszar bufora

Beyond.pl

Cloud Provider Program. W maju 2018 Beyond.pl uruchomił Microsoft Azure Stack na platformie produkcyjnej w Data Center 2. Jest to pierwsza tego typu komercyjna

Mikroprocesor

Q9550. Core i7, Bloomfield, Nehalem – technologia 45 nm, 256 KB L2 cache, 8 MB L3 cache, 780 milionów tranzystorów. AMD Phenom II w technologii 45 nm 2010

Spis formatów plików

files; AWAVE C86 C source code file; Computer Innovation C86 CA Initial cache data for root domain servers; Telnet CA? Borland packed and splitted file;