【问题标题】:Algorithm problem: select two stories per topic so that the same story is never selected for two different topics算法问题:每个主题选择两个故事,这样就不会为两个不同的主题选择同一个故事
【发布时间】:2011-06-15 21:08:55
【问题描述】:

在我的工作场所,我偶然发现了以下要求我解决的问题。解决方案是首选,但不是绝对必需的。

有一个包含一组故事的数据库,每个故事都有一组与之关联的主题。主题存储在表单(storyid、topicid)的单独表格中。

问题是如何选择理想的 5 个主题(或至少 2 个,如果 5 个不可能),使得每个主题有 2 个故事(或 1 个,如果 2 个不可能),在任何其他选定主题中都不会重复.该算法还必须返回与每个主题相关的“正确”故事。

这实际上是一个 NP 完全问题,它没有超出所有可能性的简单枚举的有效解决方案,还是有一个有效的解决方案?

如果它没有一个有效的解决方案,请尝试证明它(虽然不是绝对必要的)。

如果确实有有效的解决方案,请善待并发布。

【问题讨论】:

  • 你想用什么语言来完成这个?
  • 这似乎是主题和故事之间的二分图上的一种匹配问题。目标是找到一种解决方案还是列出所有可能的解决方案?
  • @CraigW:语言是 PHP 和 MySQL。
  • @hardmath:嗯,目标是显示 5 个或更少主题中的一个选项,这样每个主题都有两个故事,而其他 4 个主题中的任何一个都没有。接下来,用户必须从五个主题中选择两个主题,而我们不想拥有它,这样两个主题中都会出现相同的故事(因为有时没有特殊的回避算法自然会发现哪个是我的目标邮政)。因此,我认为一种解决方案就足够了。
  • @hardmath:你觉得这个问题是不是NP完全的?

标签: php mysql algorithm selection combinations


【解决方案1】:

这个问题的一个更一般的版本是为所有主题(或至少尽可能多的)选择两个故事,以便永远不会为两个不同的主题选择同一个故事。

用 S1...Sm 标记故事,用 T1...Tn 标记主题。复制每个主题,即介绍新的故事T'1...T'n,其中T'i包含Sj 当且仅当 Ti 包含它。现在可以用这种方式重新表述这个问题:为所有主题(或尽可能多的)选择一个不同的故事。获得主题-故事对后,您可以再次加入重复的主题,每个主题将有两个故事。

在从不选择任何元素两次的情况下找到尽可能多的对的问题称为最大二分匹配问题。 (您可以将其视为从bipartite graph 中选择最大数量的非连接边。)有一种称为增广路径的简单算法可以在 O((m+n)e) 步中解决它(e 是数字边缘)和一些更复杂的(例如Hopcroft–Karp algorithm),它们在大约O((m + n)^ 2.5)个步骤中解决它。增广路径算法包括在图中搜索“交替”路径,其中路径的第一条边未被选择,第二条是,第三条不是等等,而不是反转路径上的选择。这可能会适应您的情况,而无需实际进行主题的拆分和合并。

这有点矫枉过正,因为它将返回所有主题的两个故事,而不仅仅是五个;当您只需要为有限数量的主题查找故事时,您可能会做得更好。 (除了一些边缘情况,你可以只选择故事数量最多的五个主题,丢弃其中不包含的故事,然后运行算法。)无论如何,这表明问题远非NP -硬。

