昨天,我在洗衣服时把袜子配对,结果发现我这样做效率不高。我在做一个天真的搜索——挑选一只袜子,然后“反复”寻找那一双袜子。这需要平均在n/2*n/4=n2/8袜子上迭代。

作为一名计算机科学家,我在想我能做什么?排序(根据大小/颜色/…)当然是为了实现O(NlogN)解决方案。

哈希或其他不到位的解决方案是不可选择的,因为我无法复制我的袜子(如果可以的话,这可能很好)。

因此,问题基本上是:

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

我将感谢回答以下方面的问题:

大量袜子的一般理论解。袜子的实际数量没有那么多,我不相信我的配偶和我有超过30双。(而且很容易区分我的袜子和她的袜子;这也可以用吗?)它是否等同于元素清晰度问题?


当前回答

由于人脑的结构与现代CPU完全不同,所以这个问题毫无实际意义。

人类可以利用“找到匹配的对”这一事实来战胜CPU算法,这对于一个不太大的集合来说是一个操作。

我的算法:

spread_all_socks_on_flat_surface();
while (socks_left_on_a_surface()) {
     // Thanks to human visual SIMD, this is one, quick operation.
     pair = notice_any_matching_pair();
     remove_socks_pair_from_surface(pair);
}

至少这是我在现实生活中使用的,我发现它非常有效。缺点是它需要一个平坦的表面,但通常很丰富。

其他回答

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);
  }
}

我希望我能为这个问题贡献一些新的东西。我注意到,所有的答案都忽略了这样一个事实,即在不降低整体洗衣性能的情况下,有两点可以执行预处理。

此外,即使是大家庭,我们也不需要假设有大量袜子。袜子从抽屉中取出并穿上,然后在洗衣服之前,将它们扔到一个地方(可能是一个垃圾箱)。虽然我不会将所说的垃圾箱称为后进先出堆栈,但我认为可以安全地假设

人们把两只袜子大致扔在箱子箱子在任何时候都不会随机化,因此从该容器顶部获取的任何子集通常都包含一双袜子。

由于我所知道的所有洗衣机的尺寸都是有限的(不管你要洗多少袜子),而且洗衣机中会发生实际的随机性,所以无论我们有多少袜子,我们总是有几乎不含单品的小子集。

我们的两个预处理阶段是“把袜子放在晾衣绳上”和“把袜子从晾衣绳里拿出来”,我们必须这样做,这样才能得到既干净又干燥的袜子。和洗衣机一样,晾衣绳是有限的,我假设我们可以看到袜子的整个部分。

以下是put_socks_on_ine()的算法:

while (socks left in basket) {
 take_sock();
 if (cluster of similar socks is present) { 
   Add sock to cluster (if possible, next to the matching pair)
 } else {
  Hang it somewhere on the line, this is now a new cluster of similar-looking socks.      
  Leave enough space around this sock to add other socks later on 
 }
}

不要浪费时间四处移动袜子或寻找最佳搭配,这一切都应该在O(n)中完成,这也是我们将它们放在未分类的线上所需要的。袜子还没有配对,我们只有几个相似的簇。我们这里有一套有限的袜子是很有帮助的,因为这有助于我们创建“好”的簇(例如,如果这套袜子中只有黑色的袜子,那么按颜色簇就不是办法了)

下面是take_socks_from_line()的算法:

