인접한 동일한 요소없이 목록의 모든 순열 생성


87

목록을 정렬 할 때

a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]

결과 목록에서 동일한 요소는 항상 인접 해 있습니다.

반대 작업을 어떻게 수행 할 수 있습니까? 동일한 요소가 인접하지 않도록 (또는 가능한 한 드물게) 목록을 섞으십시오.

예를 들어, 위 목록에서 가능한 솔루션 중 하나는 다음과 같습니다.

p = [1,3,2,3,2,1,2]

보다 공식적으로, 주어진 list 는 쌍의 수를 최소화 a하는 순열 p을 생성합니다 p[i]==p[i+1].

목록이 크기 때문에 모든 순열을 생성하고 필터링하는 것은 옵션이 아닙니다.

보너스 질문 : 이러한 모든 순열을 효율적으로 생성하는 방법은 무엇입니까?

다음은 솔루션을 테스트하는 데 사용하는 코드입니다. https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD : 많은 사람들이 훌륭한 답변을 게시했기 때문에 여기서 우승자를 선택하는 것은 어려운 선택이었습니다. @VincentvanderWeele , @David Eisenstat , @Coady , @ enrico.bacis@srgerg 는 최상의 순열을 완벽하게 생성하는 함수를 제공했습니다. @tobias_k 와 David도 보너스 질문에 답했습니다 (모든 순열 생성). 정확성 증명을 위해 David에게 추가 포인트.

@VincentvanderWeele의 코드가 가장 빠른 것으로 보입니다.


1
그래서 당신은 평등 에만 관심이 있습니까? 당신의 기준 [1, 2, 1, 3, 1, 4, 1, 5]과 정확히 같은 것이 [1, 3, 1, 2, 1, 4, 1, 5]있습니까?
Bakuriu 2014 년

1
"효율적인"알고리즘은있을 수 없습니다. 같은 목록을 가지고 [1, 1, 1, ..., 2, 3, 4, ..., N]2N요소를. 좋은 순열을 얻기 위해 n > 1각 연속 쌍 사이 에 숫자 를 넣을 수 있습니다 1. 그런 다음 N/2요소 를 순열하고 모든 유효한 순열을 얻습니다 (나쁜 순열은 없지만 더 많을 수 있음을 의미 함). 이러한 순열의 수는 O (N ^ 2)이므로 O (N ^ 2)보다 더 잘할 수 없습니다. 그래도 순진한 접근 방식의 O (N ^ 3)보다 낫습니다.
Bakuriu 2014-08-13

6
@Bakuriu : 두 가지 : (1) 명확하게 말하자면, 귀하의 예는 보너스 질문에 대한 효율적인 알고리즘이 없을 수 있음을 보여줍니다 . (2) 귀하의 예제에 대한 모든 최적의 솔루션을 열거하는 것은 O ((N / 2)!)이며, 이는 O (N ^ 2)보다 훨씬 나쁩니다 (즉, 귀하의 예제는 당신이 깨달은 것보다 훨씬 강력합니다 :-)
j_random_hacker

11
@msw : 저는 웹 사이트를 만들고 있는데 다른 제공 업체의 광고 블록이있는 행이 있습니다. 동일한 공급자의 블록이 나란히 서 있지 않도록 정렬하고 싶습니다.
georg

2
나는 이것이 "중복에 가깝지도 않다"고 말하지는 않지만, 동일한 요소 사이의 거리를 고려하기 때문에 복제 주장은 다른 질문입니다. WhyCry의 댓글 이후에 투표를 마친 사람들 : 앞으로 더 많은 관심을 기울이십시오.
David Eisenstat

답변:


30

이것은 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))])

23

의사 코드 :

  1. 목록 정렬
  2. 정렬 된 목록의 전반부를 반복하고 결과 목록의 모든 짝수 인덱스를 채 웁니다.
  3. 정렬 된 목록의 후반부를 반복하고 결과 목록의 모든 홀수 인덱스를 채 웁니다.

