算法 – 生成列表中没有相邻等于元素的所有排列

当我们对一个列表排序时

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]

更正式地,给定列表a,生成其使p [i] == p [i 1]对的数目最小化的置换p。

由于列表很大,生成和过滤所有排列不是一个选项。

奖金问题:如何有效地生成所有这样的排列?

这是我用来测试解决方案的代码:https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD:在这里选择一个优胜者是一个艰难的选择,因为许多人发表了很好的答案。 07007,07007,@David Eisenstat,@Coady,@enrico.bacis@srgerg提供了无缺陷地产生最佳可能置换的函数。 @tobias_k和David也回答了奖金问题(生成所有排列)。对大卫的正确性证明的补充点。

来自@Heuster的代码似乎是最快的。

最佳答案
这是沿着Thijser当前不完整的伪代码的行。这个想法是取最常用的剩余项目类型,除非它只是被采取。 (参见本算法的Coady’s implementation。)

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 tobias_k’s answer加上高效的测试来检测当前正在考虑的选择在某种程度上受到全局约束。渐近运行时间是最优的,因为生成的开销是输出长度的数量级。不幸的是,最坏情况的延迟是二次的;它可以被简化为具有更好数据结构的线性(最优)。

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

转载注明原文:算法 – 生成列表中没有相邻等于元素的所有排列 - 代码日志