Esiste un algoritmo che trova le sottosequenze ordinate di dimensione tre in


21

Voglio provare o confutare l'esistenza di un algoritmo che, dato un array di numeri interi, trova tre indici e tali che e (o scopre che non esiste tale triplo) in tempo lineare.i , j k i < j < k A [ i ] < A [ j ] < A [ k ]Ai,jki<j<kA[i]<A[j]<A[k]

Questa non è una domanda a casa; L'ho visto su un forum di programmazione incorniciato come "provare a implementare un tale algoritmo". Sospetto che sia impossibile dopo vari esperimenti. La mia intuizione me lo dice, ma non conta davvero nulla.

Vorrei dimostrarlo formalmente. Come si fa? Idealmente, vorrei vedere una dimostrazione dettagliata passo dopo passo, e poi, se sei così propenso, alcune spiegazioni su come procedere per dimostrare / smentire semplici domande come questa in generale. Se aiuta, alcuni esempi:

[1,5,2,0,3] → (1,2,3)
[5,6,1,2,3] → (1,2,3)
[1,5,2,3] → (1,2,3)
[5,6,1,2,7] → (1,2,7)
[5,6,1,2,7,8] → (1,2,7)
[1,2,999,3] → (1,2,999)
[999,1,2,3] → (1,2,3)
[11,12,8,9,5,6,3,4,1,2,3] → (1,2,3)
[1,5,2,0,-5,-2,-1] → (-5,-2,-1)

Supponevo che si potesse iterare su , e ogni volta che c'è un (il nostro attuale , cioè), facciamo un nuovo triplo e lo inseriamo in un array. Continuiamo a fare un passo e confrontare ogni tripla fino a quando una delle nostre triple è completa. Così è come , ! Ma penso che questo sia più complesso del semplice poiché il numero di triple sul nostro array triplo corrisponderebbe nel peggiore dei casi alla dimensione della lista di input.i < j j O ( n )Ai<jj[1,5,2,0,-5,-2,-1] → 1..2.. -5.. -2.. -1[1,5,2,0,-5,-2,3,-1] → 1..2.. -5.. -2.. 3O(n)


6
Vedere

Si noti che nel peggiore dei casi (matrice ordinata) si ha anche molte triple adatte. Ti preghiamo di considerare di dare l'algoritmo che proponi come pseudo codice; Penso che la tua spiegazione non sia completa. Θ(n3)
Raffaello

Risposte:


14

Questa è la variazione del problema di sottosequenza crescente più lungo ; questa è la soluzione presentata su Wikipedia usando due array ausiliari e :PMP

  • k A [ k ] j A [ k ] k iM[j] - memorizza la posizione del più piccolo valore di in modo tale che vi sia una sottosuccessione crescente di lunghezza termina in sulla gamma (nota abbiamo j k i qui , perché j rappresenta la lunghezza della crescente sottosequenza, e k rappresenta la posizione del suo termine. Ovviamente, non possiamo mai avere una sottosequenza crescente di lunghezza 13 che termina in posizione 11 . k i per definizione).kA[k]jA[k]kijkijk1311ki
  • - memorizza la posizione del predecessore di A [ k ] nella sottosequenza crescente più lunga che termina con A [ k ] .P[k]A[k]A[k]

    Inoltre l'algoritmo memorizza una variabile rappresenta la lunghezza della sottosequenza crescente più lunga trovata finora.L

Questo algoritmo viene eseguito nel caso peggiore . Il tuo problema è un caso speciale che ti consente di tornare quando L = 3 che spinge il tempo di esecuzione verso il basso su O ( n ) perché la ricerca binaria viene eseguita solo su array di lunghezza al massimo due, che è quindi nel tempo O ( 1 ) rispetto a Θ ( log n ) nel caso generale.Θ(nlogn)L=3O(n)O(1)Θ(logn)