p[i]==p[i+1]입력의 절반 이상이 동일한 요소로 구성되는 경우 에만 가능 하며,이 경우 동일한 요소를 연속 스팟에 넣는 것 외에 다른 선택이 없습니다 (피전 홀 원칙에 따라).


주석에서 지적했듯이,이 접근 방식은 요소 중 하나가 적어도 n/2몇 번 발생하는 경우 (또는 n/2+1홀수 일 경우, 짝수와 홀수 모두 n(n+1)/2)대해 일반화 되는 경우) 하나의 충돌이 너무 많이 발생할 수 있습니다 . 이러한 요소는 최대 두 개이며 두 개가 있으면 알고리즘이 제대로 작동합니다. 유일한 문제는 적어도 절반의 시간 동안 발생하는 요소가 하나 일 때입니다. 우리는 요소를 찾고 먼저 처리함으로써 간단히이 문제를 해결할 수 있습니다.

나는 이것을 제대로 작성하기에 파이썬에 대해 충분히 알지 못하기 때문에 github에서 이전 버전의 OP 구현을 복사 할 자유를 얻었습니다.

# Sort the list
a = sorted(lst)

# Put the element occurring more than half of the times in front (if needed)
n = len(a)
m = (n + 1) // 2
for i in range(n - m + 1):
    if a[i] == a[i + m - 1]:
        a = a[i:] + a[:i]
        break

result = [None] * n

# Loop over the first half of the sorted list and fill all even indices of the result list
for i, elt in enumerate(a[:m]):
    result[2*i] = elt

# Loop over the second half of the sorted list and fill all odd indices of the result list
for i, elt in enumerate(a[m:]):
    result[2*i+1] = elt

return result

내 이해에서 이것은 @jojo 가하는 일 입니다. 항상 최적 아닙니다.
georg

10
0 기반 또는 1 기반 인덱스를 사용하는지 여부에 따라 [0, 1, 1]또는 [0, 0, 1]에 대해 실패합니다 .
flornquake 2013-08-13

@georg 실제로 이것은 내 대답과 동일한 접근 방식입니다. (Heuster가 나보다 먼저 대답했습니다!). 그러나 내 코드에서는 2 단계와 3 단계가 결합되어 효율성이 최적화됩니다.
jojo 2013-08-13

3
트윗 담아 가기 내가 두려워하는 것은 오래된 일회성 오류입니다. 따라서이 접근 방식은 하나의 충돌이 너무 많을 수 있으므로 최적이 아닙니다.
Vincent van der Weele 2014-08-13

1
@Heuster : 모든 조명이 녹색입니다! "0 개의 결함".
georg 2014-08-14

10

이전 항목이 아닌 가장 일반적인 항목을 남겨 두는 이미 주어진 알고리즘은 정확합니다. 다음은 가장 일반적인 것을 추적하기 위해 힙을 최적으로 사용하는 간단한 구현입니다.

import collections, heapq
def nonadjacent(keys):
    heap = [(-count, key) for key, count in collections.Counter(a).items()]
    heapq.heapify(heap)
    count, key = 0, None
    while heap:
        count, key = heapq.heapreplace(heap, (count, key)) if count else heapq.heappop(heap)
        yield key
        count += 1
    for index in xrange(-count):
        yield key

>>> a = [1,2,3,3,2,2,1]
>>> list(nonadjacent(a))
[2, 1, 2, 3, 1, 2, 3]

Python에서 알고리즘을 작성하지 않는 방법에 대한 좋은 예입니다. 간단하지만 구문을 요약하는 데 30 분 정도 걸립니다.
alex904

8

사용자가 생성 할 수있는 모든에게 재귀 되돌아 알고리즘을 사용하여 (인접하는 위치에 두 개의 동일 요소가 존재하지 않는)를 '완벽한 정렬되지 않은'순열. 실제로 모든 순열을 생성하는 유일한 차이점은 마지막 숫자를 추적하고 그에 따라 일부 솔루션을 제외한다는 것입니다.

def unsort(lst, last=None):
    if lst:
        for i, e in enumerate(lst):
            if e != last:
                for perm in unsort(lst[:i] + lst[i+1:], e):
                    yield [e] + perm
    else:
        yield []

