如何高效地从一堆袜子中匹配成对?

4183

昨天我在整理干净的衣服中的袜子,发现我所做的方法不太高效。 我正在进行一个朴素搜索——选取一只袜子并“迭代”堆以找到它的配对。这需要平均遍历n/2 * n/4 = n2/8只袜子。

作为一名计算机科学家,我在思考我该怎么做?排序(按照大小/颜色/...)当然是想到了实现O(NlogN)解决方案的方法。

哈希或其他非就地解决方案不是选择,因为我无法复制我的袜子(虽然如果我能这样做那会很好)。

所以,问题基本上是:

给定一个包含n对袜子的堆,其中包含2n个元素(假设每只袜子恰好有一个匹配对),使用多达对数额外空间有效地将它们配对的最佳方法是什么? (如果需要,我相信我可以记住那些信息的数量。)

我将感激回答以下方面的答案:

  • 大量袜子的一般理论解决方案。
  • 实际袜子的数量并不是很大,我不认为我和我的配偶拥有超过30双。 (而且区分我的袜子和她的袜子相当容易;这也可以使用吗?)
  • 它是否等同于元素不同性问题

484
我使用鸽巢原理从洗衣堆里准确地配对一双袜子。我有三种不同颜色的袜子(红色、蓝色和绿色),每种颜色有两双。每次我会拿起四只袜子,总是组成一双配对的袜子然后开始工作。 - Srinivas
68
鸽巢原理又来了:如果你从 n/2+1 只袜子中选出一个子集,那么必定至少有一对袜子在这个子集中。 - wildplasser
42
好问题!你可能会对我关于相关问题的文章感兴趣,它是关于从一堆袜子中抽出两只匹配袜子的概率讨论:http://blogs.msdn.com/b/ericlippert/archive/2010/03/22/socks-birthdays-and-hash-collisions.aspx - Eric Lippert
387
为什么不孵化一个子进程并使用 waitpid 函数,这样作为父进程,你甚至不需要亲自整理任何袜子? - Mxyk
182
我解决了这个问题,方法是只拥有白色的及膝袜子。它们全部搭配起来都很好看。我可以从一堆袜子里随便拿出任何两只袜子,它们都会搭配好。为了进一步简化问题,我不将袜子配对。我有一个袜子抽屉,我只是把所有的袜子扔进去,没有配对。每天早上我从抽屉里随机拿出两只袜子。我已经将其简化到O(0)。再也不能更简单了 :) - Lee
显示剩余38条评论
38个回答

24

成本:移动袜子->高,寻找/排队取袜子->小

我们要做的是减少移动次数,并通过增加搜索次数进行补偿。另外,我们可以利用智人的多线程环境来在决策缓存中保存更多的物品。

X = 你的,Y = 你配偶的

从一堆袜子A开始:

挑选两只袜子,将相应的X袜子放在X行,将Y袜子放在Y行的下一个可用位置。

重复以上操作直到A为空。

对于每行X和Y

  1. 选择该行中的第一只袜子,沿着该行查找直到找到相应的袜子。

  2. 将其放入相应的已完成袜子线路中。

  3. 可选项 - 当你在查找该行时,如果当前袜子与上一个袜子相同,则对这些袜子执行步骤2。

作为步骤一的替代方案,您可以从该行中取出两个袜子而不是两个,由于缓存内存足够大,我们可以快速确定任何一个袜子是否与您正在观察的该行上的当前袜子匹配。如果你很幸运有三只手臂,那么在主体的记忆力足够大的情况下,你可能同时处理三只袜子。

直到X和Y都为空为止。

完成

然而,由于I/O(移动袜子)和搜索(在行中搜索袜子)的速度快,因此这个过程的复杂度与选择排序相似,但所花费的时间要少得多。


24

这是一个基于比较模型的Omega(n log n)下限。 (唯一有效的操作是比较两只袜子。)

假设你“知道”你的2n只袜子是这样排列的:

p1 p2 p3 ... pn pf(1) pf(2) ... pf(n)

