Tạo tất cả các hoán vị của một danh sách không có các phần tử bằng nhau liền kề


87

Khi chúng tôi sắp xếp một danh sách, như

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

các phần tử bằng nhau luôn nằm liền kề trong danh sách kết quả.

Làm thế nào tôi có thể đạt được nhiệm vụ ngược lại - xáo trộn danh sách để các phần tử bằng nhau không bao giờ (hoặc hiếm khi càng tốt) liền kề?

Ví dụ, đối với danh sách trên, một trong những giải pháp khả thi là

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

Chính thức hơn, đã cho một danh sách a, tạo một hoán vị pcủa nó để giảm thiểu số cặp p[i]==p[i+1].

Vì danh sách lớn, việc tạo và lọc tất cả các hoán vị không phải là một tùy chọn.

Câu hỏi bổ sung: làm thế nào để tạo ra tất cả các hoán vị như vậy một cách hiệu quả?

Đây là mã tôi đang sử dụng để kiểm tra các giải pháp: https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD: Chọn một người chiến thắng ở đây là một lựa chọn khó khăn, vì nhiều người đã đăng câu trả lời xuất sắc. @VincentvanderWeele , @ David Eisenstat , @Coady , @ enrico.bacis@srgerg cung cấp chức năng tạo ra các hoán vị tốt nhất có thể hoàn hảo. @tobias_k và David cũng đã trả lời câu hỏi thưởng (tạo tất cả các hoán vị). Các điểm bổ sung cho David để chứng minh tính đúng đắn.

Mã từ @VincentvanderWeele có vẻ là nhanh nhất.


1
Vậy bạn chỉ quan tâm đến sự bình đẳng ? một cái gì đó giống [1, 2, 1, 3, 1, 4, 1, 5]hệt như [1, 3, 1, 2, 1, 4, 1, 5]tiêu chí của bạn?
Bakuriu

1
Không thể có một thuật toán "hiệu quả". Lấy một danh sách như [1, 1, 1, ..., 2, 3, 4, ..., N]với 2Ncác phần tử. Bạn có thể đặt một số n > 1giữa mỗi cặp liên tiếp 1để thu được một hoán vị tốt. Sau đó, bạn hoán vị các N/2phần tử và nhận được tất cả các hoán vị hợp lệ (nghĩa là không có hoán vị nào là xấu, nhưng có thể có nhiều hơn). Số hoán vị như vậy là O (N ^ 2), vì vậy bạn không thể làm tốt hơn O (N ^ 2). Tuy nhiên, vẫn tốt hơn O (N ^ 3) của cách tiếp cận ngây thơ.
Bakuriu

6
@Bakuriu: Hai điều: (1) phải rõ ràng, ví dụ của bạn cho thấy rằng không thể có thuật toán hiệu quả cho câu hỏi tiền thưởng . (2) Việc liệt kê tất cả các giải pháp tối ưu cho ví dụ của bạn là O ((N / 2)!),
Kém

11
@msw: Tôi đang tạo một trang web và có một hàng chứa các khối quảng cáo từ các nhà cung cấp khác nhau. Tôi muốn sắp xếp chúng để không có khối nào từ cùng một nhà cung cấp đứng cạnh nhau.
georg

2
Tôi sẽ không nói rằng đây là "thậm chí không gần với một bản sao", nhưng bản sao bị cáo buộc là một câu hỏi khác, vì khoảng cách giữa các phần tử giống hệt nhau được xem xét. Những người đã bình chọn đóng cửa sau bình luận của WhyCry: hãy chú ý hơn trong tương lai.
David Eisenstat

Câu trả lời:


30