이 형식의 함수는 많은 하위 목록을 생성하므로 그다지 효율적이지 않습니다. 또한 가장 제한적인 숫자 (가장 높은 숫자)를 먼저 살펴봄으로써 속도를 높일 수 있습니다. counts숫자 만 사용하는 훨씬 더 효율적인 버전 이 있습니다.

def unsort_generator(lst, sort=False):
    counts = collections.Counter(lst)
    def unsort_inner(remaining, last=None):
        if remaining > 0:
            # most-constrained first, or sorted for pretty-printing?
            items = sorted(counts.items()) if sort else counts.most_common()
            for n, c in items:
                if n != last and c > 0:
                    counts[n] -= 1   # update counts
                    for perm in unsort_inner(remaining - 1, n):
                        yield [n] + perm
                    counts[n] += 1   # revert counts
        else:
            yield []
    return unsort_inner(len(lst))

이를 사용하여 next완벽한 순열 을 생성 하거나 list모두 보유 할 수 있습니다. 그러나 완벽하게 정렬되지 않은 순열 이 없는 경우이 생성기는 결과적으로 결과를 생성 하지 않습니다 .

>>> lst = [1,2,3,3,2,2,1]
>>> next(unsort_generator(lst))
[2, 1, 2, 3, 1, 2, 3]
>>> list(unsort_generator(lst, sort=True))
[[1, 2, 1, 2, 3, 2, 3], 
 ... 36 more ...
 [3, 2, 3, 2, 1, 2, 1]]
>>> next(unsort_generator([1,1,1]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

이 문제를 피하기 위해 다른 답변에서 제안 된 알고리즘 중 하나와 함께 이것을 대체로 사용할 수 있습니다. 이렇게하면 완벽하게 정렬되지 않은 순열이 있으면 반환하고 그렇지 않으면 좋은 근사값을 반환합니다.

def unsort_safe(lst):
    try:
        return next(unsort_generator(lst))
    except StopIteration:
        return unsort_fallback(lst)

이것은 재귀 호출을 위해 목록을 복사하는 순열의 각 요소에 대해 O (N ^ 2) 메모리를 사용합니다. 또한 재귀 적이므로 "작은"길이로 실패합니다.
Bakuriu 2014-08-13

@Bakuriu 동의합니다. "효율성에 최적화되지 않았습니다"라는 뜻입니다 ... 내가 O (n ^ 2) 공간을 제외하지 않았다는 것을 인정해야하지만, 당신 말이 맞습니다 ... 개선하려고 노력 할게요 .
tobias_k 2014-08-13

O (N ^ 2)는 T(n+1) = something + T(n). 와 같은 부활이있을 때 항상 뒤에 있습니다.
Bakuriu

@tobias_k : 테스트를 위해 하나의 perm에 대한 함수를 게시 할 수 있습니까?
georg

@georg 물론입니다 : next(unsort2(collections.Counter(a)));-)하지만이 알고리즘은 모든 가능성을 생성하므로 모두 확인하지 않는 이유는 무엇입니까? 7 개 요소 테스트 목록에 대한 유일한 38 개입니다.
tobias_k 2013-08-13

5

파이썬에서는 다음을 수행 할 수 있습니다.

정렬 된 목록이 있다고 생각하면 l다음을 수행 할 수 있습니다.

length = len(l)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
    my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

이는 제자리에있는 작업이므로 다소 빠릅니다 ( O(N)). 에서 l[i] == l[i+1]로 이동 l[i] == l[i+2]하므로 결국 순서는 무작위가 아니지만 내가 질문을 이해하는 방식에서 찾고있는 무작위성이 아닙니다.

아이디어는 정렬 된 목록을 중간에 분할 한 다음 두 부분의 다른 모든 요소를 ​​교환하는 것입니다.

이를 l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]위해l = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

이 메서드 l[i] == l[i + 1]는 한 요소의 풍부도가 목록 길이의 절반보다 크거나 같으면 모든 요소를 제거하지 못합니다 .

