You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Python的物品分配算法求解:带约束的用户偏好匹配问题

问题描述

现有一组用户对物品的偏好排序(示例:3个用户对3个物品的偏好:User1: Item A > Item B > Item C;User2: Item B > Item A > Item C;User3: Item A > Item C > Item B),实际场景为25个用户与25个物品。需求是找到最优分配方案:整体让每个用户匹配到尽可能高排名的物品(无需最大化首选项数量),且每个物品仅能分配一次。

尝试使用NetworkX的单纯形算法时,遇到错误:networkx.exception.NetworkXUnfeasible: total node demand is not zero,附上相关代码示例。


错误原因与修复

核心问题

NetworkX的最小费用流要求所有节点的需求总和为0。你的代码中存在两个潜在问题:

  1. sink节点被重复添加到循环内部,虽然最终需求值正确,但写法不规范,可能引发意外问题;
  2. 用户到物品的边未显式设置容量,默认无限容量可能导致逻辑隐患。

修正后的代码片段

# 修正节点需求设置:sink节点仅添加一次
for p in P:
    G.add_node(p[0], demand=-1)
# 仅在循环外添加sink节点,需求为用户总数
G.add_node('sink', demand=len(P))

# 修正用户到物品的边:显式设置容量为1
for p, c in itertools.product(P, C):
    G.add_edge(p[0], c[0], weight=get_cost(p[1], c[1]), capacity=1)

同时需确保用户数量与物品数量严格相等(25=25),否则即使总需求为0,也会因物品容量不足导致无法分配。


适用算法与Python实现

你的问题属于等规模双边匹配问题,目标是最小化整体匹配代价(等价于最大化用户满意度),以下是两种高效实现方案:

1. 最小费用最大流(NetworkX实现)

通过构建流网络,用network_simplex算法求解,适合需要扩展复杂约束的场景。

完整修正代码:

import networkx as nx
import itertools
import random

# 生成25个用户的偏好数据(示例:每个用户的偏好是1-25的随机排列)
P = []
for i in range(1, 26):
    ranking = random.sample(range(1, 26), 25)
    P.append((f'User{i}', ranking))

# 物品列表:(名称, 索引, 最大分配数)
no_items = 25
C = [('s' + str(i), i, 1) for i in range(1, no_items + 1)]

# 计算匹配代价:排名越靠后,代价越高(平方惩罚)
def get_cost(ranking, index):
    return ranking.index(index) ** 2

# 构建最小费用流图
G = nx.DiGraph()

# 设置节点需求
for p in P:
    G.add_node(p[0], demand=-1)
G.add_node('sink', demand=len(P))

# 添加用户到物品的边
for p, c in itertools.product(P, C):
    G.add_edge(p[0], c[0], weight=get_cost(p[1], c[1]), capacity=1)

# 添加物品到sink的边
for c in C:
    G.add_edge(c[0], 'sink', capacity=c[2])

# 求解并输出结果
try:
    flow_cost, flow_dict = nx.network_simplex(G)
    print('总代价: ', flow_cost)
    print('\n分配方案:')
    for user in P:
        user_name = user[0]
        for item in C:
            item_name = item[0]
            if flow_dict[user_name].get(item_name, 0) == 1:
                print(f'{user_name} -> {item_name}')
except nx.NetworkXUnfeasible:
    print('无法找到可行分配方案,请检查用户与物品数量是否相等。')

2. 匈牙利算法(Scipy实现)

针对等规模双边匹配,匈牙利算法(Kuhn-Munkres)时间复杂度为O(n³),对于n=25的场景更高效直接。

代码示例:

import numpy as np
from scipy.optimize import linear_sum_assignment
import random

# 生成25个用户的偏好数据
P = []
for i in range(1, 26):
    ranking = random.sample(range(1, 26), 25)
    P.append((f'User{i}', ranking))

# 构建代价矩阵:行=用户,列=物品,值=用户对该物品的匹配代价
cost_matrix = []
for user in P:
    ranking = user[1]
    row = [ranking.index(item_idx) ** 2 for item_idx in range(1, 26)]
    cost_matrix.append(row)
cost_matrix = np.array(cost_matrix)

# 求解最小权匹配
user_indices, item_indices = linear_sum_assignment(cost_matrix)

# 输出结果
print('总代价: ', cost_matrix[user_indices, item_indices].sum())
print('\n分配方案:')
for u_idx, i_idx in zip(user_indices, item_indices):
    user_name = P[u_idx][0]
    item_name = f's{i_idx+1}'  # 对应物品名称
    print(f'{user_name} -> {item_name}')

关键说明
  • 代价函数:使用平方惩罚ranking.index(index)**2会放大低排名物品的代价,确保优先匹配用户偏好靠前的物品;若需强化首选项权重,可改用指数惩罚(如2**ranking.index(index))。
  • 可行性:只要用户数等于物品数,且每个用户可匹配任意物品(无论排名),就一定存在可行分配方案。

内容的提问来源于stack exchange,提问作者Johnpojohn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 20:15:25