Đây là dòng mã giả hiện chưa hoàn thiện của Thijser. Ý tưởng là sử dụng thường xuyên nhất các loại mặt hàng còn lại trừ khi nó mới được lấy. (Xem thêm việc triển khai thuật toán này của 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

Bằng chứng về tính đúng đắn

Đối với hai loại vật phẩm, với số đếm k1 và k2, giải pháp tối ưu có k2 - k1 - 1 khuyết tật nếu k1 <k2, 0 khuyết tật nếu k1 = k2 và k1 - k2 - 1 khuyết tật nếu k1> k2. Trường hợp = là hiển nhiên. Những cái khác là đối xứng; mỗi trường hợp của phần tử thiểu số ngăn chặn tối đa hai khuyết tật trong tổng số k1 + k2 - 1 có thể.

Thuật toán tham lam này trả về các giải pháp tối ưu, theo logic sau. Chúng tôi gọi tiền tố (giải pháp từng phần) là an toàn nếu nó mở rộng thành giải pháp tối ưu. Rõ ràng tiền tố trống là an toàn và nếu tiền tố an toàn là một giải pháp toàn diện thì giải pháp đó là tối ưu. Nó đủ để thể hiện một cách cảm tính rằng mỗi bước tham lam duy trì sự an toàn.

Cách duy nhất để bước tham lam đưa ra một khiếm khuyết là nếu chỉ còn lại một loại mặt hàng, trong trường hợp này chỉ có một cách để tiếp tục và cách đó là an toàn. Nếu không, hãy đặt P là tiền tố (an toàn) ngay trước bước đang xem xét, đặt P 'là tiền tố ngay sau và đặt S là một giải pháp tối ưu mở rộng P. Nếu S cũng mở rộng P', thì chúng ta đã hoàn thành. Ngược lại, đặt P '= Px và S = PQ và Q = yQ', trong đó x và y là các mục và Q và Q 'là các chuỗi.

Giả sử đầu tiên rằng P không kết thúc bằng y. Theo lựa chọn của thuật toán, x ít nhất là thường xuyên trong Q bằng y. Xét các chuỗi con cực đại của Q chỉ chứa x và y. Nếu chuỗi con đầu tiên có ít nhất bao nhiêu số x bằng số y, thì nó có thể được viết lại mà không có thêm các khuyết tật bắt đầu bằng x. Nếu chuỗi con đầu tiên có nhiều y hơn x, thì một số chuỗi con khác có nhiều x hơn y và chúng ta có thể viết lại các chuỗi con này mà không có thêm khuyết tật để x đi trước. Trong cả hai trường hợp, chúng tôi tìm thấy một giải pháp tối ưu T kéo dài P ', nếu cần.

Giả sử bây giờ P kết thúc bằng y. Điều chỉnh Q bằng cách chuyển vị trí xuất hiện đầu tiên của x lên phía trước. Khi làm như vậy, chúng tôi giới thiệu nhiều nhất một khuyết tật (trước đây là x) và loại bỏ một khuyết tật (yy).

Tạo tất cả các giải pháp

Đây là câu trả lời của tobias_k cộng với các bài kiểm tra hiệu quả để phát hiện khi nào lựa chọn đang được xem xét bị hạn chế toàn cầu theo một cách nào đó. Thời gian chạy tiệm cận là tối ưu, vì chi phí phát sinh theo thứ tự độ dài của đầu ra. Không may là độ trễ trong trường hợp xấu nhất là bậc hai; nó có thể được giảm xuống tuyến tính (tối ưu) với cấu trúc dữ liệu tốt hơn.

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

Mã giả:

  1. Sắp xếp danh sách
  2. Lặp lại nửa đầu của danh sách đã sắp xếp và điền vào tất cả các chỉ số chẵn của danh sách kết quả
  3. Lặp lại nửa sau của danh sách đã sắp xếp và điền vào tất cả các chỉ số lẻ của danh sách kết quả

Bạn sẽ chỉ có p[i]==p[i+1]nếu hơn một nửa đầu vào chứa cùng một phần tử, trong trường hợp đó không có lựa chọn nào khác ngoài việc đặt cùng một phần tử vào các điểm liên tiếp (theo nguyên tắc lỗ pidgeon).


Như đã chỉ ra trong các nhận xét, cách tiếp cận này có thể có một xung đột quá nhiều trong trường hợp một trong các yếu tố xảy ra ít nhất n/2lần (hoặc n/2+1đối với số lẻ n; điều này tổng quát (n+1)/2)cho cả chẵn và lẻ). Có nhiều nhất hai yếu tố như vậy và nếu có hai, thuật toán hoạt động tốt. Trường hợp có vấn đề duy nhất là khi có một phần tử xảy ra ít nhất một nửa thời gian. Chúng ta có thể giải quyết vấn đề này một cách đơn giản bằng cách tìm ra nguyên tố và xử lý nó trước.

