Qual è la complessità temporale di questo algoritmo? E perché?


8

Sono bloccato analizzando la complessità temporale del seguente algoritmo:

def fun (r, k, d, p):
    if d > p:
        return r
    if d = 0 and p = 0:
        r <- r + k
        return r
    if d > 0:
        fun (r, k + 1, d - 1, p)
    if p > 0:
        fun (r, k - 1, d, p - 1)

La chiamata root sarà fun (0, 0, n, n)ed nè la dimensione del problema.

Immagino che: la relazione di ricorrenza sia , che equivale a , e quindi .T(n,n)=T(n−1,n)+T(n,n−1)T(2n)=2T(2n−1)⟺T(m)=2T(m−1)O(2m)⟺O(4n)

La mia analisi è corretta (so che non è molto completa ed esatta)? Se presenta un difetto grave, segnalalo o mostrami una prova corretta e completa sulla complessità temporale di questo algoritmo.


1
xando, ti incoraggio a modificare la domanda per spiegare perché le tecniche standard non funzionano: spiega perché falliscono. (Cc: @YuvalFilmus) Ad esempio, ricevi una relazione di ricorrenza che è difficile da risolvere e, in tal caso, quale ricorrenza ottieni?
— DW

1
Nei commenti con Polyergic, mi sono reso conto che lo pseudocodice non è chiaro: non è chiaro cosa intendi per l'algoritmo, quando entrambi d>0e p>0. Non mostri cosa restituisce la funzione se raggiungiamo le istruzioni if ​​3 e 4th. Intendevi avere una returndichiarazione dopo ogni invocazione ricorsiva di fun? (intendevi fun (r, k + 1, d - 1, p)essere return fun (r, k + 1, d - 1, p)?) O intendevi avere returnun'affermazione alla fine del corpo della funzione? Modifica il tuo pseudocodice per chiarire e assicurarti di mostrare ciò che restituisce in tutti i casi possibili.
— DW

Per dirlo in un altro modo: supponiamo d<=pe d>0e p>0tutto tenga. Cosa dovrebbe succedere? L'algoritmo esegue 2 invocazioni ricorsive alla funzione? O invoca ricorsivamente fun(r, k + 1, d - 1, p)e poi immediatamente ritorna, senza invocare ricorsivamente fun(r, k - 1, d, p - 1)? Se prendo il tuo pseudocodice alla lettera, sembra che compia 2 invocazioni ricorsive e poi ritorni con un valore di ritorno indefinito, ma questo sembra strano e mi chiedo se ci sia un errore di battitura / bug nello pseudocodice.
— DW

Risposte:


10

Gli unici due argomenti rilevanti per l'analisi asintotica sono d e p. Questi argomenti (virtualmente) soddisfanod,p≥0 e d≤p(abbiamo bisogno di mescolare leggermente la logica nella funzione per ottenere questo). Ad ogni punto dell'esecuzione, prendi la coppia corrente(d,p) e quindi ricorsivamente chiama la funzione con le coppie (d−1,p),(d,p−1), evitando coppie che invalidano i vincoli sopra indicati.

Possiamo immaginare l'albero delle chiamate risultante come percorso che inizia a (0,0). Ogni volta che diminuiscip, aggiungi a / step. Ogni volta che diminuiscid, aggiungi un \ step. La condizioned≤pgarantisce che non si scende mai sotto l'asse X. Inoltre, hai un "budget" dindi ogni passaggio. Il numero totale di foglie in questo albero delle chiamate è esattamente il numero catalano , e questo ci dà un limite inferiore sul tempo di esecuzione della funzione.(2nn)/(n+1)=Θ(4n/n3/2)

Per ottenere un limite superiore, nota che sulla strada per ogni foglia passiamo attraverso nodi, e questo dà un limite superiore di più grande del limite inferiore, cioè, .2n2nΘ(4n/n)

Abbiamo un limite inferiore di e un limite superiore su . Quali sono gli esotici asintotici? Crescono come il numero totale di percorsi che non attraversano l'asse X che hanno al massimo passi in ciascuna direzione. Usando il teorema del voto di Bertrand possiamo ottenere un'espressione esatta per questo: Resta quindi da stimare asintoticamente questa somma: Ω(4n/n3/2)O(4n/n)n

∑0≤d≤p≤np−d+1p+1(p+dp).
∑0≤d≤p≤n(p+dp)−∑0≤d≤p≤ndp+1(p+dd)=∑0≤d≤p≤n(p+dp)−∑0≤d≤p≤n(p+dp+1)=∑p=0n(2p+1p+1)−∑p=0n(2p+1p+2)=∑p=0n1p+1(2p+2p)=Θ(∑p=0n4pp3/2)=Θ(4nn3/2).

1
Mi piace molto il tuo approccio geometrico usando questi "passaggi". È una tecnica comune? Non l'ho mai visto prima
— Andreas T,

@AndreasT Non la definirei una tecnica comune, anzi normalmente non si applica. Qui l'interpretazione combinatoria è piuttosto evidente e conduce a questo tipo di soluzione.
— Yuval Filmus,

0

Caso per caso:

  1. d> p : tempo costante
  2. d = 0 ∧ p = 0 : tempo costante
  3. d> 0 : Nota che d ≯ p, quindi abbiamo 0 <d ≤ p, e funricorre su d-1 fino a d ≯ 0; poiché p> 0, questo è lineare in d + (caso 4) .
  4. p> 0 : Nota che d ≯ 0, quindi abbiamo d ≤ 0 ≤ p (con d <p), e funricorre su p-1 fino a p ≯ 0; questo è lineare in p + (uno dei casi 1, 2 o 5)
  5. d ≤ p <0 : non definito; Suppongo che questo sia tempo costante

A partire da d = p = n> 0 indica il caso 3, seguito dal caso 4. Se n è un numero intero, il caso finale è 2, altrimenti il ​​caso finale è 5. Il tempo totale per tali casi è d + p +1 o 2n + 1.


2n. Immagino che tu abbia votato verso il basso perché mi sono concentrato sul ragionamento?
— ShadSterling

1
Grazie per aver modificato la risposta! Il puzzle ora è che hai concluso che il tempo di esecuzione è , ma Yuval ha concluso che il tempo di esecuzione è esponenziale in . Questa è una differenza abbastanza sostanziale. O(n)n
— DW

1
Hmm, penso di aver preso lo pseudocodice ifcome "fai uno di questi" mentre @Yuval li ha considerati "considera ognuno di questi in ordine". Quest'ultimo è, ovviamente, ciò che ifs (senza else) significa nel codice reale; Sono abituato al primo in quasi tutti gli altri contesti rispetto al codice reale (incluso in pseudocodice, sebbene l'utilizzo in pseudocodice sia incoerente).
— ShadSterling

Capisco cosa intendi. La mancanza di returndichiarazioni nella seconda metà del codice rende questo piuttosto confuso.
— DW
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.