위의 내용은 가장 빈번한 요소가 목록 크기의 절반보다 작은 한 잘 작동하지만 다음 함수는 다른 모든 요소가 첫 번째는 가장 풍부한 것이어야합니다.

def no_adjacent(my_list):
    my_list.sort()
    length = len(my_list)
    odd_ind = length%2
    odd_half = (length - odd_ind)/2
    for i in range(odd_half)[::2]:
        my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

    #this is just for the limit case where the abundance of the most frequent is half of the list length
    if max([my_list.count(val) for val in set(my_list)]) + 1 - odd_ind > odd_half:
        max_val = my_list[0]
        max_count = my_list.count(max_val)
        for val in set(my_list):
            if my_list.count(val) > max_count:
               max_val = val
               max_count = my_list.count(max_val)
        while max_val in my_list:
            my_list.remove(max_val)
        out = [max_val]
        max_count -= 1
        for val in my_list:
            out.append(val)
            if max_count:
                out.append(max_val)
                max_count -= 1
        if max_count:
            print 'this is not working'
            return my_list
            #raise Exception('not possible')
        return out
    else:
        return my_list

감사! 이것은 실패 [3, 2, 1, 2, 1, 3, 2](반환 [2, 1, 3, 1, 2, 2, 3]이어야한다 (3, 2, 1, 2, 1, 3, 2)) - 요점 참조
게오르그

@georg 죄송합니다 +1. 지금 다시 시도하십시오.
jojo 2013-08-13