Tôi không biết đủ về python để viết điều này đúng cách, vì vậy tôi đã tự do sao chép việc triển khai OP của phiên bản trước từ github:

# 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

Theo hiểu biết của tôi, đây là những gì @jojo làm - không phải lúc nào cũng tối ưu.
georg

10
Điều này không thành công cho [0, 1, 1]hoặc [0, 0, 1], tùy thuộc vào việc bạn sử dụng chỉ số dựa trên 0 hay dựa trên 1.
flornquake 13:14

@georg Quả thực đây là cách tiếp cận giống như trong câu trả lời của tôi. (Lưu ý rằng Heuster đã trả lời trước tôi!). Tuy nhiên, trong mã của tôi, các bước 2. và 3. được kết hợp, do đó tối ưu hóa hiệu quả.
jojo

3
@flornquake Bắt tốt! Tôi e rằng đó là lỗi cũ từng chút một. Vì vậy, cách tiếp cận này không phải là tối ưu, vì nó có thể có quá nhiều xung đột.
Vincent van der Weele

1
@Heuster: tất cả các đèn đều xanh! "0 lỗi".
georg

10

Thuật toán đã đưa ra để lấy mục phổ biến nhất còn lại mà không phải là mục trước đó là chính xác. Đây là một cách triển khai đơn giản, sử dụng một cách tối ưu một đống để theo dõi những điểm chung nhất.

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]

Ví dụ tốt về cách KHÔNG viết thuật toán bằng Python. Nó đơn giản nhưng cần 30 phút chỉ để đọc cú pháp.
alex904

8

Bạn có thể tạo tất cả các hoán vị 'không được sắp xếp hoàn hảo' (không có hai phần tử bằng nhau ở các vị trí liền kề) bằng cách sử dụng thuật toán quay lui đệ quy. Trên thực tế, sự khác biệt duy nhất để tạo ra tất cả các hoán vị là bạn theo dõi số cuối cùng và loại trừ một số giải pháp cho phù hợp:

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 []

Lưu ý rằng ở dạng này, hàm không hiệu quả lắm, vì nó tạo ra rất nhiều danh sách con. Ngoài ra, chúng ta có thể tăng tốc độ bằng cách xem các số bị hạn chế nhiều nhất trước (những số có số lượng cao nhất). Đây là một phiên bản hiệu quả hơn nhiều chỉ sử dụng countscác con số.

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))

Bạn có thể sử dụng điều này để chỉ tạo nexthoán vị hoàn hảo hoặc listgiữ tất cả chúng. Nhưng lưu ý rằng nếu không có hoán vị hoàn toàn không được sắp xếp, thì do đó trình tạo này sẽ không mang lại kết quả.

>>> 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

Để giải quyết vấn đề này, bạn có thể sử dụng thuật toán này cùng với một trong các thuật toán được đề xuất trong các câu trả lời khác như một phương án dự phòng. Điều này sẽ đảm bảo trả về một hoán vị hoàn toàn không được sắp xếp, nếu có, hoặc một phép gần đúng tốt nếu không.

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

Điều này sử dụng bộ nhớ O (N ^ 2) ... cho mỗi phần tử trong hoán vị mà bạn đang thực hiện một bản sao của danh sách cho cuộc gọi đệ quy. Ngoài ra, là đệ quy, nó không thành công với độ dài "nhỏ".
Bakuriu

@Bakuriu Đồng ý, đó là ý của tôi với "không được tối ưu hóa cho hiệu quả" ... mặc dù tôi phải thừa nhận rằng tôi đã không ngoại trừ khoảng trống O (n ^ 2), nhưng bạn nói đúng ... Tôi sẽ cố gắng cải thiện nó .
tobias_k

O (N ^ 2) luôn ở phía sau khi bạn có một phép định hướng như T(n+1) = something + T(n).
Bakuriu