【讨论】:

  • 为什么我需要复制每个主题?您能否详细说明您的算法是如何工作的?
  • @akanevsky:我解释得更详细了。
  • +1 表示“无论如何,这表明问题远非 NP 难题。”
  • 这个算法不正确。假设主题 A 在故事 1 和 2 中,主题 B 在故事 2 和 3 中,主题 C 在故事 3 和 4 中。匹配算法可以将 A 与 1 匹配,B 与 2 和 3 匹配,C 与4. 但是,最佳解决方案是让 A 与 1 和 2 匹配,B 与 3 和 4 匹配。可能有解决办法,但可能是一个 NP-hard 问题。请注意,由于此类示例,简单的爬山/增强路径解决方案可能会卡在需要回溯的局部最大值。
  • @jonderry:从问题描述中我不清楚哪种解决方案是最佳的:选择 3 个主题至少有一个(但理想情况下是两个)故事,或者选择两个主题恰好有两个故事。这取决于您如何优先考虑这两个“……如果不可能……”部分。如果每个主题恰好有两个故事具有更高的优先级,那么您肯定是正确的算法是不正确的。
【解决方案2】:

假设我们有一个关联 TopicID 和 StoryID 的 SQL 表 TopicStory, 这对列形成一个复合主键。

目标是找到一组特定的 TopicID。最多五个 在一组中发布的问题需要。深度优先搜索 这些概述如下。

搜索深度为 5 时,上述问题不能 比多项式复杂度差。然而一个普遍的问题 这要求可以找到的最大主题集 like 约束(选择的每个主题至少有两个与 任何其他选择的主题)可能是 NP 完全的。

“搜索”一词的使用暗示了一种回溯算法。以下 我们通过嵌套循环实现了回溯,其中每个循环都是 由其外部的循环定义和参数化。

在我们给出具体细节之前,描述一个 “蛮力”方法,其次是更复杂的方法 更容易被欣赏。

BRUTE_FORCE:

Generate all possible subsets of five topics.
Test each of these sets for feasibility (each topic has
at least two stories unrelated to any of the other topics).

我们的深度优先搜索草图假设主题具有总排序, 例如按 T​​opicID 的整数值排序。这允许主题集 不重复生成(由于主题的排列)。

NESTED_LOOPS:

(Loop_1) Select into List_1 all topics with at least two stories.
         Iterate through List_1, choosing the first topic %T1%.
         PASS control into Loop_2.
         CONTINUE in Loop_1.
         If the end of List_1 is reached, EXIT with failure.

(Loop_2) Select into List_2 all topics > %T1% with at least two
         stories unrelated to %T1%.
         Iterate through List_2, choosing the second topic %T2%.
         If topic %T1% still has at least two stories unrelated
         to %T2%, PASS control into Loop_3.
         CONTINUE in Loop_2.
         If the end of List_2 is reached, go BACK to Loop_1.

(Loop 3) Select into List_3 all topics > %T2% with at least two
         stories unrelated to %T1% or %T2%.
         Iterate through List_3, choosing the third topic %T3%.
         If topic %T1% still has at least two stories unrelated
         to %T2% or %T3%,
         and topic %T2% still has at least two stories unrelated
         to %T1% or %T3%, PASS control into Loop_4.
         CONTINUE in Loop_3.
         If the end of List_3 is reached, go BACK to Loop_2.

(Loop 4) Select into List_4 all topics > %T3% with at least two
         stories unrelated to %T1%, %T2%, or %T3%.
         Iterate through List_4, choosing the fourth topic %T4%.
         If topic %T1% still has at least two stories unrelated
         to %T2%, %T3%, or %T4%,
         and topic %T2% still has at least two stories unrelated
         to %T1%, %T3%, or %T4%,
         and topic %T3% still has at least two stories unrelated
         to %T1%, %T2%, or %T4%, PASS control into Loop_5.
         CONTINUE in Loop_4.
         If the end of List_4 is reached, go BACK to Loop_3.

(Loop 5) Select into List_5 all topics > %T4% with at least two
         stories unrelated to %T1%, %T2%, %T3%, or %T4%.
         Iterate through List_5, choosing the fifh topic %T5%.
         If topic %T1% still has at least two stories unrelated
         to %T2%, %T3%, %T4%, or %T5%,
         and topic %T2% still has at least two stories unrelated
         to %T1%, %T3%, %T4%, or %T5%,
         and topic %T3% still has at least two stories unrelated
         to %T1%, %T2%, %T4%, or %T5%,
         and topic %T4% still has at least two stories unrelated
         to %T1%, %T2%, %T3%, or %T5%, EXIT with success
         returning five topics %T1%, %T2%, %T3%, %T4%, and %T5%.
         CONTINUE in Loop_5.
         If the end of List_5 is reached, go BACK to Loop_4.

