Lista binaria

blackdie
Consideriamo una lista di elementi con soli 0 e 1, ad esempio [1,0,0,0,1]
Una sottolista si dice equilibrata se contiente tanti 0 quanti 1.
Trovare l'algoritmo che restituisce la lunghezza della sottolista equilibrata piu lunga,e darne la complessità.

Qual è il miglior algoritmo(da l punto di vista della complessità computazionale?)



Grazie

Risposte
Rggb1
Problemino interessante, che mi sembra anche di aver già visto (Knuth?). Hai vari approcci possibili, te ne indico uno:
- imposti lunghezza massima a zero, posizione di partenza zero (testa lista);
- cerchi "in avanti" l'equilibrio, tenendo in memoria la lunghezza massima;
- continui finché non trovi la coda;
- passi a prossima posizione di partenza;

Devi aggiungere dei passaggi per evitare che l'algoritmo faccia operazioni inutili (come l'ho descritto ne farebbe a iosa).

Complessità: ci devo pensare bene, magari con un algoritmo vero davanti; però ad occhio le operazioni (cicli ripetuti) sono al più n+(n-1)+...+(n/2-1) [calcola te please :-D]

Si può fare di meglio? Ci ho pensato, ma tutto quel che mi è venuto in mente non mi sembra più veloce... ovviamente attendo anche smentite.

hamming_burst
Ciao,


proviamo....

assumo la lista come lista di interi composti da {0,1}

il problema a naso dovrebbe avere complessità $Omega(n)$ (da dimostrare, ma dalla dimensione dell'input dovrebbe essere corretta)


io ho ragionato con programmazione dinamica, ma dimostrare che è una sottostruttura ottima decisamente non cio voglio.
Ho fatto qualche nota non formale, ma scriverla non è difficile. L'algoritmo risultante con memoisation è semplicemente questo:
Dovrebbe essere corretto, ma senza dimostrazione non sono sicuro.

ti scrivo direttamente l'algoritmo in pseudo-codice:


integer search_sublisteq(integer[] list, int n){

	integer max_list0 <- 0 #dimensione lista di 0 equa massima
	integer max_list1 <- 0 #dimensione lista di 1 equa massima
	integer act0 <- 0 #dimensione lista di 0 attuale
	integer act1 <- 0 #dimensione lista di 1 attuale
	integer res <- 0 #dimensione massima liste eque


	#ciclo sulla lista
	for(integer i<-0 to n do){

		if(list[i]=0){ 
			
			act0 <- act0+1 #dimensione sottolista attuale di 0
		
		#list[i]!=list[i-1] la sottolista è cambiata da sottolista di 1 a sottolista di 0, 
		#allora la dimensione della sottolista di 1 precedetemente valutata
		#non cambierà la sua dimensione (lista attuale di 1) perchè la sua valutazione è terminanta
		#confronto con la dimensione della sottolista massima di 1	

			if(act1>max_list1){

				max_list1 <- act1
				act1 <- 0 #azzeramento sottolista attuale di 1
				
				if(max_list0=max_list1){ 

					#se le dimensioni delle sottoliste massime sono eque valuterò se saranno anche le massime
					if(max_list1>0){
						res <- max_list1
				
					}
				}
			}
		}
		else if(list[i]=1){

			act1 <- act1+1	#dimensione sottolista attuale di 1 
			
		#list[i]!=list[i-1] la sottolista è cambiata da sottolista di 0 a sottolista di 1, 
		#allora la dimensione della sottolista di 0 precedetemente valutata
		#non cambierà la sua dimensione (lista attuale di 0) perchè la sua valutazione è terminanta
		#confronto con la dimensione della sottolista massima di 0	

			if(act0>max_list0){
				
				max_list0 <- act0
				act0 <- 0 #azzeramento sottolista attuale di 1
				
				if(max_list0=max_list1){

					#se le dimensioni delle sottoliste massime sono eque valuterò se saranno anche le massime
					if(max_list0>res){
						res <- max_list0						
					}
				}
			}
		}

	}

#valutazione utlimo elemento se la sottolista ha elementi uguali di dimensione >1
 if(list[n]=0){ 
		if(act0>max_list0){
				
				max_list0 <- act0
						
				if(max_list0=max_list1){

					#se le dimensioni delle sottoliste massime sono eque valuterò se saranno anche le massime
					if(max_list0>res){
						res <- max_list0						
					}
				}
			}

			
		}
		else if(list[n]=1){

			if(act1>max_list1){

				max_list1 <- act1
						
				if(max_list0=max_list1){ 

					#se le dimensioni delle sottoliste massime sono eque valuterò se saranno anche le massime
					if(max_list1>0){
						res <- max_list1
				
					}
				}
			}
		}	
		
         
	

	#valore della sottolista equa massima, 0 se inesistente
	return res
}




Sempre che sia corretto, già ad occhio ci sono confronti che non servono o ripetuti.
la complessità è data semplicemente da ciclo for iterato $n$ volte e senza contare un fattore costante dato dai confronti,
l'algoritmo è $O(n)$.

Se il problema è $Omega(n)$ e l'algoritmo $O(n)$, abbiamo trovato un algoritmo ottimo $Theta(n)$ (sempre che sia corretto),
perciò non può esistere un algoritmo migliore in fatto di complessità. Di operazioni sicuramente, ma di complessità no.

Io ho provato, se controlli vediamo se è corretto :D

Ciao :-)