@tobias_k: bạn có thể đăng một hàm cho chỉ một ứng dụng cố định để thử nghiệm không?
georg

@georg Chắc chắn: next(unsort2(collections.Counter(a)));-) Nhưng vì thuật ngữ này tạo ra tất cả các khả năng, tại sao không kiểm tra tất cả? Chỉ 38 của nó cho danh sách kiểm tra 7 phần tử đó.
tobias_k

5

Trong python, bạn có thể làm như sau.

Hãy xem xét bạn có một danh sách được sắp xếp l, bạn có thể làm:

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]

Đây chỉ là các hoạt động tại chỗ và do đó sẽ khá nhanh ( O(N)). Lưu ý rằng bạn sẽ chuyển từ l[i] == l[i+1]sang l[i] == l[i+2]vì vậy thứ tự bạn kết thúc là bất kỳ thứ gì ngoài ngẫu nhiên, nhưng theo cách tôi hiểu câu hỏi thì đó không phải là ngẫu nhiên mà bạn đang tìm kiếm.

Ý tưởng là chia danh sách đã sắp xếp ở giữa sau đó trao đổi mọi phần tử khác trong hai phần.

l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]điều này dẫn đếnl = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

Phương pháp này không thể loại bỏ tất cả l[i] == l[i + 1]ngay khi số lượng phần tử lớn hơn hoặc bằng một nửa độ dài của danh sách.

Mặc dù phần trên hoạt động tốt miễn là mức độ phong phú của phần tử thường xuyên nhất nhỏ hơn một nửa kích thước của danh sách, hàm sau cũng xử lý các trường hợp giới hạn (vấn đề riêng lẻ nổi tiếng) trong đó mọi phần tử khác bắt đầu bằng cái đầu tiên phải là cái dồi dào nhất:

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

Cảm ơn! Điều này không thành công cho [3, 2, 1, 2, 1, 3, 2](trả lại [2, 1, 3, 1, 2, 2, 3], nên được (3, 2, 1, 2, 1, 3, 2)) - xem ý chính
georg

@georg xin lỗi, tôi xấu, tôi quên mất a +1. Hãy thử lại ngay bây giờ.
jojo

