【问题标题】:How to write a bruteforce algorithm for this assignment?如何为此作业编写蛮力算法?
【发布时间】:2020-10-23 15:27:14
【问题描述】:

我必须为高中的计算机科学课做作业。几周以来,我一直在尝试编写一个可行的算法,但我的想法已经不多了。

作业:

有 n 个学生有 n 个礼物,所有礼物都有编号。现在每个学生都必须选择他最喜欢的礼物,第二好的或第三好的。然后尽可能将礼物分发给学生。

如果第一次分发中实现的第一愿望的数量高于第二次分发中实现的第一愿望的数量,则该分发优于任何其他分发。如果两个分布的这个数字相同,则满足的第二个愿望的数量决定了哪个分布更好。如果这个数字也相等,则由实现的第三个愿望的数量决定。


我尝试使用二叉树解决这个问题,每次学生希望有相同的礼物时,程序都会模拟第一个学生“排名下降”或第二个学生“排名下降”的情况,直到你不能降级或有一个有价值的分布。之后,比较所有分布并显示最佳分布。此外,如果一个学生不能满足他的任何愿望,他就会得到一份他不想要的礼物。

但是那次尝试失败了,因为我在编写所有这些时遇到了糟糕的情况。

所以我想知道如何尝试使用蛮力算法解决这个问题,因为我无法想象那会是什么样子。

以下是我的算法必须解决的一些“愿望清单”示例(每行是学生,列是第一个愿望、第二个愿望和第三个愿望):

Example 1:

 2 10  6
 2  7  3
 4  7  1
 3  4  9
 3  7  9
 4  3  2
 7  6  2
10  2  4
 9  8  1
 4  9  6

Example 2:

 4  6  5
 5  4  6
 6  4  5
 6  4  5
 5  4  6
 4  6  5
 4  5  6
 5  4  6
 6  5  4
 4  5  6

Example 3:

 4  2 25
 6 14  7
 7 12 10
18  7 19
15  4 27
 2 18 27
 4 10 18
18  7 13
18 15 25
 7 11 13
14 15 22
13 14  6
 7  1 19
15  7 13
10 13  7
 4  8 12
15  1 26
 2  7 23
 7  4 13
 6 10  3
 1 16  7
10  7  4
 4 15 23
11  4 10
11 13 18
 4  7 12
11 10 30
12 13 10
17 12  4
15  7 25

顺便说一句,我真的很想用 python 编写它,如果你们有其他关于如何解决这个问题的想法,我将非常感激。我知道这有很多问题,但我仍然希望这里有人可以帮助我。

非常感谢!

【问题讨论】:

  • 这很棘手。如果任何礼物恰好是一个人的首选,那么那个人就会得到它。但如果一份礼物是两个或更多人的第一选择,那么你需要看看他们的第二选择。如果任何人的第二或第三选择是其他人的第一选择,您可以将这些第二或第三选择视为未指定,因为您立即知道他们不会满足。最终,您可能需要进行一些搜索,但您可以先缩小范围。
  • 它更像Assignment Problem
  • 计算机科学中最好的想法通常是重用现有的库和工具来解决这些问题。您陈述的问题是在布尔逻辑和优化领域。这些领域有很多很棒的工具,您只需要描述问题,工具会自动生成解决方案,而无需实现搜索算法。此外,这些工具效率很高,并且优于特定领域的算法。一个候选者是答案集编程(clingo 也有 Python 绑定)。
  • @tphilipp 这是学校的作业。我怀疑使用现有库是否可以接受。
  • @TomKarzes 可以使用现有的库,但前提是它们有助于整个过程,而不仅仅是问题的直接直接解决方案。也感谢大家这么快的回复!

标签: python algorithm logic


【解决方案1】:

这是一个基于答案集编程的不完整解决方案,使用 clingoHere 是 Torsten Schaub 在 2013 年约束编程国际会议上关于答案集编程的演讲。

下面的代码有以下限制

  1. 我们有三个学生
  2. 我们有三份礼物
  3. 学生只能说出他们喜欢的礼物,没有优先级,因此优化会简单一些。

请注意,您可以轻松扩展此方法以满足您的确切要求。

% there are three students
student(1..3).
% there are three gifts
gift(1..3).   

% student 1 prefers gift 2
prefer(1, 2).
% student 2 prefers gift 3
prefer(2, 3).
% student 3 prefers gift 1
prefer(3, 1).

% We assign each student a gift.
% Intuitively, the line below means the following:
% For each student S, make exactly one assign(S, G) true
% where G is a gift.
1 { assign(S, G) : gift(G) } 1 :- student(S).

% We have to ensure that a gift can be assigned to a student only once.
:- assign(S1, G), assign(S2, G), S1 != S2.

% Optimize
#maximize { 1, assign(S, G) : assign(S, G), prefer(S, G) }.

% Restrict output to assign
#show assign/2.

如果你调用 clgo,你会收到以下输出

clingo gifts.pl
clingo version 5.5.0
Reading from gifts.pl
Solving...
Answer: 1
assign(2,1) assign(3,2) assign(1,3)
Optimization: 0
Answer: 2
assign(3,1) assign(2,2) assign(1,3)
Optimization: -1
Answer: 3
assign(3,1) assign(1,2) assign(2,3)
Optimization: -3
OPTIMUM FOUND

