Najduži rastući podniz

S Vikipedije, slobodne enciklopedije

Problem pronalaska najdužeg rastućeg podniza (engl. longest increasing subsequence) predstavlja algoritamski problem u kojem je potrebno odrediti najduži podniz ne obavezno uzastopnih elemenata datog niza, koji je rastući. Rešenje problema ne mora biti jedinstveno. Na primer, za niz {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}, dužina najdužeg rastućeg podniza iznosi 6 i jedno od mogućih rešenja je podniz {0, 2, 6, 9, 13, 15}. Podnizovi {0, 4, 6, 9, 11, 15} i {0, 4, 6, 9, 13, 15} su takođe moguća rešenja datog problema. Problem pronalaska najdužeg rastućeg podniza se može rešiti u vremenu O(n log n), gde n označava dužinu ulaznog niza.

Veza sa drugim algoritamskim problemima[uredi | uredi izvor]

Pronalazak najdužeg rastućeg podniza se može svesti na problem pronalaska najdužeg zajedničkog podniza, koji ima kvadratnu složenost. Najduži rastući podniz niza S je zapravo najduži zajednički podniz nizova S i T, gde je T sortiran niz S.

Rešenje zasnovano na dinamičkom programiranju[uredi | uredi izvor]

Postoji pravolinijsko rešenje zasnovano na dinamičkom programiranju, koje, kao i rešenje zasnovano na problemu najdužeg zajedničkog podniza, ima kvadratnu složenost. Neka je sa D[k] označena dužina najdužeg rastućeg podniza niza A, sa ograničenjem da je poslednji element upravo A[k]. S obzirom da se globalno rešenje problema mora završiti u nekom elementu niza A, konačno rešenje problema se može dobiti pronalaskom maksimalne vrednosti u nizu D.

Za računanje elemenata niza D, može se primeniti sledeća rekurzivna formula. Za računanje vrednosti D[k], posmatramo skup svih indeksa Sk, za koje važi i < k i A[i] < A[k]. Ako je skup Sk prazan, tada su svi elementi koji se nalaze pre A[k] u nizu A veći od njega, pa je D[k] = 1. Inače, ako pronađemo maksimalnu dužinu najdužeg rastućeg podniza u okviru skupa Sk, tada samo dodamo element A[k] na kraj ovog niza. Dakle, važi sledeća formula:

Za rekonstrukciju jednog od rešenja, može se iskoristiti pomoćni niz P. Naime, P[k] će predstavljati indeks i, dobijen pri računanju D[k]. Najduži rastući podniz se može rekonstruisati jednim prolaskom kroz niz P od nazad.

Opis efikasnog rešenja[uredi | uredi izvor]

Sledeći algoritam rešava efikasno problem pronalaska najdužeg rastućeg podniza uz upotrebu nizova i binarne pretrage. Algoritam redom obrađuje elemente ulaznog niza i u svakom trenutku računa najduži rastući podniz do tada obrađenih elemenata. Neka su elementi ulaznog niza označeni sa X[0], X[1], itd. Nakon obrade elementa X[i], čuvaju se sledeće vrednosti u dva niza:

M[j] — čuva vrednost indeksa k niza X, takvu da je X[k] najmanji mogući poslednji element svih rastućih podnizova dužine j, gde je ki.
P[k] — čuva indeks prethodnika od X[k] u najdužem rastućem podnizu koji se završava u X[k].

Dodatno, algoritam može čuvati u promenljivoj L dužinu do tada pronađenog najdužeg rastućeg podniza. Primetimo da, u bilo kom trenutku izvršavanja algoritma, niz

X[M[1]], X[M[2]], ..., X[M[L]]

je neopadajući. Ukoliko postoji rastući podniz dužine i koji se završava u X[M[i]], onda postoji i podniz dužine i-1, koji se završava u manjoj vrednosti, tj. u vrednosti X[P[M[i]]]. Za pronalazak te vrednosti možemo iskoristiti binarnu pretragu koja ima logaritamsku složenost.

Pseudokod opisanog rešenja:

 P = niz dužine N
 M = niz dužine N + 1

 L = 0
 for i = 0 to N-1:
   // Binarna pretraga najveceg broja j ≤ L
   // takva da je X[M[j]] < X[i]
   lo = 1
   hi = L
   while lo ≤ hi:
     mid = ceil((lo+hi)/2)
     if X[M[mid]] < X[i]:
       lo = mid+1
     else:
       hi = mid-1

   // Nakon pretrage, lo je za 1 veci od
   // duzine najveceg prefiksa od X[i]
   newL = lo

   // Prethodnik od X[i] je poslednji indeks 
   // podniza duzine newL-1
   P[i] = M[newL-1]
   M[newL] = i

   if newL > L:
     // Ukoliko je pronadjen podniz duzine vece od bilo koje
     // duzine koja je do sada pronadjena, azuriramo vrednost L
     L = newL

 // Rekonstruisemo najduzi rastuci podniz
 S = array of length L
 k = M[L]
 for i = L-1 to 0:
   S[i] = X[k]
   k = P[k]

 return S

S obzirom da algoritam koristi binarnu pretragu, složenost ovog rešenja iznosi O(n log n).

Implementacija u programskom jeziku C++[uredi | uredi izvor]

#define ARRAY_SIZE(A) sizeof(A)/sizeof(A[0])

// Binarna pretraga
int CeilIndex(int A[], int l, int r, int key) {
    int m;

    while( r - l > 1 ) {
        m = l + (r - l)/2;
        (A[m] >= key ? r : l) = m; 
    }

    return r;
}

int LongestIncreasingSubsequenceLength(int A[], int size) {
    

    int *tailTable   = new int[size];
    int len; // uvek pokazuje na praznu vrednost

    tailTable[0] = A[0];
    len = 1;
    for( int i = 1; i < size; i++ ) {
        if( A[i] < tailTable[0] )
            // nova najmanja vrednost
            tailTable[0] = A[i];
        else if( A[i] > tailTable[len-1] )
            // A[i] pokusava da prosiri najduzi podniz
            tailTable[len++] = A[i];
        else
            // A[i] pokusava da postane kandidat za trenutni kraj postojeceg podniza
            tailTable[CeilIndex(tailTable, -1, len-1, A[i])] = A[i];
    }

    delete[] tailTable;

    return len;
}

Reference[uredi | uredi izvor]

Spoljašnje veze[uredi | uredi izvor]