Ini sejalan dengan pseudocode Thijser yang saat ini tidak lengkap. Idenya adalah untuk mengambil jenis barang yang tersisa paling sering kecuali itu baru saja diambil. (Lihat juga implementasi Coady dari algoritma ini.)
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
Bukti kebenaran
Untuk dua jenis item, dengan jumlah k1 dan k2, solusi optimal memiliki cacat k2 - k1 - 1 jika k1 <k2, 0 cacat jika k1 = k2, dan k1 - k2 - 1 cacat jika k1> k2. Kasus = sudah jelas. Yang lainnya simetris; setiap kemunculan elemen minoritas mencegah paling banyak dua cacat dari total k1 + k2 - 1 kemungkinan.
Algoritme rakus ini mengembalikan solusi optimal, dengan logika berikut. Kami menyebut awalan (solusi parsial) aman jika diperluas ke solusi optimal. Jelas awalan kosong itu aman, dan jika awalan aman adalah solusi lengkap maka solusi itu optimal. Sudah cukup untuk menunjukkan secara induktif bahwa setiap langkah serakah menjaga keamanan.
Satu-satunya cara langkah serakah menyebabkan cacat adalah jika hanya satu jenis barang yang tersisa, dalam hal ini hanya ada satu cara untuk melanjutkan, dan cara itu aman. Jika tidak, biarkan P menjadi awalan (aman) sebelum langkah yang dipertimbangkan, biarkan P 'menjadi awalan tepat setelahnya, dan biarkan S menjadi solusi optimal yang memperluas P. Jika S juga memperpanjang P', maka kita selesai. Jika tidak, misalkan P '= Px dan S = PQ dan Q = yQ', dimana x dan y adalah item dan Q dan Q 'adalah barisan.
Misalkan dulu P tidak diakhiri dengan y. Berdasarkan pilihan algoritme, x setidaknya sama seringnya di Q dengan y. Pertimbangkan substring maksimal Q yang hanya berisi x dan y. Jika substring pertama memiliki sedikitnya x sebanyak y, maka substring tersebut dapat ditulis ulang tanpa menimbulkan cacat tambahan diawali dengan x. Jika substring pertama memiliki lebih banyak y daripada x, maka beberapa substring lain memiliki lebih banyak x daripada y, dan kita dapat menulis ulang substring ini tanpa cacat tambahan sehingga x menjadi yang pertama. Dalam kedua kasus tersebut, kami menemukan solusi optimal T yang memperluas P ', sesuai kebutuhan.
Misalkan sekarang P diakhiri dengan y. Ubah Q dengan memindahkan kemunculan pertama x ke depan. Dalam melakukannya, kami memperkenalkan paling banyak satu cacat (di mana x dulu) dan menghilangkan satu cacat (yy).
Menghasilkan semua solusi
Ini adalah jawaban tobias_k ditambah tes yang efisien untuk mendeteksi ketika pilihan yang saat ini sedang dipertimbangkan dibatasi secara global dalam beberapa cara. Waktu berjalan asimtotik adalah optimal, karena overhead pembangkitan berada pada urutan panjang keluaran. Sayangnya, penundaan kasus terburuk adalah kuadrat; dapat direduksi menjadi linier (optimal) dengan struktur data yang lebih baik.
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]
yang persis sama[1, 3, 1, 2, 1, 4, 1, 5]
dengan kriteria Anda?