Models       : 3
  Optimum    : yes
Optimization : -3
Calls        : 1
Time         : 0.003s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time     : 0.003s

【讨论】:

    【解决方案2】:

    我将详细描述一个约束优化解决方案,使其可行但不提供代码。

    首先Option 指定studentgiftrank。所以每个愿望清单指定 3 个Options。此外,如果studentgiftNone,则表示匹配失败。在实现它的类上有一个__hash__ 方法会很有帮助,这样它就可以在set 对象中使用。

    您将需要以下global 数据结构:

    • student_options:学生,Options 他们有什么。
    • gift_options:礼物,Options 他们有什么。
    • assignment:哪些学生得到哪些礼物。
    • score:有多少人得到了他们的第一个、第二个、第三个或没有选择的列表。从[0, 0, 0, 0] 开始。 (你会更新,所以列表是有意义的。)
    • best_assignment: 迄今为止找到的最佳作业。以{} 开头
    • best_score:目前找到的最好成绩。从(0, 0, 0, 0) 开始。 (你只替换,所以我更喜欢这里的元组。)
    • action_stack:一个数组,我会解释它的用途。

    现在,如果您使用递归,则必须不断复制这些数据结构以进行更改。这是缓慢而浪费的。因此,我将描述一种基于显式堆栈的方法。

    这个想法是有一个action_stack。每个action 将有一个do 和一个option。其中do 是一个函数。那么我们程序的主体看起来像:

    while 0 < len(action_stack):
        action = action_stack.pop()
        action.do(action.payload)
    

    Action 本身就很简单。

    class Action:
        def __init__(self, do, option):
            self.do = do
            self.option = option
    

    我们为什么要这样做?这是因为我们现在可以通过调用一个函数来做一些事情,或者我们可以选择稍后通过附加到action_stack 来做。所以我们可以编辑数据结构,并堆叠一个动作来撤消我们的编辑。

    以下是您需要按字母顺序执行的操作。

    • add_option:将选项添加到student_optionsgift_options。如有必要,创建键/值对。跳过None 值。
    • assign:将该礼物分配给该学生并适当调整分数。
    • analyze:这是“弄清楚要做出/发现什么结论”的核心。将描述更多。
    • choose:对于与该选项(包括其自身)共享学生/礼物的不同选项,将add_option 附加到action_stack,实际上删除该选项,然后将analyzeassign 附加到action_stack 到确保我们先assign,然后再analyze
    • remove_option:从student_optionsgift_options 中删除选项。如有必要,删除键/值对。
    • unassign:撤消作业,并适当调整分数。

    这里是 choose 的一个实现来展示样式。

    def choose(option):
        global action_stack
        found = set()
        for options in (student_options.get(option.student, []), gift_options(option.gift, [])):
            for o in options:
                found.add(o)
        for o in found:
            remove_option(o)
            action_stack.append(Action(add_option, o))
        action_stack.append(Action(analyze, None))
        action_stack.append(Action(assign_option, option))
    

    换句话说,我们实际上清除了与我们所做的选择相关的所有选项,确保我们将替换它们,确保我们将analyze 并确保我们将assign

    这意味着接下来我们分配。然后analyze 一堆选项消失了。然后当我们回溯堆栈时,我们将撤消我们所做的。

    好的,这给我们留下了analyze 的巨大黑匣子。它如何工作?这个想法是我们寻找强制的结论,如果我们找到它们,我们就会得出结论并再试一次。如果我们无法找到我们可以做出的最少选择并递归搜索。如果我们没有找到解决方案,请看看它是否是最好的。

    我们关注的强制性结论是,如果礼物是某人最想要的,那么该礼物永远不会送给不想要的人。 (它可能会送给其他人,但绝不会送给想要它的人。)

    在伪代码中它是这样工作的。

    remove_options = []
    for each student:
        for their most desired option:
            all lower ranked options on its gift to into remove_options
    
    if 0 < len(remove_options):
        # Note, sets are useful for calculating distinct.
        for distinct option in removed_options:
            remove_option(option)
            action_stack.append(Action(add_option(option))
        action_stack.append(Action(analyze, None))
    else:
        find smallest non-empty option group from students, gifts
        if group found:
            for option in group:
                action_stack.append(Action(choose, option))
        elif best_score < tuple(score):
            best_score = tuple(score)
            best_assignment = assignment.copy()
    

    好的,我们差不多完成了。有了所有这些机器,代码是如何工作的?思路是这样的。

    action_stack = [Action(analyze, None)]
    for wishlist_item in wishlists:
        action_stack.append(Action(add_option(option from wishlist_item)))
    for student in students:
        # This assumes ranks go from 0..2 for wanted gifts so 3 is for unwanted
        action_stack.append(Action(add_option(student, None, 3)))
    for gift in gifts:
        # This assumes ranks go from 0..2 for wanted gifts so 3 is for unwanted
        action_stack.append(Action(add_option(None, gift, 3)))
    
    while 0 < len(action_stack):
        action = action_stack.pop()
        action.do(action.option)
    
    Fix up assignment to assign unwanted presents to those who don't have presents.
    show assignment
    

    祝你好运!

    【讨论】:

      猜你喜欢
      • 2021-12-29
      • 2015-06-22
      • 1970-01-01
      • 2015-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多