Considera lo pseudo codice modificato:

 L = 0
 for i = 1, 2, ... n:
    binary search for the largest positive j ≤ L
      such that X[M[j]] < X[i] (or set j = 0 if no such value exists)
    P[i] = M[j]
    if j == L or X[i] < X[M[j+1]]:
       M[j+1] = i
       L = max(L, j+1)
   if L==3 : return true; // you can break here, and return true.
return false; // because L is smaller than 3.

@SaeedAmiri Ho visto il commento ma non avevo ancora avuto il tempo di esaminarlo (ho pubblicato la domanda prima di andare a letto). Ho sospettato dal tuo link che il nostro caso speciale L = 3 avrebbe aiutato in qualche modo ma non aveva avuto la possibilità di capire i dettagli. Sono attualmente al lavoro e il tempo è limitato. Ti assicuro che apprezzo la tua risposta. Sarebbe superficiale da parte mia ringraziarti per questo senza comprendere appieno ogni linea al suo interno.
Christopher Fatto il

@SaeedAmiri: sono d'accordo che ti aspetti di più "colmare il divario" qui, ma devi ancora dare gli argomenti d'angolo di una prova (comunque imprecisa) almeno. Per quanto riguarda l'OP, sembra che abbia sede in Italia, quindi probabilmente era profondamente addormentato tra il tuo commento e la tua risposta (e probabilmente è impegnato con l'Est ora).
Raffaello

@ChristopherDone, non voglio farti arrabbiare, scusa se è un mio errore, hai sicuramente ragione.

O(1)

OK, sembra buono. Mi ci è voluto un po 'per capire il comportamento dell'algoritmo di sequenza crescente più lungo in generale. Successivamente, la lunghezza massima == 3 modifica va bene. Grazie!
Christopher Fatto il

11

Una nota sulla metodologia

Ho pensato un po 'a questo problema e ho trovato una soluzione. Quando ho letto la risposta di Saeed Amiri , mi sono reso conto che quella che mi è venuta in mente era una versione specializzata dell'algoritmo di ricerca della sottosequenza più lunga standard per una sequenza di lunghezza 3. Sto pubblicando il modo in cui ho trovato la soluzione, perché la penso è un esempio interessante di problem solving.

La versione a due elementi

i<jA[i]<A[j]

Ai<j,A[i]A[j]i,A[i]A[i+1]iA[i]<A[i+1]

Questo caso è molto semplice; proveremo a generalizzarlo. Mostra che il problema, come affermato, non è risolvibile: gli indici richiesti non sempre esistono. Quindi chiederemo piuttosto che l'algoritmo restituisca indici validi, se esistono, o affermi correttamente che tali indici non esistono.

Venendo con l'algoritmo

A(A[i1],,A[im])i1<<imA(A[i],A[i+1],,A[i+m1])

Abbiamo appena visto che gli indici richiesti non sempre esistono. La nostra strategia è quella di studiare quando gli indici non esistono. Lo faremo supponendo che stiamo tentando di trovare gli indici e vedere come la nostra ricerca potrebbe andare storta. Quindi i casi in cui la ricerca non va storto forniranno un algoritmo per trovare gli indici.

4,3,2,1,0

j=i+1k=j+1A[i]<A[i+1]<A[i+2]

4,3,2,1,2,3,2,1,0

A[j]<A[j+1]iA[i]<A[j]kA[j+1]<A[k]

4,3,2,2.5,1.5,0.5,1,0

ik

3,2,1,3.5,2.5,1.5,0.5, -0.5,1.25, -0.25 3,2,1,2.5,1.5,0.5,2,1,0

ijki

2.1,3,2,1,2.5,1.5,0.5,2,1,0 1,2,0,2.5,1.5,0.5

i(i,j)ki(i,j)(i,j)i>jA[i]<A[i]ii(i,j)jA[j]<A[j](i,j)

Dichiarazione dell'algoritmo

Dato nella sintassi di Python, ma attenzione che non l'ho provato.

