在我看来,这类似于属于 NP-Hard 复杂性类别的组合优化问题,这当然意味着很难为超过 30 个用户的实例找到精确的解决方案。
如果您要为具有这样的指数搜索空间的问题找到一个可用的算法(这里的解决方案空间是所有 2^n 个用户子集),动态编程将是您想要使用的工具,但我由于缺乏重叠的子问题,在这里看不到 DP 帮助我们。也就是说,要让 DP 提供帮助,我们必须能够在多项式时间内使用较小子问题的解决方案并将其组合成一个整体解决方案,而我不知道我们该如何解决这个问题。
假设您有一个 size=k 问题的解决方案,使用有限的用户子集 {u1, u2,...uk},并且您希望在添加另一个用户 u 时使用该解决方案来找到新的解决方案(k+1)。问题是在增量更大的实例中设置的解决方案可能与之前的解决方案完全不重叠(它可能是完全不同的用户/兴趣组),因此我们无法有效地将子问题的解决方案组合起来以获得整体解决方案。如果不尝试仅使用大小 k 问题的单一最优解来推理大小 k+1 问题,而是存储来自较小实例的所有可能的用户组合及其分数,那么您当然可以很容易地进行设置将这些组的兴趣与新用户的兴趣相交,以找到新的最优解。但是,这种方法的问题当然是您必须存储的信息会随着迭代而加倍,产生的指数时间算法并不比蛮力解决方案好。如果您尝试将您的 DP 建立在逐步添加兴趣而不是用户的基础上,您会遇到类似的问题。
因此,如果您知道自己只有少数用户,则可以使用蛮力方法:生成所有用户组合,获取每个组合兴趣的集合交集,评分并保存最高分。处理较大实例的最佳方法可能是通过搜索算法使用近似解决方案(除非有我看不到的 DP 解决方案)。您可以迭代地添加/减去/交换用户以提高分数并朝着最佳方向攀升,或者使用分支定界算法系统地探索所有用户组合但停止探索任何具有零兴趣交集的用户子集分支(如添加该子集的其他用户仍将产生空交集)。您可能有很多具有零兴趣交叉点的用户组,因此后一种方法实际上可以通过修剪大部分搜索空间来相当快,如果您在没有深度限制的情况下运行它,它最终会找到确切的解决方案.
Branch-and-bound 会像这样工作:
def getLargestCluster((user, interest)[]):
userInterestDict := { user -> {set of user's interests} } # build a dict
# generate and score user clusters
users := userInterestDict.keys() # save list of users to iterate over
bestCluster, bestInterests, bestClusterScore := {}, {}, 0
generateClusterScores()
return [bestCluster, bestInterests bestClusterScore]
# (define locally in getLargestCluster or pass needed values
def generateClusterScores(i = 0, userCluster = {}, clusterInterests = {}):
curScore := userCluster.size * clusterInterests.size
if curScore > bestScore:
bestScore, bestCluster, bestInterests := curScore, curCluster, clusterInterests
if i = users.length: return
curUser := users[i]
curInterests := userInterestDict[curUser]
newClusterInterests := userCluster.size = 0 ? curInterests : setIntersection(clusterInterests, curInterests)
# generate rest subsets with and without curUser (copy userCluster if pass by reference)
generateClusterScores(i+1, userCluster, clusterInterests)
if !newClusterInterests.isEmpty(): # bound the search here
generateClusterScores(i+1, userCluster.add(curUser), newClusterInterests)
您也许可以做一个更复杂的边界(例如,如果您可以计算出当前的集群分数不能超过您当前的最佳分数,即使所有剩余的用户都已添加到集群中并且兴趣交叉点保持在同样),但检查一个空的兴趣交叉点很简单。这适用于 100 个用户,50 个兴趣点,最多约 800 个数据点。您还可以通过迭代 |interests| 的最小值来提高效率。和 |用户| (以生成更少的递归调用/组合)并仅反映兴趣较低的情况的逻辑。此外,您会获得更多有趣的集群,但用户/兴趣更少