EDIT:
$Rggb$ mi hai preceduto, non ho visto la tua risposta, scusa :)

EDIT2:
ricontrollato il codice e modificato un res>max_list? con max_list?>res
? = 0 o 1 non cambia nel codice
commentato.

EDIT3:
anche se l'algoritmo non risolve il problema,
ho corretto un errore, se la stringa a come elemento una sottolista di valori uguali non si avrà una valutazione e cambiamento dei massimo,
es. elementi uguali [1110000] risulterà 0. Correggo questo errore.

Rggb1
Mica male ;)
Domanda: è corretto? Ora verifico, ma direi di si.

Anche considerando che l'algoritmo da me descritto fa meno operazioni per ciclo, e che effettivamente se tolgo i controlli già fatti mi rimangono "solo" al più n+(n/2+1) cicli (credo, ma devo verificare), la complessita di questo è minore.

Certo, come al solito mi sono focalizzato sul problema "reale", ho implementato qualcosa che restituisce (una delle) la sottolista massima, oltre la sua lunghezza. Deformazione professionale: che te ne fai di sapere quanto è lunga se non sai quale è? :-D

blackdie
Uhm,forse non capisco la soluzione guardandola velocemente,ma forse mi viene il dubbio di essermi spiegato male.Come sottolista equilibrata intendo una ad esempio se 11110001 la piu grande sottolista è 111000.Avevate inteso cosi?se si,allora dopo guardo con piu calma!

apatriarca
@ham_burst: Non riesco a capire che cosa stai facendo nel tuo codice, potresti aggiungere qualche tipo di spiegazione?

P.S. Non è che sia complicato come codice, ma non ho voglia di mettermi a cercare di comprendere del codice, quando due righe di spiegazione sarebbero sufficienti.

hamming_burst
@apatriarca:

si scusa, avevo iniziato a scriverli ma....la fame chiamava :D
adesso commento qualcosa (nel post sopra) :)

EDIT:
ho compilato e provato il codice a me torna il risultato con la stringa

[1110001] ritorna 3
[100001] ritorna 0

Io ho pensato di riportare la dimensione massima, l'eventuale posizione, la sottolista, non erano richieste nel problema :)

Se ho capito bene il problema dovrebbe (forse) essere corretto, aspetto smentite :-)


Uhm,forse non capisco la soluzione guardandola velocemente,ma forse mi viene il dubbio di essermi spiegato male.Come sottolista equilibrata intendo una ad esempio se 11110001 la piu grande sottolista è 111000.Avevate inteso cosi?se si,allora dopo guardo con piu calma!


ecco ho letto adesso, ho compreso male il problema, ma allora con questo assunto tipo:

[10001] dovrebbe tornare 1
perchè la sottolista è [10] e la sotolista massima di 0 è 1, quella di 1 è 1.
con questo l'algoritmo è sbagliato. Ma il problema allora è spiegato male.

EDIT2:
ma come sottolista si intende una sottolista continua?

la mia assunzione è
es: [11100111011]
ci sono 5 sottoliste:

[111] dim 3
[00] dim 2
[111] dim 3
[0] dim 1
[11] dim 2

la sottolista equa massima è

[00] [11] ma non sono continue.

Deckard1
Per come l'ho capito, intendo per sottolista una sottostringa. Per es.: la stringa 00100110010 ha una sottostringa equilibrata massima di valore 6 che è, tra le possibili, 011001 perchè è la sottostringa di lunghezza massima che ha al suo interno lo stesso numero di 0 e di 1.
Il primo algoritmo che mi è venuto in mente è una specie di ricerca esaustiva ed ha complessità $ O(n^2) $, ma non ho fatto nessun tipo di ottimizzazione.

hamming_burst
Riscrivendo di nuovo l'algoritmo (non quello sopra)

