Come trovare l'insieme massimo di elementi


14

Ho un problema algoritmico.

Dato un array (o un insieme) di numeri interi non negativi. Trova l'insieme massimo di tale che per tutti ,.TnSTaSa|S|

Per esempio:

  1. Se T = [1, 3, 4, 1, 3, 6], allora S può essere [3, 3, 6] o [3, 4, 6] o [4, 3, 6].
  2. In T = [7, 5, 1, 1, 7, 4], quindi S è [7, 5, 7, 4].

Ho provato questa funzione ricorsiva.

function(T):
    if minimum(T) >= length(T): 
        return T
    else: 
        return function(T\minimum(T))

Esiste un algoritmo non ricorsivo. (Non ho controllato il mio algoritmo ricorsivo, quindi potrebbe avere qualche difetto.)

Risposte:


14

Ordina T. Quindi prendi gli elementi mentre T[i] >= i+1.

Per esempio sorted(T)=[6,4,3,3,1,1]. Poi, T[0] = 6 > 1, T[1] = 4 > 2, T[2] = 3 <= 3e, infine, T[3] = 3 < 4così abbiamo S = [T[0], T[1], T[2]].


3
Questo, ovviamente, manca l'altra soluzione , ma sembra che il PO cercando qualsiasi soluzione, piuttosto che tutte le soluzioni. {6,3,3}
Rick Decker,

2
Ottiene il numero di elementi giusto. Sappiamo di avere soluzioni con 3 elementi ma non con 4; in questo caso abbiamo 4 elementi ≥ 3, quindi sappiamo che possiamo scegliere qualsiasi 3 di quelli per una soluzione.
gnasher729,

3
Gradirei un argomento di correttezza.
Raffaello

Penso che probabilmente potresti farlo in tempo O (n) con una variante di introselect.
user2357112 supporta Monica il

8

Dal mio commento originariamente: questo è strettamente correlato a una quantità onnipresente nella valutazione accademica della produttività, l'indice Hirsh, meglio noto come -indexh . In breve è definito come il numero di pubblicazioni si ha tale che ciascuno di essi presenta almeno h citazioni (il più grande tale h ).hhh

L'unico modo in cui il tuo problema differisce è che potresti essere interessato non solo a quante pubblicazioni soddisfano il criterio, ma anche a ciò che conta la loro citazione , ma questa è una banale modifica. I dati sono già lì, l'algoritmo originale li rilascia.

Il calcolo generalmente attuato è piuttosto semplice e concorda con la risposta di Karolis Juodelė .

Aggiornamento: a seconda della dimensione e del carattere dei dati, può valere la pena esplorare metodi che ordinano parzialmente l'array filtrando i dati sopra e sotto un punto cardine (mi viene in mente quicksort). Quindi, a seconda che ci sia troppo o troppo poco, regola il perno e ripeti sul sottoinsieme che lo contiene e così via. Non è necessario un ordine tra elementi superiori a , e certamente non tra quelli inferiori a quello. Ad esempio, una volta trovati tutti gli elementi maggiori o uguali a h 1 e ce ne sono meno di h 1 , non è necessario toccare nuovamente quel sottoinsieme, basta aggiungerlo. Questo converte la ricorsione inerente al quicksort in una ricorsione della coda e quindi può essere riscritta come un ciclo.hh1h1

Il mio Haskell è un po 'arrugginito ma questo dovrebbe fare quello che ho descritto sopra e sembra funzionare. Spero che possa essere compreso fino a un certo punto, sono felice di fornire ulteriori spiegazioni.

-- just a utility function
merge :: [a] -> [a] -> [a]
merge [] ys = ys
merge (x:xs) ys = x : merge xs ys

-- the actual implementation
topImpl :: [Int] -> [Int] -> [Int]
topImpl [] granted = granted
topImpl (x:xs) granted
  | x == (1 + lGreater + lGranted) = x : merge greater granted
  | x > (1 + lGreater + lGranted) = topImpl smaller (x : merge greater granted)
  | otherwise = topImpl greater granted
  where smaller = [y | y <- xs, y < x]
        greater = [y | y <- xs, y >= x]
        lGreater = length greater
        lGranted = length granted