其中f是集合{1,2,…,n}的未知排列。知道这个对问题并没有更大的影响。有n!种可能的输出(第一半和第二半之间的匹配),这意味着您需要log(n!) = Omega(n log n)次比较。可以通过排序来实现。

由于您对元素唯一性问题的联系感兴趣:证明元素唯一性的Omega(n log n)下限更难,因为输出是二进制的是/否。在这里,输出必须是匹配,并且可能的输出数量足以得到合理的下限。但是,与元素唯一性相关的变体如下。假设您有2n只袜子,并想知道它们是否可以唯一配对。通过将(a1, a2, ..., an)发送到(a1, a1, a2, a2, ..., an, an),可以从ED获得缩小。(顺便说一下,ED的难度证明非常有趣,通过拓扑学。)

如果只允许等式测试,则我认为原始问题应该有一个Omega(n2)下限。我的直觉是:考虑一个图,在测试后添加一条边,并论证如果该图不密集,则输出不能唯一确定。


23

现实世界的做法:

尽可能快地,一次从未分类的袜子堆中取出一只袜子,并将其放在你面前的堆中。这些堆应该相对紧凑地排列,所有袜子指向同一个方向;堆的数量受到您可以轻松触及的距离的限制。选择要放置袜子的堆应该是——尽可能快地——把一只袜子放在表面上与此类似的袜子堆上;偶尔会出现I型(将袜子放在不属于它的堆上)或II型(将袜子放在已有同类袜子的堆上)错误——最重要的考虑因素是速度

一旦所有的袜子都在堆里,迅速检查多袜子堆,创建并移除成对的袜子(这些袜子将进入抽屉)。如果堆中有不匹配的袜子,则将它们重新放到最佳堆中(在尽可能快的约束条件下)。当所有多袜子堆被处理完毕后,匹配其余可成对的袜子,这些袜子由于II型错误而未能成对。嘘,你就完成了——我有很多袜子,直到其中的大部分变脏才洗它们。另一个实用的注意事项:我将一双袜子中的一只翻下来盖在另一只上面,利用它们的弹性特性,这样它们在被运送到抽屉和在抽屉中时就可以粘在一起。


21
这是我实际处理袜子配对(n = 2p个独立的袜子,其中p对)的方法:
  • 从一堆袜子中随机抓取一只。
  • 对于第一只袜子,或者如果之前选择的所有袜子都已经成对,只需将袜子放入你面前未成对袜子的“数组”中的第一个“插槽”中。
  • 如果你有一只或多只已选择的未成对袜子,请将当前的袜子与数组中的所有未成对袜子进行比较。
    • 在构建数组时,可以将袜子分为一般类别或类型(白色/黑色,踝部/长筒,运动/正装),并且“逐层深入”,仅进行同类比较。
    • 如果找到可接受的匹配项,请将两只袜子放在一起并将它们从数组中删除。
    • 如果没有找到匹配项,请将当前袜子放入数组中的第一个空插槽中。
  • 重复以上步骤,直到每只袜子都被配对。

这种方案的最坏情况是每一对袜子都足够不同,必须完全匹配,并且你选择的前n/2只袜子都不同。这是O(n2)情况,极其不太可能发生。如果袜子的唯一类型数t小于配对数p = n/2,并且每个类型的袜子足够相似(通常在磨损相关方面),那么如我上面所示,你需要比较的袜子数量最多为t,之后你拉出来的下一只袜子将与未成对的袜子之一匹配。这种情况在普通的袜子抽屉中更有可能发生,可以将最坏情况的复杂度降至O(n*t),通常t << n


1
这大概就是我的心理过程。我有一个额外的预先排序优化层。我的运动袜子和白色衣服一起洗,而我的正式袜子则与彩色衣服一起洗。 这意味着只要我不把两堆衣服混在一起,我的袜子已经按类型分组了。白色衣服的洗涤速度非常快(许多相同的袜子),但正式袜子需要更长时间。另一个关键提示-为排序制造更多可用内存(首先将所有非袜子折叠并移除,然后再运行配对算法)。 - o.h

16