여전히 문제 [1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
게오르그

내가 지적했듯이 @georg는이 예에서는 그렇지 않은 목록 길이의 절반 미만으로 가장 풍부한 것이 존재하는 한 작동합니다.
jojo 2013-08-13

@georg 그래서 하나씩 오류를 처리하는 부분을 추가했습니다. 이 부분은 매우 빠르지는 않지만 (Thijser가 제안한 알고리즘과 거의 같음) 드문 경우에만 실행됩니다.
jojo

5

다음은 좋은 알고리즘입니다.

  1. 우선 얼마나 자주 발생하는지 모든 숫자를 계산합니다. 지도에 답을 넣으십시오.

  2. 가장 자주 발생하는 숫자가 먼저 오도록이지도를 정렬합니다.

  3. 답변의 첫 번째 숫자는 정렬 된지도의 첫 번째 숫자입니다.

  4. 첫 번째가 이제 하나 더 작아 지도록지도를 선택합니다.

효율성을 높이려면 정렬 단계의 효율성을 높이는 방법을 찾으십시오.


예, 이것이 @tobias_k가 한 일입니다. 잘 작동하는 것 같습니다!
georg

@georg 조금 다릅니다. 카운터는 공간의 복잡성을 줄이기 위해서만 사용하지만 특정 순서로 숫자를 테스트하지는 않습니다 (이것이 또 다른 속도 향상 일 수 있다고 생각 함). 어떤 다른 것은 내 솔루션은 항상 모든 '완벽한'순열을 얻을 수 있다는 것입니다 (있는 경우) 이 가장 좋은 (?) 솔루션 (완벽한 여부를) 양보해야하지만,.
tobias_k

3
이 의사 코드는 옳지 않습니다. 항목 개수가 5 x, 2 y, 2 z이면 불필요하게 x를 합칩니다. 내 대답 을 참조하십시오 .
David Eisenstat

1
동의합니다. 예를 들어 [1,1,1,2,3]의 경우 [1,2,1,3,1] 대신 [1,1,2,1,3]이 생성됩니다.
tobias_k

3 단계는 실제로 비생산적입니다. 숫자가 일반적인 경우 (다음으로 가장 빈번한 숫자보다 두 개 이상 더 많은 항목) 3 단계에서는 이유없이 해당 숫자를 연속으로 두 번 사용합니다.
MSalters

5

보너스 질문에 대한 대답 : 이것은 인접한 요소가 동일 할 수없는 집합의 모든 순열을 찾는 알고리즘입니다. 나는 이것이 개념적으로 가장 효율적인 알고리즘이라고 믿습니다 (다른 사람들은 더 간단한 코드로 번역되기 때문에 실제로 더 빠를 수 있습니다). 무차별 대입을 사용하지 않고 고유 한 순열 만 생성하며 솔루션으로 연결되지 않는 경로는 가장 빠른 시점에서 차단됩니다.

다른 모든 요소를 ​​결합한 것보다 더 자주 발생하는 집합의 요소에 대해 "풍부한 요소"라는 용어를 사용하고 풍부한 요소의 수에서 다른 요소의 수를 뺀 값에 대해 "풍부함"이라는 용어를 사용합니다.
예를 들어, 세트는 abac더 풍부한 요소 세트가없는 abaca그리고 aabcaaa풍부한 원소로서 및 풍부도 1 및도 2에 각각.

  1. 다음과 같은 세트로 시작하십시오.

aaabbcd

  1. 반복에서 첫 번째 발생을 분리하십시오.

첫 번째 : abcd
반복 : aab

  1. 반복에서 풍부한 요소 (있는 경우)를 찾고 풍부도를 계산합니다.

풍부한 요소 :
풍부 : 1

  1. 풍부한 요소 뒤의 요소 수가 풍부보다 적지 않은 첫 번째 순열을 모두 생성합니다. (예에서 "a"는 마지막이 될 수 없음)

abcd, abdc, acbd, acdb, adbc, adcb, bacd, badc, bcad, bcda , bdac, bdca ,
cabd, cadb, cbad, cbda , cdab, cdba , dabc, dacb, abac, dbca , dcab, dcba

  1. 각 순열에 대해 다음 규칙에 따라 반복되는 문자 집합을 하나씩 삽입합니다.

5.1. 집합의 풍부도가 지금까지 순열에서 풍부한 요소가 마지막으로 발생한 이후의 요소 수보다 크면 다음 순열로 건너 뜁니다.
예를 들어 지금까지 순열이 abc이면 풍부한 요소 a가 있는 집합 은 풍부도가 2 이하인 경우에만 삽입 할 수 있으므로 aaaabc괜찮습니다 aaaaabc.

5.2. 순열의 마지막 항목이 먼저 오는 집합에서 요소를 선택합니다.
예를 들어, 순열 지금까지 때 abcba와 세트 인 ab선택b

5.3. 순열에서 마지막 항목의 오른쪽에 선택한 요소를 최소 2 개 위치에 삽입합니다.
예를 들어 b순열에 삽입 할 때 babca결과는 다음 babcba과 같습니다.babcab

5.4. 각 결과 순열과 나머지 세트로 5 단계를 반복합니다.

EXAMPLE:
set = abcaba
firsts = abc
repeats = aab

perm3  set    select perm4  set    select perm5  set    select perm6

abc    aab    a      abac   ab     b      ababc  a      a      ababac  
                                                               ababca  
                                          abacb  a      a      abacab  
                                                               abacba  
                     abca   ab     b      abcba  a      -
                                          abcab  a      a      abcaba  
acb    aab    a      acab   ab     a      acaba  b      b      acabab  
                     acba   ab     b      acbab  a      a      acbaba  
bac    aab    b      babc   aa     a      babac  a      a      babaca  
                                          babca  a      -
                     bacb   aa     a      bacab  a      a      bacaba  
                                          bacba  a      -  
bca    aab    -
cab    aab    a      caba   ab     b      cabab  a      a      cababa  
cba    aab    -

이 알고리즘은 고유 한 순열을 생성합니다. 순열의 총 수 ( abaa를 전환 할 수 있기 때문에 두 번 계산 됨) 를 알고 싶다면 고유 한 순열의 수를 인자로 곱하십시오.

F = N 1 ! * N 2 ! * ... * N n !

여기서 N은 집합에있는 각 요소의 발생 횟수입니다. 세트의 경우 abcdabcaba이것은 4입니다! * 삼! * 2! * 1! 또는 288은 고유 한 순열 만이 아닌 모든 순열을 생성하는 알고리즘이 얼마나 비효율적인지 보여줍니다. 이 경우 모든 순열을 나열하려면 고유 순열을 288 번 나열하세요. :-)

