Questo è sulla falsariga dello pseudocodice attualmente incompleto di Thijser. L'idea è di prendere il più frequente dei restanti tipi di oggetti a meno che non sia stato appena preso. (Vedi anche l'implementazione di Coady di questo algoritmo.)
import collections
import heapq
class Sentinel:
pass
def david_eisenstat(lst):
counts = collections.Counter(lst)
heap = [(-count, key) for key, count in counts.items()]
heapq.heapify(heap)
output = []
last = Sentinel()
while heap:
minuscount1, key1 = heapq.heappop(heap)
if key1 != last or not heap:
last = key1
minuscount1 += 1
else:
minuscount2, key2 = heapq.heappop(heap)
last = key2
minuscount2 += 1
if minuscount2 != 0:
heapq.heappush(heap, (minuscount2, key2))
output.append(last)
if minuscount1 != 0:
heapq.heappush(heap, (minuscount1, key1))
return output
Prova di correttezza
Per due tipi di elementi, con conteggi k1 e k2, la soluzione ottimale ha difetti k2 - k1 - 1 se k1 <k2, 0 difetti se k1 = k2 e k1 - k2 - 1 difetti se k1> k2. Il caso = è ovvio. Gli altri sono simmetrici; ogni istanza dell'elemento di minoranza previene al massimo due difetti su un totale di k1 + k2 - 1 possibile.
Questo algoritmo avido restituisce soluzioni ottimali, secondo la seguente logica. Chiamiamo sicuro un prefisso (soluzione parziale) se si estende a una soluzione ottimale. Chiaramente il prefisso vuoto è sicuro, e se un prefisso sicuro è una soluzione completa, allora quella soluzione è ottimale. È sufficiente mostrare in modo induttivo che ogni passo avido mantiene la sicurezza.
L'unico modo in cui un passaggio avido introduce un difetto è se rimane un solo tipo di oggetto, nel qual caso c'è solo un modo per continuare, e quel modo è sicuro. Altrimenti, sia P il prefisso (sicuro) appena prima del passaggio in esame, sia P 'il prefisso subito dopo e sia S una soluzione ottima che estende P. Se S estende anche P', allora abbiamo finito. Altrimenti, siano P '= Px e S = PQ e Q = yQ', dove xey sono elementi e Q e Q 'sono sequenze.
Supponiamo prima che P non termini con y. A scelta dell'algoritmo, x è almeno tanto frequente in Q quanto y. Considera le sottostringhe massime di Q contenenti solo x e y. Se la prima sottostringa ha almeno tante x quante y, allora può essere riscritta senza introdurre difetti aggiuntivi per iniziare con x. Se la prima sottostringa ha più y di x, allora qualche altra sottostringa ha più x di y, e possiamo riscrivere queste sottostringhe senza ulteriori difetti in modo che x sia la prima. In entrambi i casi troviamo una soluzione ottima T che estende P ', secondo necessità.
Supponiamo ora che P finisca con y. Modificare Q spostando la prima occorrenza di x in primo piano. In tal modo, introduciamo al massimo un difetto (dove era x) ed eliminiamo un difetto (il yy).
Generazione di tutte le soluzioni
Questa è la risposta di tobias_k più test efficienti per rilevare quando la scelta attualmente in esame è in qualche modo vincolata a livello globale. Il tempo di esecuzione asintotico è ottimale, poiché l'overhead di generazione è dell'ordine della lunghezza dell'output. Il ritardo nel caso peggiore purtroppo è quadratico; potrebbe essere ridotto a lineare (ottimale) con strutture dati migliori.
from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange
def get_mode(count):
return max(count.items(), key=itemgetter(1))[0]
def enum2(prefix, x, count, total, mode):
prefix.append(x)
count_x = count[x]
if count_x == 1:
del count[x]
else:
count[x] = count_x - 1
yield from enum1(prefix, count, total - 1, mode)
count[x] = count_x
del prefix[-1]
def enum1(prefix, count, total, mode):
if total == 0:
yield tuple(prefix)
return
if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
yield from enum2(prefix, mode, count, total, mode)
else:
defect_okay = not prefix or count[prefix[-1]] * 2 > total
mode = get_mode(count)
for x in list(count.keys()):
if defect_okay or [x] != prefix[-1:]:
yield from enum2(prefix, x, count, total, mode)
def enum(seq):
count = Counter(seq)
if count:
yield from enum1([], count, sum(count.values()), get_mode(count))
else:
yield ()
def defects(lst):
return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))
def test(lst):
perms = set(permutations(lst))
opt = min(map(defects, perms))
slow = {perm for perm in perms if defects(perm) == opt}
fast = set(enum(lst))
print(lst, fast, slow)
assert slow == fast
for r in range(10000):
test([randrange(3) for i in range(randrange(6))])
[1, 2, 1, 3, 1, 4, 1, 5]
è esattamente lo stesso[1, 3, 1, 2, 1, 4, 1, 5]
del tuo criterio?