while(socks left on line) {
 take_next_sock();
 if (matching pair visible on line or in basket) {
   Take it as well, pair 'em and put 'em away
 } else {
   put the sock in the basket
 }

我应该指出,为了提高其余步骤的速度,明智的做法是不要随机选择下一个袜子,而是从每个簇中依次选择一个又一个袜子。这两个预处理步骤只需要将袜子放在晾衣绳上或放在篮子里,这是我们无论做什么都必须做的,因此这将大大提高洗衣性能。

在此之后,很容易执行哈希分区算法。通常,大约75%的袜子已经配对,给我留下了非常小的袜子子集,并且这个子集已经(有点)聚类(在预处理步骤之后,我没有在我的篮子中引入太多熵)。另一件事是,剩余的集群往往足够小,可以一次处理,因此可以从篮子中取出整个集群。

下面是sort_maining_clusters()的算法:

while(clusters present in basket) {
  Take out the cluster and spread it
  Process it immediately
  Leave remaining socks where they are
}

之后,只剩下几只袜子了。在这里,我将之前未配对的袜子引入到系统中,并在不使用任何特殊算法的情况下处理剩余的袜子——剩余的袜子非常少,可以非常快速地进行视觉处理。

对于所有剩余的袜子,我假设它们的同伴仍然没有洗,并将它们放在一边,以备下次迭代。如果你记录了一段时间内未配对袜子的增长(“袜子泄漏”),你应该检查你的垃圾箱——它可能会随机出现(你有猫睡在里面吗?)

我知道这些算法需要很多假设:一个充当某种LIFO堆栈的垃圾箱,一台有限的普通洗衣机,以及一条有限的普通晾衣绳——但这仍然适用于大量袜子。

关于并行性:只要你把两个袜子放在同一个箱子里,你就可以很容易地并行化所有这些步骤。

对于p双袜子(n=2p只袜子),我实际上是这样做的:

从袜子堆里随便拿一只袜子。对于第一只袜子,或者如果之前选择的所有袜子都已配对,只需将袜子放入前面未配对袜子“阵列”的第一个“槽”中。如果有一个或多个选定的未配对袜子,请对照阵列中的所有未配对袜子检查当前袜子。在构建阵列时,可以将袜子分为普通类别或类型(白色/黑色、脚踝/圆领、运动型/连衣裙),并“向下搜索”以仅比较同类。如果你找到了一个可以接受的匹配,把两只袜子放在一起,然后把它们从阵列中去掉。如果没有,请将当前袜子放入阵列中第一个打开的插槽中。对每只袜子重复上述步骤。

这种方案的最坏情况是,每双袜子都不同,必须完全匹配,而且你挑选的第一双n/2袜子都不同。这是你的O(n2)场景,极不可能。如果袜子的独特类型的数量t小于袜子对的数量p=n/2,并且每种类型的袜子都足够相似(通常在穿着相关的术语中),使得该类型的任何袜子都可以与任何其他袜子配对,那么正如我上面所推断的,你必须与之进行比较的袜子的最大数量是t,之后你拉动的下一只袜子将与未配对的袜子之一相匹配。这种情况在普通袜子抽屉中比在最坏情况下更可能发生,并将最坏情况的复杂性降低到O(n*t),其中通常t<<n。

我的解决方案并不完全符合您的要求,因为它正式需要O(n)“额外”空间。然而,考虑到我的条件,它在我的实际应用中非常有效。因此,我认为这应该很有趣。

与其他任务合并

我的特殊情况是,我不用烘干机,只是把衣服挂在普通的烘干机上。挂布需要O(n)操作(顺便说一句,我在这里总是考虑垃圾箱包装问题),这个问题本质上需要线性的“额外”空间。当我从桶里拿出一只新袜子时,如果这双袜子已经挂好了,我会试着把它挂在旁边。如果是新袜子,我会在旁边留出一些空间。

Oracle机器更好;-)

显然,这需要一些额外的工作来检查是否有匹配的袜子已经挂在某个地方,这将为计算机提供系数约为1/2的解O(n^2)。但在这种情况下,“人为因素”实际上是一种优势——如果匹配的袜子已经挂起,我通常可以很快(几乎为O(1))识别出它(可能涉及到大脑缓存中的一些难以察觉的因素)——将其视为一种有限的“预言机”,如oracle Machine;-)我们人类在某些情况下比数字机器有这些优势;-)

快到O(n)!

因此,将袜子配对的问题与挂布的问题联系起来,我可以免费获得O(n)“额外的空间”,并有一个及时的解决方案,大约O(n),只需要比简单的挂布多一点的工作,即使在非常糟糕的星期一早晨,也可以立即获得一双完整的袜子…;-)

如果你可以将一双袜子抽象为密钥本身,将另一双袜子作为值,那么我们可以使用哈希来发挥作用。

在你身后的地板上做两个假想的部分,一个给你,另一个给配偶。从袜子堆里取一只。现在,按照以下规则将袜子一只一只地放在地板上。确定袜子是你的还是她的,并查看地板上的相关部分。如果你能在地板上找到这双鞋,就把它捡起来,把它们系起来,或者把它们夹起来,或者在找到一双鞋后做任何你想做的事情,然后把它放在篮子里(把它从地板上取下来)。将其放在相关章节中。重复3次,直到所有袜子都从袜子堆上取下。