Vẫn có vấn đề, [1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
georg

@georg như tôi đã chỉ ra, nó hoạt động miễn là số lượng nhiều nhất hiện diện ít hơn một nửa độ dài của danh sách, điều này không đúng trong ví dụ này.
jojo

@georg Vì vậy, tôi đã thêm phần xử lý lỗi của từng người một. Phần này không đặc biệt nhanh (giống như thuật toán do Thijser đề xuất), mặc dù nó sẽ chỉ được chạy trong những trường hợp hiếm hoi.
jojo

5

Đây là một thuật toán tốt:

  1. Trước hết hãy đếm cho tất cả các con số tần suất chúng xuất hiện. Đặt câu trả lời vào bản đồ.

  2. sắp xếp bản đồ này để những con số xuất hiện thường xuyên nhất đứng trước.

  3. Số đầu tiên trong câu trả lời của bạn là số đầu tiên trong bản đồ được sắp xếp.

  4. Khu nghỉ dưỡng bản đồ với cái đầu tiên bây giờ là một cái nhỏ hơn.

Nếu bạn muốn nâng cao hiệu quả, hãy tìm cách tăng hiệu quả của bước phân loại.


Vâng, đây là những gì @tobias_k đã làm. Có vẻ hoạt động tốt!
georg

@georg Nó hơi khác một chút ... Tôi chỉ sử dụng bộ đếm để giảm độ phức tạp của không gian, nhưng tôi không kiểm tra các con số theo bất kỳ thứ tự cụ thể nào (nghĩ rằng đây có thể là một sự tăng tốc khác). Điều khác biệt là giải pháp của tôi luôn mang lại tất cả các hoán vị 'hoàn hảo', nếu có , trong khi điều này sẽ mang lại giải pháp tốt nhất (?) (Hoàn hảo hoặc không).
tobias_k

3
Mã giả này không đúng lắm; nếu số mục là 5 x, 2 y, 2 z, thì nó sẽ không cần thiết phải ghép x lại với nhau. Xem câu trả lời của tôi để sửa chữa.
David Eisenstat

1
Đã đồng ý. Ví dụ: [1,1,1,2,3] điều này sẽ tạo ra ví dụ: [1,1,2,1,3] thay vì [1,2,1,3,1].
tobias_k

Bước 3 thực sự phản tác dụng. Nếu một số là phổ biến (ít nhất hai mục nhập nhiều hơn số thường xuyên tiếp theo), bước 3 sẽ sử dụng số đó hai lần liên tiếp mà không cần biện minh.
MSalters

5

Trả lời cho câu hỏi bổ sung: đây là một thuật toán tìm tất cả các hoán vị của một tập hợp mà không có phần tử liền kề nào có thể giống hệt nhau. Tôi tin rằng đây là thuật toán hiệu quả nhất về mặt khái niệm (mặc dù những thuật toán khác có thể nhanh hơn trong thực tế vì chúng dịch thành mã đơn giản hơn). Nó không sử dụng bạo lực, nó chỉ tạo ra các hoán vị duy nhất và các đường dẫn không dẫn đến các giải pháp sẽ bị cắt ngay từ điểm sớm nhất.

Tôi sẽ sử dụng thuật ngữ "phần tử dồi dào" cho một phần tử trong một tập hợp xảy ra thường xuyên hơn tất cả các phần tử khác cộng lại và thuật ngữ "dồi dào" cho số phần tử dồi dào trừ đi số phần tử khác.
ví dụ: tập hợp abackhông có phần tử dồi dào, các tập hợp abacaaabcaaaphần tử dồi dào, và mức độ dư thừa 1 và 2 tương ứng.

  1. Bắt đầu với một tập hợp như:

aaabbcd

  1. Tách những lần xuất hiện đầu tiên khỏi những lần lặp lại:

lần đầu tiên: abcd
lặp lại: aab

  1. Tìm phần tử dồi dào trong các lần lặp lại, nếu có và tính toán phần tử dồi dào:

yếu tố phong phú: một
sự phong phú: 1

  1. Tạo tất cả các hoán vị đầu tiên trong đó số phần tử sau phần tử dồi dào không ít hơn số dư: (vì vậy, trong ví dụ, "a" không thể là cuối cùng)

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

  1. Đối với mỗi hoán vị, hãy chèn lần lượt tập hợp các ký tự được lặp lại, tuân theo các quy tắc sau:

5.1. Nếu mức độ phong phú của tập hợp lớn hơn số phần tử sau lần xuất hiện cuối cùng của phần tử dồi dào trong hoán vị cho đến nay, hãy chuyển sang hoán vị tiếp theo.
Ví dụ: khi hoán vị cho đến nay abc, một tập hợp có nhiều phần tử achỉ có thể được chèn vào nếu mức độ dồi dào là 2 hoặc ít hơn, như vậy aaaabclà ok, aaaaabckhông phải vậy.

5.2. Chọn phần tử từ tập hợp có lần xuất hiện cuối cùng trong hoán vị đến trước.
ví dụ: khi hoán vị cho đến nay là abcbavà đặt là ab, chọnb

5.3. Chèn phần tử đã chọn ít nhất 2 vị trí vào bên phải của lần xuất hiện cuối cùng của nó trong hoán vị.
Ví dụ: khi chèn bvào hoán vị babca, kết quả là babcbababcab

5.4. Lặp lại bước 5 với mỗi hoán vị kết quả và phần còn lại của tập hợp.

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    -

Thuật toán này tạo ra các hoán vị duy nhất. Nếu bạn muốn biết tổng số hoán vị (trong đó abađược tính hai lần vì bạn có thể chuyển các chữ a), hãy nhân số hoán vị duy nhất với một thừa số:

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

trong đó N là số lần xuất hiện của mỗi phần tử trong tập hợp. Đối với một bộ, abcdabcabađây sẽ là 4! * 3! * 2! * 1! hoặc 288, chứng tỏ một thuật toán không hiệu quả như thế nào tạo ra tất cả các hoán vị thay vì chỉ những hoán vị duy nhất. Để liệt kê tất cả các hoán vị trong trường hợp này, chỉ cần liệt kê các hoán vị duy nhất 288 lần :-)