아래는 자바 스크립트의 (다소 서투른) 구현입니다. 나는 파이썬과 같은 언어가 이런 종류의 것에 더 적합 할 것이라고 생각합니다. 코드 조각을 실행하여 "abracadabra"의 분리 된 순열을 계산합니다.

// FIND ALL PERMUTATONS OF A SET WHERE NO ADJACENT ELEMENTS ARE IDENTICAL
function seperatedPermutations(set) {
    var unique = 0, factor = 1, firsts = [], repeats = [], abund;

    seperateRepeats(set);
    abund = abundance(repeats);
    permutateFirsts([], firsts);
    alert("Permutations of [" + set + "]\ntotal: " + (unique * factor) + ", unique: " + unique);

    // SEPERATE REPEATED CHARACTERS AND CALCULATE TOTAL/UNIQUE RATIO
    function seperateRepeats(set) {
        for (var i = 0; i < set.length; i++) {
            var first, elem = set[i];
            if (firsts.indexOf(elem) == -1) firsts.push(elem)
            else if ((first = repeats.indexOf(elem)) == -1) {
                repeats.push(elem);
                factor *= 2;
            } else {
                repeats.splice(first, 0, elem);
                factor *= repeats.lastIndexOf(elem) - first + 2;
            }
        }
    }

    // FIND ALL PERMUTATIONS OF THE FIRSTS USING RECURSION
    function permutateFirsts(perm, set) {
        if (set.length > 0) {
            for (var i = 0; i < set.length; i++) {
                var s = set.slice();
                var e = s.splice(i, 1);
                if (e[0] == abund.elem && s.length < abund.num) continue;
                permutateFirsts(perm.concat(e), s, abund);
            }
        }
        else if (repeats.length > 0) {
            insertRepeats(perm, repeats);
        }
        else {
            document.write(perm + "<BR>");
            ++unique;
        }
    }

    // INSERT REPEATS INTO THE PERMUTATIONS USING RECURSION
    function insertRepeats(perm, set) {
        var abund = abundance(set);
        if (perm.length - perm.lastIndexOf(abund.elem) > abund.num) {
            var sel = selectElement(perm, set);
            var s = set.slice();
            var elem = s.splice(sel, 1)[0];
            for (var i = perm.lastIndexOf(elem) + 2; i <= perm.length; i++) {
                var p = perm.slice();
                p.splice(i, 0, elem);
                if (set.length == 1) {
                    document.write(p + "<BR>");
                    ++unique;
                } else {
                    insertRepeats(p, s);
                }
            }
        }
    }

    // SELECT THE ELEMENT FROM THE SET WHOSE LAST OCCURANCE IN THE PERMUTATION COMES FIRST
    function selectElement(perm, set) {
        var sel, pos, min = perm.length;
        for (var i = 0; i < set.length; i++) {
            pos = perm.lastIndexOf(set[i]);
            if (pos < min) {
                min = pos;
                sel = i;
            }
        }
        return(sel);
    }

    // FIND ABUNDANT ELEMENT AND ABUNDANCE NUMBER
    function abundance(set) {
        if (set.length == 0) return ({elem: null, num: 0});
        var elem = set[0], max = 1, num = 1;
        for (var i = 1; i < set.length; i++) {
            if (set[i] != set[i - 1]) num = 1
            else if (++num > max) {
                max = num;
                elem = set[i];
            }
        }
        return ({elem: elem, num: 2 * max - set.length});
    }
}

seperatedPermutations(["a","b","r","a","c","a","d","a","b","r","a"]);


1
감사합니다! 이것이 자바 스크립트에서 약간 짧아 질 수 있는지 확인합니다.
stt106

4

아이디어는 가장 일반적인 것부터 가장 적은 것까지 요소를 정렬하고, 가장 일반적인 것을 취하고, 개수를 줄이고, 내림차순을 유지하면서 목록에 다시 넣는 것입니다. .

이는 Counterbisect다음을 사용하여 구현할 수 있습니다 .

from collections import Counter
from bisect import bisect

