Python 数据结构与算法——从二分图到寻找最大排列(Maximum Permutation)

假设现在有 8 位有着特殊癖好的人去买票看电影,其中有一部分人得到了自己喜欢的座位,但大多数人并不满意。现在的问题是,如果这些人各自都有自己喜欢的座位(喜欢的座位有重叠,这是进行最大排列的前提,否则无法进行;座位有重叠,就必然存在一些无人愿去的座位,这恰是进行有效归纳的前提),那么我们希望给出某种交换座位的方式,以求让更多的人得到满意。

这其实是匹配问题的一种,我们其实可以将该问题(实例)模型化成某种图结构,如下图所示:



这其实是二分图(bipartite graph)的一个具体示例,这意味着在该图中,节点被分成了两个集合,而所有的边线都只存在于两个集合之间(边不存在于两个集合内部)。

问题分析归纳如下:

有没有座位是没有人想座的,也即上图中下排入度为 0 的点(b 和 g)?在一个有效的解决方案(一组排列)中,一个人(元素)最多只能被安排到一个既定座位上。也即不应该有空位,如果有空位的话,等于是让两个人做同一个座位。因此,移除空座位(包括对应的人)的做法不仅是对的,而且是必须的。对待上图,我们可以直接排除 b 和 g。问题得以归纳。

现在是时候将我们的归纳/递归算法转换为实际实现了。这种时候,往往要先解决实例中各对象如何表示(也即数据结构的选择)。就本例而言,我们可能会从图结构或能够反映图像之间映射关系的某个函数开始思考。本质上说,这里的映射关系只不过是各元素(0,…, n-1)与其位置(0,…,n-1)之间的关联性,我们使用 Python 中的列表(list)即可实现。例如,对于图,如果 a, b, c, d, e, f, g, h = range(8),我们就可做如下表示:

>>> a, b, c, d, e, f, g, h = range(8)
>>> M = [c, c, a, f, d, f, h, e]
>>> M[c]
0
                # M 本质上是
                # [2, 2, 0, 5, 3, 5, 7, 4]

接下来我们即可将这个递归算法的思路实现出来,尽管其查找淘汰元素的方式有些粗暴,也必然不会多有效率,但有时效率低下的实现也是一个良好的开始

def naive_max_perm(M, A=None):
    if A is None:
        A = set(range(len(M)))
    if len(A) == 1: return A
    B = set(M[i] for i in A)
    C = A - B
    if C:
        A.remove(C.pop())
        return naive_max_perm(M, A)
    return A
>>> naive_max_perm(M)
{0, 2, 5}
            # a、c、f 保留在排列中
            # 而其他人不得不留在自己不喜欢的位置上

这里最浪费的操作无疑是集合 B 的重复创建,如果该操作纯粹只是为了跟踪那些没人选的座位的话,我们可为每个元素设置一个计数器,有关计数器的详细论述,请见 Python 数据结构与算法——引用计数

from collections import Counter
def max_perm(M):
    n = len(M)
    A = set(range(n))
    count = Counter(M)
    Q = [i for i in range(n) if count[i] == 0]
    while Q:
        i = Q.pop()
                                    # 移除座位
        A.remove(i)
        j = M[i]
                                    # M[i]:表示选择该座位的人
        count[j] -= 1
                                    # 此人对此座位的引用关系也要删除
        if count[j] == 0:
            Q.append(j)
    return A
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五道口纳什

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值