从您的问题中可以清楚地看出您在洗衣方面没有太多实际经验:)您需要一个适用于少量无法成对的袜子的算法。

到目前为止的答案没有充分利用我们人类的模式识别能力。 Set游戏提供了如何做到这一点的线索:将所有袜子放在二维空间中,以便您可以轻松地认识它们并用手轻松接近它们。 这将限制您的区域大约为120 * 80厘米左右。 然后选择您认可的双数并将其移除。 将额外的袜子放入空闲空间并重复此过程。 如果您为那些有易于识别的袜子(小孩子最容易想到)的人洗衣服,则可以通过首先选择这些袜子来进行基数排序。 当单只袜子数量较少时,此算法效果良好。


通常我都是这样做的。这比每次迭代所有剩余的袜子要好得多。 - yu_ominae
不错的方法,我认为它也可以应用于一些真实的计算机科学问题。你能否举一个这样的例子(我们可以使用类似的方法来解决问题的计算机科学问题)?此外,这个解决方案如何适用于数百万只袜子? - amit
我认为这基本上与此答案相同,来自1月20日的https://dev59.com/c2Yq5IYBdhLWcg3whQ80#14423956。两个+1。人类视觉系统是极其并行的。 - Will Ness

16

我已经采取了简单的步骤来将我的工作流程降低到只需要O(1)时间的过程。

通过将我的输入减少为两种袜子之一(白色袜子用于休闲,黑色袜子用于工作),我只需要确定手中有哪一双袜子即可。(从技术上讲,因为它们从未一起洗涤,我已将此过程降至O(0)时间。)

要找到理想的袜子并购买足够数量以消除您现有袜子的需求,需要一些前期的努力。由于在我需要黑色袜子之前就已经做过这件事情,所以我的努力很小,但是每个人的情况都不同。

这种前期的努力在非常受欢迎和有效的代码中已经出现了很多次。例如,#DEFINE将pi定义为几个小数位(还有其他例子,但这是我现在想到的一个)。


16

拿起第一只袜子,放在桌子上。现在再拿另一只袜子;如果它与第一只匹配,请将其放在第一只袜子的顶部。否则,请将其放在离第一只袜子有一小段距离的桌子上。接着拿第三只袜子;如果它与前两只中的任何一只匹配,请将其放在它们的顶部,否则请将其放在距第三只袜子有一小段距离的地方。重复这个过程,直到你把所有的袜子都拿完。


1
这是唯一有效的答案。其他所有答案都忽略了最多时间花费在区分相似袜子上的事实(因此将它们全部按外观相似性归为一类只会使情况更糟)。 - entonio
为了好玩,我写了一个小的Python程序来堆积袜子。https://gist.github.com/justinfay/53b574cf0a492f6795ef - Justin Fay

13
List<Sock> UnSearchedSocks = getAllSocks();
List<Sock> UnMatchedSocks = new list<Sock>();
List<PairOfSocks> PairedSocks = new list<PairOfSocks>();

foreach (Sock newSock in UnsearchedSocks)
{
  Sock MatchedSock = null;
  foreach(Sock UnmatchedSock in UnmatchedSocks)
  {
    if (UnmatchedSock.isPairOf(newSock))
    {
      MatchedSock = UnmatchedSock;
      break;
    }
  }
  if (MatchedSock != null)
  {
    UnmatchedSocks.remove(MatchedSock);
    PairedSocks.Add(new PairOfSocks(MatchedSock, NewSock));
  }
  else
  {
    UnmatchedSocks.Add(NewSock);
  }
}

13

我提出了另一个解决方案,它不保证操作次数更少,也不保证时间消耗更少,但应该尝试一下,看看是否可以成为足够好的启发式方法,以在大量袜子配对中减少时间消耗。

先决条件: 没有保证有相同的袜子。如果它们是同一颜色,并不意味着它们有相同的大小或图案。袜子被随机洗牌。可能会有奇数只袜子(缺少一些,我们不知道有多少)。准备记住一个变量“index”,并将其设置为0。

结果将有一个或两个堆:1. "匹配"和2. "缺失"