Dưới đây là một triển khai (khá vụng về) trong Javascript; Tôi nghi ngờ rằng một ngôn ngữ như Python có thể phù hợp hơn cho loại điều này. Chạy đoạn mã để tính toán các hoán vị riêng biệt của "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
cám ơn vì cái này! sẽ xem liệu điều này có thể được rút ngắn một chút trong javascript hay không.
stt106 19/12/15

4

Ý tưởng là sắp xếp các phần tử từ phổ biến nhất đến ít phổ biến nhất, lấy phần tử phổ biến nhất, giảm số lượng của nó và đưa nó trở lại danh sách theo thứ tự giảm dần (nhưng tránh đặt phần tử được sử dụng cuối cùng trước để tránh lặp lại khi có thể) .

Điều này có thể được thực hiện bằng cách sử dụng 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

Thí dụ

>>> 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]

Điều này không thành công với, ví dụ: [1, 1, 2, 3]nơi có các giải pháp như [1, 2, 1, 3].
Bakuriu

Vâng, tôi chỉ nhận ra rằng, xin lỗi
enrico.bacis

Cảm ơn! Điều này không phải lúc nào cũng tạo ra kết quả tối ưu, ví dụ: [1, 2, 3, 2, 3, 2, 2]nó trả về [2, 3, 1, 2, 3, 2, 2](1 lỗi), trong khi lý tưởng là (2, 1, 2, 3, 2, 3, 2)) - xem ý chính.
georg

@georg Đúng, rất hay, tôi đã cập nhật nó theo nguyên tắc đơn giản mà nó sử dụng.
enrico.bacis

@ enrico.bacis: cảm ơn! Phiên bản mới hoạt động hoàn hảo. Tôi đã cập nhật ý chính. Thật tệ là tôi không thể ủng hộ bạn nữa.
georg

2
  1. Sắp xếp danh sách.
  2. Tạo "xáo trộn tốt nhất" trong danh sách bằng thuật toán này

Nó sẽ cung cấp tối thiểu các mục từ danh sách ở vị trí ban đầu của chúng (theo giá trị mục), ví dụ: nó sẽ cố gắng đặt số 1, số 2 và số 3 ra khỏi vị trí đã sắp xếp của chúng.


Tôi đã thử best_shufflevà nó tạo ra [1,1,1,2,3] -> [3, 1, 2, 1, 1]- không lý tưởng!
georg

2

Bắt đầu với danh sách được sắp xếp có độ dài n. Cho m = n / 2. Nhận các giá trị tại 0, sau đó m, sau đó 1, sau đó m + 1, sau đó 2, sau đó m + 2, v.v. Trừ khi bạn có hơn một nửa số giống nhau, bạn sẽ không bao giờ nhận được các giá trị tương đương theo thứ tự liên tiếp.


Cảm ơn vì ý tưởng. Tôi nghĩ đây là những gì @Heuster đã triển khai.
georg

2

Xin hãy tha thứ cho câu trả lời kiểu "tôi cũng vậy", nhưng câu trả lời của Coady có thể được đơn giản hóa cho điều này không?

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)

Chỉnh sửa: Đây là phiên bản python 2 trả về danh sách:

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

Có, điều này có vẻ hoạt động tốt (ngoại trừ việc tôi đang ở trên py2 và hàm sẽ trả về một danh sách).
georg

@georg Ok, tôi đã thêm phiên bản python 2 trả về danh sách.
srgerg

2
  1. Đếm số lần mỗi giá trị xuất hiện
  2. Chọn các giá trị theo thứ tự từ thường xuyên nhất đến ít thường xuyên nhất
  3. Thêm giá trị đã chọn vào đầu ra cuối cùng, tăng chỉ số lên 2 mỗi lần
  4. Đặt lại chỉ mục thành 1 nếu chỉ mục nằm ngoài giới hạn
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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.