si riesce a risolvere es [11110001] restituisce 3, liste continue [111]e[000] non più sparse.
Non lo copio perchè leggendo il commento di $Deckard$ il problema è completamente diverso.

L'approccio da fare è della classe string matching, però al momento non mi metto a riscriverlo.

apatriarca
La nostra lista formata da simboli ${0, 1}$ è isomorfa ad una stringa con simboli ${-1, 1}$ attraverso la funzione $(-1)^x$. Considerano quindi una stringa con valori ${-1, 1}$ al posto di quella formata da ${0, 1}$, possiamo ridefinire il problema nel seguente modo: trovare la lunghezza della più lunga sotto-lista la cui somma sia uguale a $0$ (lo stesso numero di $1$ e $-1$). Un'idea per un algoritmo di complessità $O(n)$ è quella di memorizzare per ogni valore possibile $z$ il primo e l'ultimo indice per cui la somma degli elementi fino a quell'indice sia uguale a $z$ e la massima distanza tra i due indici trovata fino a quel punto. L'algoritmo naive è $O(n^2)$. Non credo che si possa fare meglio di $O(n)$ comunque, ma si può cercare di trovare un algoritmo $O(n)$ in cui la richiesta di spazio non sia $O(n)$.

blackdie
L'interpretazione corretta è quella di deckard.Scusate per il malinteso!

Rggb1
"blackdie":
L'interpretazione corretta è quella di deckard.Scusate per il malinteso!

Nessun malinteso, io l'avevo capito in quel modo.

"apatriarca":
Un'idea per un algoritmo di complessità $O(n)$ è quella di memorizzare per ogni valore possibile $z$ il primo e l'ultimo indice per cui la somma degli elementi fino a quell'indice sia uguale a $z$ e la massima distanza tra i due indici trovata fino a quel punto. L'algoritmo naive è $O(n^2)$. Non credo che si possa fare meglio di $O(n)$ comunque, ma si può cercare di trovare un algoritmo $O(n)$ in cui la richiesta di spazio non sia $O(n)$.

Se ci fai caso, quello che ho proposto ha complessità minore di $O(n^2)$ - e trova anche un risultato "extra".

Mi sta venendo in mente un metodo che scorra contemporaneamente dalla testa e dalla coda (ammesso si possa fare: che tipo di lista?..), sono sicuro di averlo già visto o fatto per qualcosa, che potrebbe avere complessità temporale minore della mia prima soluzione (addirittura $O(n)$ o meno), ma necessita di un bel po' memoria in strutture ausiliarie di conteggio.

hamming_burst
mi devo scusare io, ho inteso il problema male.
Tornandomi in mente un problema simile sono andato avanti con in mente particolarità di quel problema mischiandolo con questo (pensando fossero simili).

morale: mai fare assunzioni :)

EDIT:
adesso con il problema corretto, avrei una domanda sul vostro algoritmo, ma perchè $O(n^2)$ e spazio $O(n)$? Io pensadoci, lo vedo ancora come problema lineare, vedo di usare la stessa lista di input senza modificarla, con le stesse variabili dell'algoritmo sbagliato e con degli indici si dovrebbe risolvere. Ci ripenso meglio prima di scrivere di nuovo :-), ma l'algoritmo naive non lo vedo mica....

Deckard1
L'algoritmo proposto da apatriarca è infatti lineare (beh se vogliamo essere proprio precisi ci sarebbe un overhead al più logaritmico dovuto alla somma delle varie sottoliste poiché si costruisce un array di n elementi ciascuno grande al massimo, ma solitamente molto meno, $logn$). Non escludo si possa ottimizzare ancora qualcosina.
Complessità minore di $O(n)$ è comunque ovviamente impossibile da raggiungere anche solo per scandire l'intera sequenza.
Un possibile algoritmo naive consiste semplicemente nel determinare per ogni possibile sottostringa (e ce ne sono $n(n-1)/2$) quanti 1 e 0 questa contiene.

apatriarca
La complessità $O(n^2)$ era riferita all'algoritmo naive, quello di scorrere tutta la lista e verificare qual'è la sequenza più lunga che si può ottenere partendo da quell'elemento (ovviamente ci si può fermare quando la lista più lunga trovata è maggiore della lista rimanente). L'altro algoritmo è lineare. Sono certo che si possa fare di meglio a livello di spazio comunque.

@Deckard: non mi è chiaro il commento sull'overhead al più logaritmico. Ti riferisci all'uso di una qualche struttura particolare (albero ad esempio) per memorizzare i valori? Io pensavo ad una hash table con una semplice hash basata sul modulo (le somme delle sottoliste differiscono tutte per +1 o -1).

Rggb1
"apatriarca":
Sono certo che si possa fare di meglio a livello di spazio comunque.