启发式方法:

  1. 找到最不同的袜子。
  2. 找到它的匹配项。
  3. 如果没有匹配项,将其放在“缺失”堆中。
  4. 重复从1开始,直到没有最不同的袜子为止。
  5. 如果袜子少于6只,则转到11.
  6. 将所有袜子盲目配对到它们的邻居(不要打包)
  7. 找到所有匹配的对,打包并将打包的对移动到“匹配”堆中; 如果没有新的匹配项-将“index”加1
  8. 如果“index”大于2(这可能是根据袜子数量而定的值,因为随着袜子数量的增加,将它们盲目配对的机会变少),则转到11.
  9. 洗剩余的袜子
  10. 转到1
  11. 忘记“index”
  12. 拿出一只袜子
  13. 找到它的配对项
  14. 如果该袜子没有配对项,则将其移动到“缺失”堆中
  15. 如果找到匹配项,请将其配对,打包并将其移动到“匹配”堆中
  16. 如果仍然有超过一只袜子,请转到12.
  17. 如果只剩下一只袜子,请转到14.
  18. 微笑满意:)

此外,还可以添加损坏袜子的检查,以便将其移除。它可以插入在第2和第3之间,以及第13和第14之间。

我期待听到任何经验或修正意见。


写完这个之后,我每次都用它。它帮助我变得更加高效,工作现在也不那么无聊了。 - Saša

13

为了说明从一堆袜子中配对的效率有多高,我们必须先定义机器,因为配对不是通过图灵机或随机访问机进行的,这些通常用作算法分析的基础。

机器

该机器是真实世界元素“人”的抽象。它能够通过一双眼睛从环境中读取信息。我们的机器模型可以使用两只手臂来操作环境。逻辑和算术操作使用我们的大脑计算(希望如此;-))。

我们还必须考虑使用这些工具可以执行的原子操作的本质运行时间。由于物理限制,由手臂或眼睛执行的操作具有非常数时间复杂度。这是因为我们不能用手臂移动无尽大的一堆袜子,眼睛也看不到无尽大的一堆袜子的顶部袜子。

然而,机械物理也给了我们一些好处。我们不仅限于用一只手臂移动一个袜子。我们可以一次移动一整对袜子。

因此,根据先前的分析,应按降序使用以下操作:

  • 逻辑和算术操作
  • 环境读取
  • 环境修改

我们还可以利用人们只有非常有限的袜子这一事实。因此,环境修改可以涉及堆中所有袜子。

算法

所以这是我的建议:

  1. 将所有袜子散落在地面上。
  2. 通过查看地面上的袜子找到一双袜子。
  3. 重复步骤2直到无法匹配为止。
  4. 从1开始重复,直到地面上没有袜子为止。

操作4是必要的,因为在将袜子散落在地面时,某些袜子可能会隐藏其他袜子。以下是该算法的分析:

分析

该算法高概率终止。这是由于在第2步中无法找到袜子配对。

对于配对n双袜子的运行分析,我们假设至少有一半的2n只袜子在步骤1后没有隐藏。因此,在平均情况下,我们可以找到n/2对。这意味着循环的第4步执行O(log n)次。步骤2执行O(n^2)次。因此,我们可以得出以下结论:

  • 算法涉及O(ln n + n)个环境修改(步骤1包括O(ln n)个加上从地板上选取每对袜子)
  • 算法涉及O(n^2)个环境读取(步骤2)
  • 算法涉及O(n^2)个逻辑和算术操作以比较步骤2中的袜子

因此,我们有总运行复杂度为O(r*n^2 + w*(ln n + n)),其中rw是合理数量的袜子的环境读取和环境写入操作的因素。逻辑和算术操作的成本被省略,因为我们假设判断两只袜子是否属于同一对需要恒定数量的逻辑和算术操作。这在每种情况下可能不可行。


1
这段代码与 https://dev59.com/c2Yq5IYBdhLWcg3whQ80#14423956 和 https://dev59.com/c2Yq5IYBdhLWcg3whQ80#14468913 相同。 - Will Ness
@WillNess 是的,需要更详细的解释。 - SpaceTrucker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接