def subsequence3(A):
    """Return the indices of a subsequence of length 3, or None if there is none."""
    index1 = None; value1 = None
    index2 = None; value2 = None
    for i in range(0,len(A)):
        if index1 == None or A[i] < value1:
            index1 = i; value1 = A[i]
        else if A[i] == value1: pass
        else if index2 == None:
            index2 = (index1, i); value2 = (value1, A[i])
        else if A[i] < value2[1]:
            index2[1] = i; value2[1] = A[i]
        else if A[i] > value2[1]:
            return (index2[0], index2[1], i)
    return None

Schizzo di prova

index1è l'indice del minimo della parte dell'array che è già stata attraversata (se si verifica più volte, conserviamo la prima occorrenza) o Noneprima di elaborare il primo elemento. index2memorizza gli indici della sottosequenza crescente di lunghezza 2 nella parte già attraversata dell'array che ha l'elemento più basso più basso, o Nonese tale sequenza non esiste.

Quando return (index2[0], index2[1], i)viene eseguito, abbiamo value2[0] < value[1](questo è un invariante di value2) e value[1] < A[i](ovvio dal contesto). Se il ciclo termina senza invocare il ritorno anticipato value1 == None, nel qual caso non vi è alcuna sottosequenza crescente di lunghezza 2 e tanto meno 3, oppure value1contiene la sottosequenza crescente di lunghezza 2 che presenta l'elemento più basso più basso. In quest'ultimo caso, abbiamo inoltre l'invariante che nessuna sottosequenza crescente di lunghezza 3 termina prima di value1; pertanto l'ultimo elemento di tale sottosequenza, aggiunto a value2, formerebbe una sottosequenza crescente di lunghezza 3: poiché abbiamo anche l'invariante che value2non fa parte di una sottosequenza crescente di lunghezza 3 contenuta nella parte già attraversata dell'array, lì non esiste tale sottosequenza nell'intero array.

Dimostrare che gli invarianti di cui sopra sono lasciati come esercizio per il lettore.

Complessità

O(1)O(1)O(n)

Prova formale

Lasciato come esercizio al lettore.


8

O(n)O(n)

Innanzitutto, attraversa l'array da sinistra a destra mantenendo uno stack e un array ausiliario che indica per ogni elemento, l'indice di un elemento maggiore di esso e alla sua destra.

1

Ogni volta che si considera un nuovo elemento nella matrice, se tale elemento è maggiore dell'elemento superiore della pila, lo si estrae dalla pila e si imposta l'elemento della matrice ausiliaria corrispondente alla cima per avere l'indice del nuovo elemento sotto considerazione.

Continua a estrarre gli elementi dallo stack e impostare l'indice corrispondente, mentre l'elemento corrente è maggiore. Una volta che la parte superiore ha un elemento che non è minore (o diventa vuoto), spingere l'elemento corrente sullo stack e procedere all'elemento successivo dell'array, ripetendo il passaggio precedente.

Effettua un altro passaggio (e un altro array ausiliario), ma andando da destra a sinistra.

1

O(n)

ki

Lo pseudo codice per il primo passaggio potrebbe essere simile al seguente:

Stack <Pair<Elem, Index>> greats;
Elem auxArr[inputArr.Length];

for (Index i = 0; i < inputArr.Length; i++) {

    while (!greats.IsEmpty() && inputArr[i] > greats.PeekTop().Elem) {
        Pair top = greats.Pop();
        auxArr[top.Index] = i;
    }

    Pair p;
    p.Elem = inputArr[i];
    p.Index = i;

    greats.Push(p);
}

"Dato che si considera ogni elemento dell'array solo un numero costante di volte, questo è O (n) tempo." Oh, briciole. In qualche modo avevo escluso più passaggi costanti, scartandolo come non O (n). Molto stupido. Sono grato per la tua spiegazione e tenterò ancora una volta di risolverlo.
Christopher Fatto il
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.