이것은 Thijser의 현재 불완전한 의사 코드를 따른 것입니다. 아이디어는 방금 가져온 것이 아니라면 나머지 항목 유형 중 가장 자주 사용하는 것입니다. ( 이 알고리즘 의 Coady 구현 도 참조하십시오 .)
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
정확성 증명
개수가 k1 및 k2 인 두 항목 유형의 경우 최적 솔루션은 k1 <k2 인 경우 k2-k1-1 개의 결함, k1 = k2 인 경우 0 개의 결함, k1> k2 인 경우 k1-k2-1 개의 결함이 있습니다. = 경우는 분명합니다. 나머지는 대칭입니다. 소수 요소의 각 인스턴스는 가능한 총 k1 + k2-1 중 최대 2 개의 결함을 방지합니다.
이 탐욕스러운 알고리즘은 다음 논리에 따라 최적의 솔루션을 반환합니다. 최적의 솔루션으로 확장되는 경우 접두사 (부분 솔루션)를 안전하다고 부릅니다 . 분명히 빈 접두사는 안전하며 안전한 접두사가 전체 솔루션이면 해당 솔루션이 최적입니다. 탐욕스러운 각 단계가 안전을 유지한다는 것을 귀납적으로 보여 주면 충분합니다.
탐욕스러운 단계가 결함을 유발하는 유일한 방법은 항목 유형이 하나만 남아있는 경우 계속할 수있는 방법이 하나 뿐이며 그 방법이 안전합니다. 그렇지 않으면 고려중인 단계 바로 전에 P를 (안전한) 접두사로, P '를 바로 뒤의 접두사로, S를 P를 확장하는 최적의 솔루션으로 두십시오. S가 P'도 확장하면 완료됩니다. 그렇지 않으면 P '= Px 및 S = PQ 및 Q = yQ'로 설정합니다. 여기서 x와 y는 항목이고 Q와 Q '는 시퀀스입니다.
먼저 P가 y로 끝나지 않는다고 가정합니다. 알고리즘의 선택에 따라 x는 최소한 Q에서 y만큼 자주 발생합니다. x와 y 만 포함하는 Q의 최대 부분 문자열을 고려하십시오. 첫 번째 부분 문자열에 최소한 y만큼 x가있는 경우 x로 시작하는 추가 결함을 도입하지 않고 다시 작성할 수 있습니다. 첫 번째 부분 문자열이 x보다 y가 더 많으면 다른 부분 문자열에 y보다 x가 더 많으며 추가 결함없이 이러한 부분 문자열을 다시 작성하여 x가 먼저 가도록 할 수 있습니다. 두 경우 모두 필요에 따라 P '를 확장하는 최적 솔루션 T를 찾습니다.
이제 P가 y로 끝난다고 가정합니다. x의 첫 번째 발생을 앞으로 이동하여 Q를 수정합니다. 이를 통해 최대 하나의 결함 (x가 있었던 곳)을 도입하고 하나의 결함 (yy)을 제거합니다.
모든 솔루션 생성
이것은 tobias_k의 대답 과 현재 고려중인 선택이 어떤 식 으로든 전역 적으로 제한되는 경우를 감지하는 효율적인 테스트입니다. 생성 오버 헤드가 출력 길이의 순서이기 때문에 점근 적 실행 시간이 최적입니다. 불행히도 최악의 경우 지연은 2 차입니다. 더 나은 데이터 구조로 선형 (최적)으로 축소 될 수 있습니다.
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]
과 정확히 같은 것이[1, 3, 1, 2, 1, 4, 1, 5]
있습니까?