Non sono certo, ma anche io ho questa sensazione.

[ Eppure ho già visto/letto il problema, e relativa soluzione, da qualche parte... ]

Deckard1
"apatriarca":
@Deckard: non mi è chiaro il commento sull'overhead al più logaritmico. Ti riferisci all'uso di una qualche struttura particolare (albero ad esempio) per memorizzare i valori? Io pensavo ad una hash table con una semplice hash basata sul modulo (le somme delle sottoliste differiscono tutte per +1 o -1).

Io avevo in mente un semplice array in cui per ogni indice i mi salvo il valore della sottolista [1..i]. Ad ogni modo con l'approccio "inverso" (ovvero che per tutti i valori delle possibili sottoliste ti salvi i due indici) non dovrebbe cambiare nulla: nel caso peggiore ti devi salvare n valori che distano l'uno dall'altro +1. Di conseguenza il massimo valore raggiunto avrà dimensione $logn$. E la complessità in tempo e spazio sarà $O(nlogn)$ quindi. Il caso peggiore è facilmente aggirabile, ma comunque senza ottimizzazioni sostanziali non credo che questo algoritmo scenda sotto il muro dell'$O(nlogn)$.

Umby2
Mi piace l'approccio proposto da apatriarca, andrei in questa direzione per trovare la migliore soluzione.

Memorizzerei su vettori di appoggio per ogni nuovo numero che si genera dalla somma, l'indice iniziale e quello finale, e se questo risulta essere il maggiore, mi salvo il contenuto in una successiva variabile work.
Alla fine del ciclo di lettura di tutto il vettore, potrò sapere dove inizia la stringa piu' lunga, dove termina, ed il numero di caratteri che si compone.

apatriarca
Sia ${ a_i }_{1 \le i \le n}$ la lista formata da ${-1, 1}$. "Unendo i punti" di $S(i) = \sum_{j=1}^{i} a_j$, otteniamo una linea spezzata in cui ogni segmento ha alternativamente coefficiente angolare $+1$ o $-1$. Sia $S(x)$ il valore di questa funzione per $x \in \RR$. Le funzioni
$m(s) = \min \{ x | S(x) = s \}$,
$M(s) = \max \{ x | S(x) = s \}$
e
$D(s) = M(s) - m(s)$
sono invece formate da successioni di segmenti e presentano discontinuità (dei salti). L'idea originale era semplicemente quella di aggiornare i valori delle funzioni $m(s)$ e $M(s)$ (con $s$ intero) ad ogni passo e aggiornare il massimo di $D(s)$ se necessario. Quando dicevo che si poteva forse fare di meglio in termini di spazio è perché non tutti i valori di $m(s)$ e $M(s)$ (con $s$ intero) sono necessari per ottenere il massimo di $D(s)$. Sono infatti sufficienti gli estremi dei segmenti che formano le due funzioni. Se $e_k$ ed $e_{k+1}$ sono due eventi successivi (cioè due estremi in $m(s)$ o $M(s)$) allora la funzione $D(s)$ può variare solo in uno dei seguenti modi tra $e_k$ ed $e_{k+1}$:
- è costante,
- è un segmento di coefficiente angolare $+2$,
- è un segmento di coefficiente angolare $-2$.
Si può quindi semplicemente mantenere una lista degli eventi e mantenere il valore in questi punti. Non ho comunque molta voglia di analizzare il numero di questi eventi.

Umby2
"Umby":
Mi piace l'approccio proposto da apatriarca, andrei in questa direzione per trovare la migliore soluzione.

Memorizzerei su vettori di appoggio per ogni nuovo numero che si genera dalla somma, l'indice iniziale e quello finale, e se questo risulta essere il maggiore, mi salvo il contenuto in una successiva variabile work.
Alla fine del ciclo di lettura di tutto il vettore, potrò sapere dove inizia la stringa piu' lunga, dove termina, ed il numero di caratteri che si compone.


Riprendo questa discussione, in quanto l'ho verificata scrivendo un po di codice.
Confermo che basta leggere la stringa una sola volta ed alla fine delle lettura è possibile conoscere il risultato.
Per la simulazione ho usato una stringa composta da 80 caratteri:
10110110111001010111100101110010110001110100011010101111011000101110110011011001

La elaborazione mi ha dato questo risultato, che sembrerebbe corretto:

La stringa massima si compone di 36 elementi (dal 29 al 64 [compresi gli estremi]), il codice di riferimento è "8".
Con questa elaborazione è anche possibile conoscere la seconda o terza stringa, in ordine di grandezza.


Rispondi
Per rispondere a questa discussione devi prima effettuare il login.