在每个嵌套循环的开头使用“选择”是为了唤起 SQL 查询实现大部分逻辑的可能性。为了 例如,最外面的循环基本上只是获取结果集 这个查询:

SELECT   TS1.TopicID, Count(*)
 From    TopicStory TS1
Group By TS1.TopicID
Having   Count(*) > 1

内部循环的对应列表可以类似地构造 通过 SQL 查询,具体取决于在 外循环。为了说明没有不必要的重复让我们跳 直接到最里面的循环并为 List_5 提供适当的查询:

SELECT   TS5.TopicID, Count(*)
 From    TopicStory TS5
 Where   TS5.TopicID > %T4%
  and    NOT EXISTS ( SELECT *
                       From    TopicStory TSX
                       Where   TSX.TopicID in (%T1%,%T2%,%T3%,%T4%)
                        and    TSX.StoryID = TS5.StoryID
                    )
Group By TS5.TopicID
Having   Count(*) > 1

然后检查 List_5 中的 %T5% 是否生成 主题 %T1% 的至少两个故事的计数:

SELECT Count(*)
 From  TopicStory TZ1
 Where TZ1.TopicID = %T1%
  and  NOT EXISTS ( SELECT *
                     From    TopicStory TX1
                     Where   TX1.StoryID = TZ1.StoryID
                      and    TX1.TopicID in (%T2%,%T3%,%T4%,TS5.TopicID)
                  )

并且比照其他先前的主题选择。

虽然它可能会不可接受地降低性能,但附加的逻辑 用于限制与 %T5% 相关的主题(以便较早的主题选择 仍然保留至少两个故事选择)可以推送到一个查询中。 它看起来像这样:

/*
   Given %T1%, %T2%, %T3$, and %T4% from queries above, find all topics %T5% > %T4%
   with at least 2 stories not related to %T1%, %T2%, %T3%, or %T4% and such that
   %T1% still has at least 2 stories not related to %T2%, %T3%, %T4%, or %T5% and
   %T2% still has at least 2 stories not related to %T1%, %T3%, %T4%, or %T5% and
   %T3% still has at least 2 stories not related to %T1%, %T2%, %T4%, or %T5% and
   %T4% still has at least 2 stories not related to %T1%, %T2%, %T3%, or %T5%
*/

SELECT   TS5.TopicID, Count(*)
 From    TopicStory TS5
 Where   TS5.TopicID > %T4%
  and    NOT EXISTS ( SELECT *
                       From    TopicStory TSX
                       Where   TSX.TopicID in (%T1%,%T2%,%T3%,%T4%)
                        and    TSX.StoryID = TS5.StoryID
                    )
  and    ( SELECT Count(*)
            From  TopicStory TZ1
            Where TZ1.TopicID = %T1%
             and  NOT EXISTS ( SELECT *
                                From    TopicStory TX1
                                Where   TX1.StoryID = TZ1.StoryID
                                 and    TX1.TopicID in (%T2%,%T3%,%T4%,TS5.TopicID)
                             )
         ) > 1
  and    ( SELECT Count(*)
            From  TopicStory TZ2
            Where TZ2.TopicID = %T2%
             and  NOT EXISTS ( SELECT *
                                From    TopicStory TX2
                                Where   TX2.StoryID = TZ2.StoryID
                                 and    TX2.TopicID in (%T1%,%T3%,%T4%,TS5.TopicID)
                             )
         ) > 1
  and    ( SELECT Count(*)
            From  TopicStory TZ3
            Where TZ3.TopicID = %T3%
             and  NOT EXISTS ( SELECT *
                                From    TopicStory TX3
                                Where   TX3.StoryID = TZ3.StoryID
                                 and    TX3.TopicID in (%T1%,%T2%,%T4%,TS5.TopicID)
                             )
         ) > 1
  and    ( SELECT Count(*)
            From  TopicStory TZ4
            Where TZ4.TopicID = %T4%
             and  NOT EXISTS ( SELECT *
                                From    TopicStory TX3
                                Where   TX3.StoryID = TZ3.StoryID
                                 and    TX3.TopicID in (%T1%,%T2%,%T3%,TS5.TopicID)
                             )
         ) > 1