说明:

哈希和抽象

抽象是一个非常强大的概念,已用于改善用户体验(UX)。现实生活中与计算机交互的抽象示例包括:

用于在GUI(图形用户界面)中导航以访问地址的文件夹图标,而不是键入实际地址以导航到某个位置。GUI滑块用于控制不同级别的音量、文档滚动位置等。。

哈希或其他不到位的解决方案是不可选择的,因为我无法复制我的袜子(如果可以的话,这可能很好)。

我相信提问者正在考虑使用哈希,这样在放置袜子之前,应该知道袜子的位置。

这就是为什么我建议将放在地板上的一只袜子抽象为哈希键本身(因此不需要复制袜子)。

如何定义哈希键?

如果有不止一双类似的袜子,下面的密钥定义也适用。也就是说,假设有两双黑色男士袜子PairA和PairB,每双袜子都被命名为PairA-L、PairA-R、PairB-L和PairB-R。因此,PairA-L可以与PairB-R配对,但PairA-L和PairB-L不能配对。

假设任何袜子都可以通过以下方式唯一标识,

属性[性别]+属性[颜色]+属性(材质)+属性[类型1]+属性[类别2]+属性[左_右]

这是我们的第一个哈希函数。让我们对这个h1(G_C_M_T1_T2_LR)使用一个简短的符号。h1(x)不是我们的位置键。

消除Left_or_Right属性的另一个哈希函数是h2(G_C_M_T1_T2)。第二个函数h2(x)是我们的位置键!(你身后地板上的空间)。

要定位插槽,请使用h2(G_C_M_T1_T2)。一旦找到了槽,就使用h1(x)来检查它们的哈希值。如果它们不匹配,你就有一对。否则,把袜子扔到同一个槽里。

注意:由于我们在找到一个插槽时删除了一个插槽,因此可以安全地假设最多只有一个插槽具有唯一的h2(x)或h1(x)值。

如果我们每只袜子正好有一对匹配的袜子,那么使用h2(x)来查找位置,如果没有袜子,则需要进行检查,因为可以安全地假设它们是一对。

为什么把袜子放在地板上很重要

让我们考虑一个场景,袜子堆在一起(最坏的情况)。这意味着我们别无选择,只能进行线性搜索来找到一对。

将它们铺在地板上可以提高可见度,从而提高发现匹配袜子(匹配哈希键)的机会。当第三步把袜子放在地板上时,我们的大脑已经下意识地记录了位置。-因此,如果这个位置在我们的内存中可用,我们可以直接找到匹配的配对。-如果没有记住位置,不要担心,然后我们可以一直返回到线性搜索。

为什么从地板上取下这对鞋很重要?

短期人类记忆在需要记忆的项目较少时效果最好。因此,增加了我们使用哈希来识别这对的概率。当使用线性搜索对时,它还将减少要搜索的项目的数量。

分析

情况1:最坏的情况是,Derpina无法记住或直接使用哈希技术在地板上发现袜子。Derp对地板上的物品进行线性搜索。这并不比遍历堆以找到对更糟。比较上限:O(n^2)。比较下限:(n/2)。(当Derpina每捡一只袜子都是上一只的时候)。案例2:德普记得他放在地板上的每一只袜子的位置,每只袜子正好有一双。比较上限:O(n/2)。比较下限:O(n/2)。

我说的是比较操作,从袜子堆里挑选袜子必然是n次操作。因此,实际的下限是n次迭代,n/2次比较。

加快进度

为了获得完美的分数,使Derp获得O(n/2)比较,我建议Derpina,

花更多时间穿袜子来熟悉它。是的,这意味着也要花更多时间穿着德普的袜子。玩记忆游戏,如在网格中找出对,可以提高短期记忆性能,这是非常有益的。

这是否等同于元素清晰度问题?

我建议的方法是用于解决元素区分问题的方法之一,将它们放在哈希表中并进行比较。

考虑到您的特殊情况,即只有一个精确的对,它已经变得非常等价于元素区别问题。因为我们甚至可以对袜子进行分类,并检查相邻袜子是否成对(EDP的另一种解决方案)。

然而,如果给定袜子可能存在不止一双,那么它就偏离了EDP。