-- starting point is: top of whole array, granted is empty
top :: [Int] -> [Int]
top arr = topImpl arr []

L'idea è quella di raccogliere grantedciò che sai parteciperà sicuramente al risultato e non ordinarlo ulteriormente. Se greaterinsieme agli xaccoppiamenti, siamo fortunati, altrimenti dovremo provare con un sottoinsieme più piccolo. (Il perno xè semplicemente quello che è accaduto per essere il primo elemento dell'elenco secondario che è attualmente considerato.) Notare che il vantaggio significativo rispetto al prendere elementi più grandi uno per uno è che lo facciamo su blocchi di dimensioni medie e non è necessario ordinarli ulteriormente.remainiong/2

Esempio:

Prendiamo il tuo set [1,3,4,1,3,6].

  1. x = 1, granted = [], greater = [3,4,1,3,6]. Ouch, abbiamo colpito un caso patologico quando il perno è troppo piccolo (in realtà così piccolo che smallerè vuoto) proprio nel primo passo. Fortunatamente il nostro algo è pronto per questo. Scarta xe riprova con greatersolo.

  2. x = 3, granted = [], greater = [4,3,6]. Insieme, formano un array di lunghezza 4, ma abbiamo solo un limite dal basso di 3, quindi è troppo. Ripeti da greatersolo.

  3. x = 4, granted = [], greater = [6]. Questo dà una matrice di 2 elementi ≥ 4 ciascuno, sembra che potremmo averne bisogno per alcuni di più. Continua e ripeti smaller = [3].

  4. x = 3, granted = [4,6], greater = []. Questo insieme fornisce una matrice di 3 elementi ≥ 3 ciascuno, quindi abbiamo la nostra soluzione [3,4,6]e possiamo tornare. (Si noti che la permutazione può variare a seconda dell'ordinamento dell'input, ma conterrà sempre i termini più alti possibili, mai [3,3,6]o [3,3,4]per il vostro esempio.)

(A proposito, si noti che la ricorsione in effetti è appena crollata in un ciclo.) La complessità è leggermente migliore di quicksort a causa dei molti confronti salvati:

n-1

O(logn)O(n)

nO(n2)

Ci sono alcuni confronti inutili nel codice sopra, come calcolare smallerse ne abbiamo bisogno o no, possono essere facilmente rimossi. (Penso che la valutazione pigra se ne occuperà comunque.)


6

Non c'è niente di sbagliato nel tuo algoritmo, e ovviamente la maggior parte degli algoritmi ricorsivi può essere convertita in loop, qui una versione in loop del tuo codice ricorsivo:

function(T):
    while minimum(T) <= lenght(T):
         remove minimum(T) from T
    loop

6
Tutti gli algoritmi ricorsivi possono essere convertiti in loop. Dopotutto, una macchina di Turing non sa nulla della ricorsione.
David Richerby,

4

Qualsiasi algoritmo ricorsivo può essere riscritto per usare l'iterazione. Dopotutto, una macchina di Turing non sa nulla della ricorsione, ma può implementare qualsiasi algoritmo. In linea di principio, è possibile riscrivere la funzione ricorsiva scrivendo il proprio codice di manipolazione dello stack per ricordare i valori dei parametri della funzione e qualsiasi variabile locale che potrebbe avere. In questo caso particolare, la tua funzione è ricorsiva alla coda (una volta che una chiamata ricorsiva ritorna, anche la cosa che l'ha chiamata immediatamente ritorna) quindi non è nemmeno necessario mantenere lo stack.


3

Utilizzare un min-heap per eseguire un heapsort parziale, poiché non è necessario ordinare l'intero array.

Continua a far scoppiare gli elementi avidamente fino a quando non superi la soglia indicata.


2
Anche qui apprezzerei un'idea di correttezza.
Raffaello
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.