Group By TS5.TopicID
Having   Count(*) > 1

MySQL 的功能集是一项正在进行的工作,因此可以想象是一个高效的 在存储过程中实现是可能的,其中游标可以采用 主题列表的作用。但是,如果 “游标”是外部管理的列表(例如在 PHP 中)和数据库查询 尽可能保持简单。

【讨论】:

    【解决方案3】:

    尝试调整它以满足您的需求:

    SELECT topic, story 
    FROM story_topic 
    WHERE story IN (SELECT story FROM story_topic GROUP BY story HAVING COUNT(*) = 1);
    

    这里的关键是要知道哪些故事只发生在一个主题中。您可能需要预先计算主题数以消除子选择。

    【讨论】:

    • 可能所有故事都出现在多个主题中。
    • 您最初的问题将如何处理?也许我只是误解了这个问题。
    【解决方案4】:

    这个怎么样? (如果我理解你的问题)

    (我实际上并没有运行它 - 只是一个想法 - 所以......可能是错误,或者我可能公然错过了一些东西。但是 - 目前,我疲惫的头脑认为它会起作用 :)

    $num_topics = 5;
    $stories_per = 5;
    $stories = array();  //array to store story ids
    
    //select 5 topics
    $query = mysql_query("SELECT * FROM topics ORDER BY RAND() LIMIT ".$num_topics);
    
    //run repeat as many times as you want stories
    for($i=0; $i<$stories_per; $i++) {
    
        //repeat through each selected topic
        while($row = mysql_fetch_array($query)) {
    
            $q_addon = "";
            foreach($stories as $value) {
                $q_addon .= "id <> '".$value."' AND ";
            }
    
            //find a story not yet chosen for each topic
            $q = mysql_query("SELECT storyid FROM stories_topics WHERE ".$q_addon." topicid='".$row['id']."' LIMIT 1");
    
            //add that id to your $stories array
            $tmp_id = mysql_result($q,0,'storyid');
            array_push($stories, $tmp_id);
    
        }
    }
    

    【讨论】:

      【解决方案5】:

      如果你愿意选择类似的 5 个主题,就像我理解的不同的故事一样: 因此您可以对 2 个表进行连接,并在查询中使用前 5 个条件主题标题 =“您想要的主题”

      如果这没有帮助,请让我清楚>>>

      【讨论】:

      • 我需要的不仅仅是 5 个主题,而是 5 个主题,每个主题都有 2 个故事,在其他 4 个主题中都不会重复。
      • 你真的让我很困惑,你能发布和示例,例如:story1 --topic1,topic2-topic3 ... story2 ---topic2,topic5 ....
      • 为什么需要具体的例子?我有一个表格,其中包含(storyid,title)形式的故事和一个包含主题(storyid,topicid)的表格。我想向用户展示 5 个主题,当他选择其中 2 个主题时,我不希望他碰巧在两个主题下看到相同的故事。现在清楚了吗?
      • 所以如果你的意思是如果他从选择框中选择多个主题,那么它必须显示重复的故事..所以你必须使用 :SELECT DISTINCT column_name(s) FROM table_name
      猜你喜欢
      • 2021-06-01
      • 1970-01-01
      • 2022-01-05
      • 2014-09-18
      • 1970-01-01
      • 2018-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多