def unsorted(lst):
    # use elements (-count, item) so bisect will put biggest counts first
    items = [(-count, item) for item, count in Counter(lst).most_common()]
    result = []

    while items:
        count, item = items.pop(0)
        result.append(item)
        if count != -1:
            element = (count + 1, item)
            index = bisect(items, element)
            # prevent insertion in position 0 if there are other items
            items.insert(index or (1 if items else 0), element)

    return result

>>> print unsorted([1, 1, 1, 2, 3, 3, 2, 2, 1])
[1, 2, 1, 2, 1, 3, 1, 2, 3]

>>> print unsorted([1, 2, 3, 2, 3, 2, 2])
[2, 3, 2, 1, 2, 3, 2]

예를 들어 다음 [1, 1, 2, 3]과 같은 경우 실패합니다 [1, 2, 1, 3].
Bakuriu

예, 방금 깨달았습니다. 죄송합니다
enrico.bacis 2014-08-13

감사! 이것이 항상 최적의 결과를 생성하는 것은 아닙니다. 예를 들어 (1 개의 결함)을 [1, 2, 3, 2, 3, 2, 2]반환 [2, 3, 1, 2, 3, 2, 2]하는 반면 이상은 (2, 1, 2, 3, 2, 3, 2))입니다. 요점을 참조하십시오.
georg

@georg 사실, 좋은 캐치, 사용하는 간단한 원칙을 유지하면서 업데이트했습니다.
enrico.bacis 2014-08-20

@ enrico.bacis : 감사합니다! 새 버전은 완벽하게 작동합니다. 요점을 업데이트했습니다. 안타깝지만 더 이상 찬성 할 수 없습니다.
georg

2
  1. 목록을 정렬하십시오.
  2. 이 알고리즘을 사용하여 목록의 "최상의 셔플"생성

목록의 최소 항목을 원래 위치 (항목 값 기준)에 제공하므로 예를 들어 정렬 된 위치에서 1, 2, 3을 멀리 놓으려고합니다.


나는 시도 best_shuffle하고 생성했습니다 [1,1,1,2,3] -> [3, 1, 2, 1, 1]-이상적이지 않습니다!
georg

2

정렬 된 길이 n 목록으로 시작합니다. m = n / 2로합시다. 0, m, 1, m + 1, 2, m + 2 등의 값을 가져옵니다. 동일한 숫자의 절반 이상을 가지지 않는 한 연속적인 순서로 동등한 값을 얻을 수 없습니다.


아이디어 주셔서 감사합니다. 나는 이것이 @Heuster가 구현 한 것이라고 생각합니다.
georg

2

제 "나도"스타일의 대답을 용서해주세요.하지만 Coady의 대답은 이것 으로 단순화 될 수 없었 나요?

from collections import Counter
from heapq import heapify, heappop, heapreplace
from itertools import repeat

def srgerg(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        yield val
    yield from repeat(val, -freq)

편집 : 다음은 목록을 반환하는 파이썬 2 버전입니다.

def srgergpy2(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    result = list()
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        result.append(val)
    result.extend(repeat(val, -freq))
    return result

예, 이것은 잘 작동하는 것 같습니다 (제가 py2에 있고 함수가 목록을 반환해야한다는 점 제외).
georg

@georg 좋아, 목록을 반환하는 파이썬 2 버전을 추가했습니다.
srgerg

2
  1. 각 값이 나타나는 횟수를 계산합니다.
  2. 가장 빈번한 것부터 가장 적은 빈도로 값을 선택하십시오.
  3. 선택한 값을 최종 출력에 추가하여 매번 색인을 2 씩 증가시킵니다.
  4. 인덱스가 경계를 벗어난 경우 인덱스를 1로 재설정
from heapq import heapify, heappop
def distribute(values):
    counts = defaultdict(int)
    for value in values:
        counts[value] += 1
    counts = [(-count, key) for key, count in counts.iteritems()]
    heapify(counts)
    index = 0
    length = len(values)
    distributed = [None] * length
    while counts:
        count, value = heappop(counts)
        for _ in xrange(-count):
            distributed[index] = value
            index = index + 2 if index + 2 < length else 1
    return distributed
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.