Dies entspricht dem derzeit unvollständigen Pseudocode von Thijser. Die Idee ist, den häufigsten der verbleibenden Artikeltypen zu verwenden, es sei denn, er wurde gerade genommen. (Siehe auch Coadys Implementierung dieses Algorithmus.)
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
Korrektheitsnachweis
Für zwei Elementtypen mit den Zählungen k1 und k2 weist die optimale Lösung k2 - k1 - 1 Fehler auf, wenn k1 <k2, 0 Fehler, wenn k1 = k2, und k1 - k2 - 1 Fehler, wenn k1> k2. Der Fall = ist offensichtlich. Die anderen sind symmetrisch; Jede Instanz des Minderheitselements verhindert höchstens zwei Fehler von insgesamt k1 + k2 - 1 möglich.
Dieser gierige Algorithmus liefert nach der folgenden Logik optimale Lösungen. Wir nennen ein Präfix (Teillösung) sicher, wenn es sich um eine optimale Lösung handelt. Das leere Präfix ist eindeutig sicher, und wenn ein sicheres Präfix eine vollständige Lösung ist, ist diese Lösung optimal. Es genügt, induktiv zu zeigen, dass jeder gierige Schritt die Sicherheit aufrechterhält.
Der einzige Weg, wie ein gieriger Schritt einen Fehler einführt, besteht darin, dass nur noch ein Elementtyp übrig bleibt. In diesem Fall gibt es nur einen Weg, um fortzufahren, und dieser Weg ist sicher. Andernfalls sei P das (sichere) Präfix unmittelbar vor dem betrachteten Schritt, sei P 'das Präfix unmittelbar danach und sei S eine optimale Lösung, die P erweitert. Wenn S auch P' erweitert, sind wir fertig. Andernfalls sei P '= Px und S = PQ und Q = yQ', wobei x und y Elemente und Q und Q 'Sequenzen sind.
Angenommen, P endet nicht mit y. Nach Wahl des Algorithmus ist x in Q mindestens so häufig wie y. Betrachten Sie die maximalen Teilzeichenfolgen von Q, die nur x und y enthalten. Wenn der erste Teilstring mindestens so viele x wie y hat, kann er umgeschrieben werden, ohne zunächst zusätzliche Fehler einzuführen, beginnend mit x. Wenn die erste Teilzeichenfolge mehr y als x hat, hat eine andere Teilzeichenfolge mehr x als y, und wir können diese Teilzeichenfolgen ohne zusätzliche Fehler neu schreiben, sodass x zuerst geht. In beiden Fällen finden wir eine optimale Lösung T, die P 'nach Bedarf erweitert.
Angenommen, P endet jetzt mit y. Ändern Sie Q, indem Sie das erste Vorkommen von x nach vorne verschieben. Dabei führen wir höchstens einen Defekt ein (wo x früher war) und beseitigen einen Defekt (das yy).
Alle Lösungen generieren
Dies ist die Antwort von tobias_k sowie effiziente Tests, um festzustellen, wann die derzeit in Betracht gezogene Auswahl in irgendeiner Weise global eingeschränkt ist. Die asymptotische Laufzeit ist optimal, da der Overhead der Erzeugung in der Größenordnung der Länge der Ausgabe liegt. Die Verzögerung im schlimmsten Fall ist leider quadratisch; es könnte mit besseren Datenstrukturen auf linear (optimal) reduziert werden.
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]
ist genau das gleiche wie[1, 3, 1, 2, 1, 4, 1, 5]
nach deinem kriterium?