How to pair socks from a pile efficiently?
昨天我在整理干净的衣服的袜子,发现我的工作效率不是很高。我做了一个幼稚的搜索——挑选一只袜子,然后"迭代"这一堆,以便找到它的一对。这要求平均迭代n/2*n/4=n2/8个SOCKS。
作为一名计算机科学家,我在想我能做什么?排序(根据大小/颜色/…)当然是为了实现一个O(nlogn)解决方案。
散列或其他不到位的解决方案不是一个选择,因为我不能复制我的袜子(尽管如果我可以的话,这可能很好)。
所以,问题基本上是:
考虑到一堆包含
我将感谢回答以下方面的问题:
- 大量袜子的一般理论解。
- 袜子的实际数量不是那么大,我不相信我和我的配偶有30多双。(而且很容易区分我的袜子和她的,这也可以用吗?)
- 它等价于元素的清晰度问题吗?
排序解决方案已经提出,但排序有点太多:我们不需要顺序;我们只需要平等组。
所以散列就足够了(而且更快)。
当需要对大型数据集进行哈希联接或哈希聚合时,SQL Server实际上正在执行这种递归哈希分区。它将构建输入流分布到许多独立的分区中。该方案可以线性扩展到任意数量的数据和多个CPU。
如果您可以找到一个分布键(散列键),它提供足够的存储桶,每个存储桶都足够小,可以很快地进行处理,那么就不需要递归分区。不幸的是,我不认为袜子有这样的属性。
如果每个袜子有一个称为"pairid"的整数,根据
我能想到的最好的现实分区是创建一个成堆的矩形:一个维度是颜色,另一个是模式。为什么是矩形?因为我们需要O(1)随机访问堆。(一个三维长方体也可以,但这不是很实用。)
更新:
并行性呢?多人能更快地搭配袜子吗?
元素的清晰度问题呢?如本文所述,
显然,我们不能比
尽管输出并不完全相同(在一种情况下,只是一个布尔值)。在另一种情况下,袜子对),渐进的复杂性是相同的。
由于人脑的结构与现代CPU完全不同,这个问题没有实际意义。
人类可以利用"找到匹配对"这一事实来战胜CPU算法,这对于一个不太大的集合来说是一个操作。
我的算法:
1 2 3 4 5 6 | 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); } |
至少这是我在现实生活中使用的,我发现它非常有效。缺点是它需要一个平坦的表面,但通常是丰富的。
案例1:所有的袜子都是一样的(顺便说一下,这就是我在现实生活中所做的)。
随便挑两个来做一对。固定时间。
案例2:有固定数量的组合(所有权、颜色、大小、纹理等)。
使用基数排序。这只是线性时间,因为不需要进行比较。
案例3:组合的数量事先未知(一般案例)。
我们得比较一下,看看两双袜子是否成对。选择一种基于
然而,在现实生活中,当袜子的数量相对较小(不变)时,这些理论上的优化算法就不能很好地工作。它可能比顺序搜索花费更多的时间,这在理论上需要二次时间。
非算法答案,但当我这样做时"有效":
步骤1)丢弃所有现有的袜子
步骤2)去沃尔玛,按10-N包购买白色和M包黑色。在日常生活中不需要其他颜色生活。
然而,有时,我不得不再次这样做(丢失的袜子,损坏的袜子等),我不喜欢经常丢弃完美的袜子(我希望他们继续销售相同的袜子参考!)所以我最近采取了不同的方法。
算法答案:
考虑一下,如果你只为第二叠袜子画一只袜子,就像你正在做的那样,在一次天真的搜索中找到匹配的袜子的几率很低。
- 所以随机挑选五个,记住它们的形状或长度。
为什么是五?通常情况下,人类很好地记住工作记忆中的五到七个不同元素——有点像人类的RPN堆栈——五个元素是一个安全的默认值。
从2N-5堆中取一个。
现在在你画的五幅图中寻找一个匹配(视觉模式匹配——人类很擅长用一个小的堆栈进行匹配),如果你找不到一幅,那么把它加到你的五幅图中。
从袜子堆中随机挑选袜子,并与你的5+1袜子进行比较。随着堆栈的增长,它将降低您的性能,但会增加您的几率。快得多。
请随意写下公式,计算出50%的匹配几率需要抽取多少样本。是超几何定律。
我每天早上都这样做,很少需要超过三次的抽签——但我有一双类似的
顺便说一句,我发现每次我需要一双袜子时,整理所有袜子的交易成本之和远远低于一次整理和装订袜子的成本。A"准时制"效果更好,因为这样你就不必装订袜子了,而且边际回报也在减少(也就是说,你一直在寻找那两到三只袜子,当你在洗衣房的某个地方,你需要完成你的袜子的搭配,你就失去了时间)。
我要做的是拿起第一只袜子,把它放下(比如说,放在洗衣碗的边上)。然后我拿起另一只袜子,检查它是否和第一只袜子一样。如果是的话,我会把它们都去掉。如果不是,我把它放在第一只袜子旁边。然后我拿起第三只袜子,把它和前两只比较一下(如果它们还在的话)。等。
此方法可以很容易地在数组中实现,假设"删除"SOCKS是一个选项。实际上,您甚至不需要"删除"SOCKS。如果您不需要对袜子进行排序(见下文),那么您可以将它们四处移动,最后得到一个数组,其中所有的袜子成对排列在该数组中。
假设SOCKS的唯一操作是比较等式,这个算法基本上还是一个n2算法,尽管我不知道平均情况(从未学过计算)。
当然,分类可以提高效率,特别是在现实生活中,你可以很容易地在两个袜子之间"插入"一只袜子。在计算中,同样可以通过树来实现,但这是额外的空间。当然,我们又回到了NLogn(或者更进一步,如果根据分类标准有几个袜子是相同的,但不是来自同一双)。
除此之外,我什么都想不起来,但这种方法在现实生活中似乎非常有效。:)
这是在问错误的问题。正确的问题是,为什么我要花时间整理袜子?当你把你的空闲时间用你选择的x个货币单位来衡量时,它每年要花多少钱?
通常情况下,这不仅仅是任何空闲时间,而是早晨的空闲时间,你可以躺在床上,喝咖啡,或者早点离开,不被交通阻塞。
退后一步,想出解决问题的办法通常是好的。
还有一种方法!
找一只你喜欢的袜子。考虑所有相关特征:不同照明条件下的颜色、整体质量和耐用性、不同气候条件下的舒适性以及气味吸收。同样重要的是,它们在储存时不应失去弹性,所以天然织物是好的,它们应该用塑料包装。
如果左脚袜子和右脚袜子没有区别,那就更好了,但这并不重要。如果袜子是左右对称的,找到一双是O(1)操作,对袜子进行分类大约是O(M)操作,其中M是你家里的地方数量,你已经把袜子乱丢了,理想情况下是一些小的常量。
如果你选择了一双左右袜子不同的花式袜子,对左右脚的水桶进行全桶分类,取o(n+m),其中n是袜子的数量,m与上面相同。其他人可以给出找到第一对的平均迭代次数的公式,但是用盲搜索找到一对的最坏情况是n/2+1,这对于合理的n来说是天文上不太可能的情况。当用mk1眼球扫描一堆未分类的袜子时,这可以通过使用高级的图像识别算法和启发式方法来加速。
因此,实现O(1)袜子配对效率(假设为对称袜子)的算法是:
你需要估计下半辈子你需要多少双袜子,或者直到你退休,搬到温暖的气候,不再需要穿袜子。如果你还年轻,你也可以估计在我们家里有袜子分类机器人需要多长时间,整个问题就变得无关紧要了。
您需要了解如何批量订购您选择的袜子,以及它的成本,以及它们的交付方式。
订购袜子!
把你的旧袜子脱掉。
另一个步骤3将涉及到比较多年来一次购买相同数量甚至更便宜的袜子的成本,并增加整理袜子的成本,但我要说的是:批量购买更便宜!另外,袜子在储藏室里的价值以股票价格通胀的速度增长,这比你在许多投资上得到的要多。另外还有储存成本,但是袜子在壁橱的最上面的架子上确实不占多少空间。
问题解决了。所以,只要买新袜子,扔掉/捐赠你的旧袜子,并在知道你每天都在为你的余生节省金钱和时间后快乐地生活。
理论上的极限是O(N),因为你需要触摸每只袜子(除非有些已经以某种方式配对)。
使用基数排序可以实现O(N)。您只需要为桶选择一些属性。
如果您可以选择有限数量的属性,但有足够多的属性可以唯一地标识每对属性,那么您应该选择O(k*n),如果我们认为k是有限的,那么它就是O(n)。
作为实际解决方案:
如果你有1000只袜子,有8种颜色,平均分布,你可以在C*N时间内把每125只袜子分成4堆。有了5只袜子的门槛,你可以在6次跑步中对每一堆进行分类。(数到2秒,把一只袜子扔到正确的一堆上,只需不到4个小时。)
如果你只有60只袜子,3种颜色和2种袜子(你/你妻子的),你可以在1次跑步中对每一堆10只袜子进行分类(同样,阈值为5)。(数到2秒需要2分钟)。
最初的桶分类会加速你的进程,因为它在
与任何
在计算机科学中,这是有帮助的:我们收集了n个东西,一个订单(长度)和一个等价关系(额外的信息,例如袜子的颜色)。等价关系允许我们对原始集合进行分区,并且在每个等价类中,我们的顺序仍然保持不变。将一个事物映射到它的等价类可以在O(1)中完成,因此只需要O(n)就可以将每个项分配给一个类。现在我们已经使用了额外的信息,可以以任何方式对每个类进行排序。其优点是数据集已经非常小了。
该方法也可以嵌套,如果我们有多个等价关系->使颜色堆,比在每个堆内划分纹理,比排序长度。任何创建一个分区的等价关系,如果有两个以上的元素,并且大小都差不多,那么这将提高排序的速度(前提是我们可以直接将一个sock分配给它的堆栈),并且在较小的数据集上,排序可以非常快地进行。
这个问题实际上是深刻的哲学问题。从本质上讲,这是关于人们解决问题的能力(我们大脑的"湿器")是否等同于算法所能完成的。
袜子分类的一个明显算法是:
1 2 3 4 5 6 | Let N be the set of socks that are still unpaired, initially empty for each sock s taken from the dryer if s matches a sock t in N remove t from N, bundle s and t together, and throw them in the basket else add s to N |
现在这个问题的计算机科学都是关于步骤的。
人类将使用各种策略来实现这些目标。人类内存是关联的,类似于哈希表,其中存储值的特征集与相应的值本身配对。例如,"红色汽车"的概念映射到一个人能够记住的所有红色汽车。有着完美记忆的人有着完美的映射。大多数人在这方面(以及其他大多数人)都不完美。关联映射的容量有限。在各种情况下(一杯啤酒太多),映射可能会消失,可能会被错误地记录下来("我虽然她的名字是贝蒂,而不是内蒂"),也可能永远不会被覆盖,即使我们观察到真相已经改变("爸爸的车"在我们真正知道他把它换成红色卡马罗的时候会唤起"橙色火鸟")。
在袜子的情况下,完美的回忆意味着看一只袜子
记忆力不足的人可能会根据自己能力范围内的特征使用一些常识等价类来跟踪:大小(爸爸、妈妈、宝宝)、颜色(绿色、红色等)、图案(菱形、普通等)、风格(脚、膝盖高等)。因此,熨衣板将按类别划分为若干部分。这通常允许内存在固定时间内定位类别,但需要通过类别"bucket"进行线性搜索。
一个完全没有记忆和想象力的人(对不起)会把袜子放在一堆里,然后对整堆进行线性搜索。
一个整洁的怪胎可能会使用数字标签对,如有人建议的。这就打开了一扇完全排序的大门,这使得人们可以使用与CPU完全相同的算法:二进制搜索、树、哈希等。
因此,"最佳"算法取决于运行它的wetware/hardware/software的质量,以及我们对pairs施加一个总顺序来"欺骗"的意愿。当然,一个"最佳"的元算法是雇佣世界上最好的袜子分类机:一个人或机器,可以通过不断的时间查找、插入和删除,在1-1关联内存中获取并快速存储大量的袜子属性集。这样的人和机器都可以买到。如果你有一双,你可以在O(N)时间内为N双配对所有的袜子,这是最理想的。total order标签允许您使用标准散列来获得与人类或硬件计算机相同的结果。
你试图解决错误的问题。
解决方案1:每次你把脏袜子放进洗衣篮里,都要把它们打成一个小结。这样洗完之后你就不必做任何分类了。把它想象成在一个Mongo数据库中注册一个索引。为了将来节省一些CPU,需要提前一点工作。
解决方案2:如果是冬天,你不必穿配套的袜子。我们是程序员。没有人需要知道,只要它有效。
解决方案3:展开工作。您希望异步执行如此复杂的CPU进程,而不阻塞UI。把那堆袜子装进袋子里。只有在你需要的时候才找一双。这样一来,所需的工作量就不那么明显了。
希望这有帮助!
成本:移动袜子->高,查找/搜索行中的袜子->小
我们要做的是减少移动次数,并用搜索次数进行补偿。此外,我们还可以利用智人的多线程环境在描述缓存中保存更多的东西。
X=你的,Y=你的配偶
从所有袜子的A堆:
选择两个袜子,将相应的X袜子放在X行,Y袜子放在Y行的下一个可用位置。
直到A为空。
每行x和y
选择行中的第一个袜子,沿行搜索,直到找到对应的袜子。
放入相应的成品线袜子。
可以选择第一步,从该行中选择两个sock,而不是两个,因为缓存内存足够大,我们可以快速确定其中一个sock是否与您正在观察的行中的当前sock匹配。如果你有幸拥有三只手臂,那么考虑到主题的记忆足够大,你可以同时解析三只袜子。
直到X和Y都为空。
多恩
但是,由于选择排序的复杂性相似,因此由于I/O(移动SOCKS)和搜索(搜索行中的SOCK)的速度,所用的时间要少得多。
在基于比较的模型中,这里有一个ω(n log n)下界。(唯一有效的操作是比较两个SOCKS。)
假设你知道你的2N袜子是这样排列的:
P1P2P3pnpf(1)pf(2)pf(n)
其中f是集合1,2,…,n的未知排列。知道这一点并不能使问题更难解决。有N!可能的输出(上半部分和下半部分之间的匹配),这意味着您需要日志(n!)=欧米伽(n对数n)比较。这可以通过分类得到。
由于您对元素区分性问题的连接感兴趣:要证明元素区分性的ω(n log n)界限比较困难,因为输出是二进制的是/否。这里,输出必须是匹配的,并且可能的输出数量足以得到一个合适的界限。然而,有一种变体与元素的独特性有关。假设你有2N双袜子,想知道它们是否能唯一配对。您可以通过发送(a1,a2,…,an)到(a1,a1,a2,a,…,an,ann)。(换句话说,通过拓扑学,ED的硬度证明是非常有趣的。)
我认为,如果你只允许平等测试,就应该有一个欧米伽(n2)来约束原始问题。我的直觉是:考虑一个在测试后添加边的图,并认为如果图不密集,输出就不是唯一确定的。
我就是这样做的,对于P双袜子(n=2p双袜子):
- 从那堆袜子里随便拿一只。
- 对于第一双袜子,或者如果之前选择的所有袜子都已配对,只需将袜子放在前面的"一排"未配对袜子的第一个"槽"中。
- 如果您选择了一个或多个未配对的袜子,请对照数组中所有未配对的袜子检查当前的袜子。
- 在构建阵列时,可以将袜子分为一般类别或类型(白色/黑色、脚踝/圆领、运动型/连衣裙),并"向下钻取"以仅进行相似比较。
- 如果你发现一个可以接受的匹配,把两个袜子放在一起,然后把它们从数组中移除。
- 如果不这样做,请将当前sock放入阵列中第一个打开的插槽中。
- 每只袜子都要重复。
这个方案最坏的情况是,每双袜子都是完全不同的,必须完全匹配,而且你挑选的第一双N/2袜子都是不同的。这是你的O(n2)场景,这是极不可能的。如果唯一类型的袜子t的数量小于对的数量p=n/2,并且每种类型的袜子都足够相似(通常是与穿着相关的术语),以至于任何类型的袜子都可以与任何其他袜子配对,那么正如我在上面推断的那样,您将不得不比较的袜子的最大数量是t,之后,您拉的下一个将是把其中一只袜子脱掉。与最坏情况相比,这种情况更可能出现在普通的袜子抽屉中,并将最坏情况的复杂性降低到O(n*t),而O(n*t)通常是t<
现实世界方法:
尽可能快地将袜子从未分类的一堆中取出,一次一堆地放在你面前。桩的排列应该有一定的空间,所有的袜子都指向同一个方向;桩的数量受您可以轻松到达的距离的限制。选择要放袜子的那一堆应该——尽可能快地——把袜子放在一堆显然很像袜子的袜子上;可以容忍偶尔出现的I型(把一只袜子放在一堆它不属于的袜子上)或II型(把一只袜子放在它自己的袜子堆里,当有一堆类似袜子的时候)错误——最重要的考虑因素是操作就是速度。一旦所有的袜子成堆,快速穿过多个袜子堆,形成一对一对的袜子,然后将它们取下(这些袜子正朝抽屉走去)。如果袜子堆中有不匹配的,将它们重新堆到它们的最佳(在尽可能快的限制范围内)堆中。当所有的多袜子堆都处理好后,匹配由于II型错误而没有配对的剩余可配对袜子。哇哦,你完了——我有很多袜子,要等到大部分脏了才洗。另一个实用的注意事项是:我把一双袜子中的一只的顶部向下翻转,利用它们的弹性特性,这样它们就可以在被送到抽屉和放在抽屉里的时候呆在一起。
拿起第一只袜子放在桌子上。现在再挑选一只袜子;如果它与第一只相匹配,就把它放在第一只上面。如果没有,把它放在桌子上离第一个稍微远一点。挑选第三只袜子;如果它与前两只袜子中的任何一只相匹配,请将它放在它们上面,或者将它放在距离第三只稍远的地方。重复这个动作,直到你把所有的袜子都捡起来。
从你的问题来看,很明显你在洗衣方面没有太多实际经验。您需要一种算法,它可以很好地处理少量不可修复的袜子。
到目前为止,答案还不能很好地利用我们的人类模式识别能力。布景游戏提供了一个如何做好这件事的线索:把所有的袜子放在一个二维空间里,这样你们都能很好地识别它们,并且用手很容易地够到它们。这会限制你的面积在120*80厘米左右。从中选择您识别的对并将其删除。把多余的袜子放在空闲的地方,然后重复。如果你为那些穿着容易辨认的袜子的人洗衣服(小孩会想到),你可以先选择那些袜子来做基数排序。这种算法只有在单只袜子的数目很低的情况下才能很好地工作。
我提出了另一个解决方案,它既不保证减少操作,也不减少时间消耗,但应该尝试看看它是否足够好的启发式方法,以在一系列袜子配对中提供更少的时间消耗。
先决条件:不能保证有同样的袜子。如果它们的颜色相同,并不意味着它们的尺寸或图案相同。袜子是随机洗的。袜子的数量可能是奇数(有些丢失了,我们不知道有多少)。准备记住变量"index"并将其设置为0。
结果将有一个或两个桩:1。"匹配"和2"。"失踪"
启发式的:
此外,还可以增加检查损坏的袜子,好像那些被删除。它可以插在2和3之间,13和14之间。
我期待听到任何经验或纠正。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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); } } |
为了说明从一堆袜子中配对有多有效,我们必须首先定义机器,因为无论是通过图灵还是随机访问机器(通常用作算法分析的基础)都不会进行配对。
机器机器是一个抽象的现实世界元素称为人类。它能通过一双眼睛从环境中读出信息。我们的机器模型能够使用两个臂来操纵环境。逻辑运算和算术运算是用我们的大脑计算的(希望是;-)。
我们还必须考虑可以用这些仪器执行的原子操作的内在运行时。由于物理约束,手臂或眼睛执行的操作具有非恒定的时间复杂性。这是因为我们不能用胳膊移动无限大的袜子,也不能用眼睛看到无限大的袜子上的最上面的袜子。
然而,机械物理学也给了我们一些好处。我们不局限于用手臂最多移动一只袜子。我们可以一次移动一对。
因此,根据前面的分析,应按降序使用以下操作:
- 逻辑运算和算术运算
- 环境读数
- 环境改造
我们也可以利用这样一个事实:人们只有非常有限的袜子。因此,环境改造可能涉及到所有的袜子堆。
算法所以我的建议是:
操作4是必要的,因为当把袜子铺在地板上时,有些袜子可能会把其他的藏起来。下面是算法分析:
分析算法以很高的概率终止。这是因为在步骤2中找不到成对的袜子。
对于以下对
- 算法涉及到
O(ln n + n) 环境修改(步骤1O(ln n) 加上从地板上挑选每一双袜子) - 该算法涉及步骤2中的
O(n^2) 环境读取。 - 该算法包括
O(n^2) 逻辑运算和算术运算,用于在步骤2中比较一个SOCK与另一个SOCK。
因此,我们有一个
当我整理袜子时,我会做一个近似的基数排序,把袜子放在同一颜色/图案类型的其他袜子旁边。除非我能在我要丢袜子的地方/附近看到一个精确的匹配,否则我会在那一点提取这双袜子。
几乎所有其他算法(包括USR的最高分答案)排序,然后删除对。我发现,作为一个人,最好减少一次考虑的袜子数量。
我这样做是:
这利用了人在O(1)时间内进行模糊匹配的能力,这在某种程度上相当于在计算设备上建立哈希图。
首先,通过拉动与众不同的袜子,您可以留出空间"放大"那些不那么与众不同的功能。
在去除了绒毛色、条纹袜子和三双长袜之后,你可能最终会发现大部分是白色的袜子,根据它们的磨损程度大致分类。
在某种程度上,袜子之间的差异非常小,以至于其他人不会注意到差异,不需要进一步的匹配工作。
SOCK,无论是真实的还是类似的数据结构,都将成对提供。
最简单的答案是,在允许对进行分离之前,应该初始化对的单个数据结构,该结构包含指向左右袜子的指针,从而使袜子可以直接或通过它们的对引用。袜子也可以扩展为包含指向其伙伴的指针。
这通过用一个抽象层删除任何计算配对问题来解决。
把同样的想法应用到袜子配对的实际问题上,显而易见的答案是:永远不要让你的袜子不成对。袜子是成对提供的,成对放在抽屉里(也许是把它们揉成一团),成对穿。但是,脱毛的可能点是在洗衣机里,所以所需要的只是一种物理机制,使袜子能够保持在一起并有效地清洗。
有两种物理可能性:
对于每只袜子上都有一个指针的"配对"物品,我们可以用一个布袋把袜子放在一起。这似乎是巨大的开销。
但是对于每一只袜子来说,要保持对另一只的引用,有一个很好的解决方案:一个popper(如果你是美国人,也可以是一个"snap button"),比如:
http://www.aliexpress.com/compare/compare-invisible-snap-buttons.html
然后,你所要做的就是在你脱下袜子并把它们放进你的洗衣篮后把它们合在一起,然后你又一次消除了需要将袜子与"对"概念的物理抽象相结合的问题。
每当你拿起一只袜子,把它放在一个地方。然后你拿起的下一只袜子,如果与第一只不匹配,就放在第一只旁边。如果有,就有一对。这样,无论有多少种组合都不重要,而且你挑选的每只袜子只有两种可能——要么是你的袜子阵列中已经有了一个匹配项,要么不是,这意味着你要把它添加到阵列中的某个位置。
这也意味着你几乎肯定不会把所有的袜子都放在阵列中,因为袜子在匹配时会被移除。
考虑一个大小为"n"的哈希表。
如果假设为正态分布,那么将至少一个sock映射到一个bucket的估计"插入"数为nlogn(即,所有bucket都已满)。
我把它作为另一个谜题的一部分推导出来,但我很高兴被证明是错的。这是我的博客文章
让"n"对应于您拥有的袜子的唯一颜色/图案数量的近似上限。
一旦发生碰撞(也就是说,火柴),只需脱掉那双袜子。对下一批NLogn袜子重复相同的实验。它的美妙之处在于,由于人类思维的工作方式,您可以进行非登录并行比较(冲突解决)。-)
如果"移动"操作相当昂贵,"比较"操作也很便宜,而且您无论如何都需要将整个集移动到一个缓冲区中,在这个缓冲区中搜索要比原始存储快得多…只需将排序集成到强制移动中。
我发现把分类的过程整合到挂干的过程中会让它变得轻而易举。不管怎样,我需要拿起每只袜子,把它挂起来(移动),把它挂在绳子上的某个特定位置也不需要花多少钱。现在,为了不强制搜索整个缓冲区(字符串),我选择按颜色/阴影放置袜子。左边更暗,右边更亮,前面颜色更鲜艳等等。现在在我挂上每只袜子之前,我先看看它的"右边附近",如果已经有一只匹配的袜子的话——这将把"扫描"限制在另外2-3只袜子的范围内——如果是的话,我把另一只挂在它的右边。然后,我把它们成对卷起来,当它们干燥时,从弦上取下。
现在,这似乎与上面答案建议的"颜色成桩"没有什么不同,但首先,通过不选择离散的桩,而是范围,我对"紫色"是"红色"还是"蓝色"桩没有问题,只是介于两者之间。然后,通过集成两个操作(挂起晾干和排序),挂起时的排序开销就像是单独排序的10%。
我希望我能为这个问题做出新的贡献。我注意到所有的答案都忽略了这样一个事实,即有两个点可以在不降低整体洗衣性能的情况下执行预处理。
此外,我们不需要假设大量的袜子,即使是对于大家庭。袜子是从抽屉里拿出来穿的,它们被扔到一个地方(可能是一个垃圾箱),放在那里,然后才被洗。虽然我不会称之为后进先出法栈,但我认为这是安全的。
因为我所知道的所有洗衣机的尺寸都是有限的(不管你要洗多少只袜子),实际随机化发生在洗衣机上,不管我们有多少只袜子,我们总是有小的子集,几乎不包含单件。
我们的两个预处理阶段是"把袜子放在晾衣绳上"和"把袜子从晾衣绳上取下来",我们必须这样做,才能得到既干净又干燥的袜子。就像洗衣机一样,晾衣绳是有限的,我假设我们有一整条线,我们把袜子放在那里。
这是将"袜子"放在"线"上的算法:
1 2 3 4 5 6 7 8 9 | 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),我们也需要把它们放在线上不排序。这些袜子还没有配对,我们只在生产线上有几个相似的集群。我们这里的袜子有限,这很有帮助,因为这有助于我们创建"好"的分类(例如,如果一组袜子中只有黑色的袜子,那么按颜色分类就不是解决问题的方法)。
下面是从_Line()中获取_socks_的算法:
1 2 3 4 5 6 7 | 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%的袜子已经配对,只剩下一个非常小的袜子子集,这个子集已经(有点)聚集在一起(在预处理步骤之后,我不会在我的篮子中引入太多的熵)。另一件事是,剩下的集群往往足够小,可以立即处理,因此可以将整个集群从篮子中取出。
以下是排序剩余的集群的算法:
1 2 3 4 5 | while(clusters present in basket) { Take out the cluster and spread it Process it immediately Leave remaining socks where they are } |
之后,只剩下几只袜子了。在这里,我将以前未配对的袜子引入系统,在没有任何特殊算法的情况下处理剩余的袜子-剩余的袜子非常少,并且可以很快地进行视觉处理。
对于剩下的所有袜子,我假设它们的对应物仍然是未洗的,并在下一次迭代中将它们放在一边。如果你登记了一段时间内未成对袜子的增长(一个"袜子泄漏"),你应该检查你的箱子-它可能会被随机(你有猫谁睡在那里?)
我知道这些算法有很多假设:一个像后进先出栈的箱子,一台有限的、正常的洗衣机,以及一条有限的、正常的晾衣绳——但这仍然适用于大量的袜子。
关于并行性:只要你把两个袜子都扔到同一个箱子里,你就可以很容易地把所有这些步骤并行起来。
我刚刚完成了我的袜子配对,我发现最好的方法是:
- 选择其中一只袜子并把它收起来(为这双袜子做一个"桶")。
- 如果下一个是前一个的对,那么把它放到现有的bucket中,否则创建一个新的bucket。
在最坏的情况下,这意味着你将有n/2个不同的桶,你将有n-2个关于哪个桶包含当前的袜子的决定。显然,如果你只有几个对,这个算法会很好地工作;我用12个对来完成。
这不是很科学,但很有效。)
我已经采取了一些简单的步骤,将我的工作减少到一个花费O(1)时间的过程中。
通过将我的投入减少到两种袜子中的一种(休闲用的白色袜子,工作用的黑色袜子),我只需要确定手头上有两种袜子中的哪一种。(从技术上讲,因为它们从未一起清洗过,所以我将处理时间缩短到了O(0)次)
为了找到合适的袜子,并购买足够数量的袜子,以消除对现有袜子的需求,需要预先做一些努力。在我需要黑色袜子之前,我已经做了这件事,我的努力是最小的,但里程数可能会有所不同。
这种预先的努力在非常流行和有效的代码中被多次看到。例子包括将pi定义为几个小数(也有其他的例子,但这就是现在想到的例子)。
做些预处理怎么样?我会在每只袜子上缝上一个标记或ID号,这样每双袜子都有相同的标记/ID号。这个过程可能会在你每次买新袜子的时候完成。然后,你可以做一个基数排序来得到总成本。为每个马克/身份证号码找一个地方,把所有的袜子一个一个地挑起来放在正确的地方。
创建一个哈希表,该表将用于不匹配的SOCKS,使用模式作为哈希。一个接一个地迭代袜子。如果sock在哈希表中有模式匹配,则将sock从表中取出并配对。如果袜子不匹配,把它放在桌子上。
我的解决方案并不完全符合您的要求,因为它正式要求
在我的情况下,特殊的情况是我不使用烘干机,只要把我的衣服挂在一个普通的烘干机上就行了。挂布需要
很明显,需要做一些额外的工作来检查是否有匹配的袜子已经挂在某个地方,它将为计算机提供系数为
因此,将袜子搭配问题与挂布问题联系起来,我免费得到了
把你的N双袜子分类的问题是O(N)。在你把它们扔进洗衣篮之前,先把左边的穿到右边。把它们拿出来后,你剪下线,把每一对放进你的抽屉里——在n对上做2个操作,所以o(n)。
现在,下一个问题就是你是否自己洗衣服,你妻子是否自己洗。这可能是一个完全不同领域的问题。:)
在我的博士学位(计算机科学)期间,我经常想到这一点。根据区分袜子的能力,我想出了多种解决方案,从而尽可能快地找到正确的袜子。
假设看袜子和记住它们独特的图案的成本可以忽略不计(ε)。最好的解决办法就是把所有的袜子都扔到桌子上。这包括以下步骤:
这确实是最快的可能性,并且在n+1=o(n)复杂性中执行。但它假设你完全记得所有的模式…实际上,情况并非如此,我个人的经验是,您有时在第一次尝试时找不到匹配的一对:
这取决于我们找到匹配对的能力。如果你穿的是深色/灰色的运动袜或白色的运动袜,它们的图案通常非常相似,那么这一点尤其正确!我们承认你有可能找到相应的袜子。在找到相应的袜子形成一对之前,平均需要1/p的尝试。总体复杂度为1+(n/2)*(1+1/p)=o(n)。
两者都是线性的袜子数量和非常相似的解决方案。让我们稍微修改一下这个问题,承认你有多双相似的袜子,而且很容易一次就存储多双袜子(1+ε)。对于k个不同的模式,您可以实现:
总的复杂性变为n+k=o(n)。它仍然是线性的,但是选择正确的算法现在可能很大程度上取决于p和k的值!但有人可能会再次反对,您可能很难为每个袜子找到(或创建)集群。
此外,你也可以通过在网站上寻找最佳算法并提出自己的解决方案来放松时间:)
两条思路,找到任何匹配项所需的速度,与找到所有匹配项所需的速度(与存储相比)。
对于第二个案例,我想指出一个GPU并行版本,它查询所有匹配的SOCKS。
如果您有多个要匹配的属性,那么为了简单起见,可以使用分组元组和Fancier Zip迭代器以及pust的转换函数,尽管这里有一个基于GPU的简单查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | //test.cu #include <thrust/device_vector.h> #include <thrust/sequence.h> #include <thrust/copy.h> #include <thrust/count.h> #include <thrust/remove.h> #include <thrust/random.h> #include <iostream> #include <iterator> #include <string> // Define some types for pseudo code readability typedef thrust::device_vector<int> GpuList; typedef GpuList::iterator GpuListIterator; template <typename T> struct ColoredSockQuery : public thrust::unary_function<T,bool> { ColoredSockQuery( int colorToSearch ) { SockColor = colorToSearch; } int SockColor; __host__ __device__ bool operator()(T x) { return x == SockColor; } }; struct GenerateRandomSockColor { float lowBounds, highBounds; __host__ __device__ GenerateRandomSockColor(int _a= 0, int _b= 1) : lowBounds(_a), highBounds(_b) {}; __host__ __device__ int operator()(const unsigned int n) const { thrust::default_random_engine rng; thrust::uniform_real_distribution<float> dist(lowBounds, highBounds); rng.discard(n); return dist(rng); } }; template <typename GpuListIterator> void PrintSocks(const std::string& name, GpuListIterator first, GpuListIterator last) { typedef typename std::iterator_traits<GpuListIterator>::value_type T; std::cout << name <<":"; thrust::copy(first, last, std::ostream_iterator<T>(std::cout,"")); std::cout <<" "; } int main() { int numberOfSocks = 10000000; GpuList socks(numberOfSocks); thrust::transform(thrust::make_counting_iterator(0), thrust::make_counting_iterator(numberOfSocks), socks.begin(), GenerateRandomSockColor(0, 200)); clock_t start = clock(); GpuList sortedSocks(socks.size()); GpuListIterator lastSortedSock = thrust::copy_if(socks.begin(), socks.end(), sortedSocks.begin(), ColoredSockQuery<int>(2)); clock_t stop = clock(); PrintSocks("Sorted Socks:", sortedSocks.begin(), lastSortedSock); double elapsed = (double)(stop - start) * 1000.0 / CLOCKS_PER_SEC; std::cout <<"Time elapsed in ms:" << elapsed <<" "; return 0; } //nvcc -std=c++11 -o test test.cu |
1000万只袜子的运行时间:9毫秒
我提出的解决方案假设所有的袜子在细节上都是相同的,除了颜色。如果有更多的细节需要在袜子之间延迟,那么在我的示例中,这些细节可以用来定义不同类型的袜子,而不是颜色。
考虑到我们有一堆袜子,一只袜子可以有三种颜色:蓝色、红色或绿色。
然后我们可以为每种颜色创建一个并行工作程序;它有自己的列表来填充相应的颜色。
1 2 3 4 5 6 7 | At time i: Blue read Pile[i] : If Blue then Blue.Count++ ; B=TRUE ; sync Red read Pile[i+1] : If Red then Red.Count++ ; R=TRUE ; sync Green read Pile [i+2] : If Green then Green.Count++ ; G=TRUE ; sync |
同步过程:
1 2 3 4 5 6 7 8 | Sync i: i++ If R is TRUE: i++ If G is TRUE: i++ |
这需要初始化:
1 2 3 4 5 6 7 8 | Init: If Pile[0] != Blue: If Pile[0] = Red : Red.Count++ Else if Pile[0] = Green : Green.Count++ If Pile[1] != Red: If Pile[0] = Green : Green.Count++ |
在哪里?
1 2 3 4 5 6 7 8 9 10 11 | Best Case: B, R, G, B, R, G, .., B, R, G Worst Case: B, B, B, .., B Time(Worst-Case) = C * n ~ O(n) Time(Best-Case) = C * (n/k) ~ O(n/k) n: number of sock pairs k: number of colors C: sync overhead |
一种高效的袜子成堆配对算法
先决条件
算法
尝试:
除:
桌子不够大:&小心地将所有未成对的袜子混合在一起,然后继续操作。&//此操作将导致新的堆和空表
桌上没有袜子:&扔(最后一只不透气的袜子)
一堆袜子都没有了:&退出洗衣房
最后:
- 如果还有袜子在堆里:&转到3
已知问题
如果周围没有表或桌子上没有足够的地方放至少一只袜子。
可能的改进
根据要排序的SOCKS数量,吞吐量可以是把桌子上的袜子分类,只要有足够的空间。
为了使其工作,需要一个具有唯一属性的属性每双袜子的价值。这样的属性很容易由袜子的视觉特性合成。
按上述属性对桌上的袜子进行分类。我们称之为属性"颜色"。把袜子排成一排,把颜色较深的袜子放在右边(即,push_back())和左边较浅颜色的袜子(即Purth-Frimth.()
对于巨大的堆积物,尤其是以前看不见的袜子,属性合成可能需要大量的时间,因此吞吐量将明显下降。但是,这些属性可以在内存中持久化并重用。
需要进行一些研究来评估这种可能性的有效性。改进。出现以下问题:
- 用上面的方法搭配袜子的最佳数量是多少改进?
- 对于给定数量的袜子,之前需要多少次迭代吞吐量增加?a)最后一次迭代b)对于所有迭代
POC符合MCVE指南:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #include <iostream> #include <vector> #include <string> #include <time.h> using namespace std; struct pileOfsocks { pileOfsocks(int pairCount = 42) : elemCount(pairCount<<1) { srand(time(NULL)); socks.resize(elemCount); vector<int> used_colors; vector<int> used_indices; auto getOne = [](vector<int>& v, int c) { int r; do { r = rand() % c; } while (find(v.begin(), v.end(), r) != v.end()); v.push_back(r); return r; }; for (auto i = 0; i < pairCount; i++) { auto sock_color = getOne(used_colors, INT_MAX); socks[getOne(used_indices, elemCount)] = sock_color; socks[getOne(used_indices, elemCount)] = sock_color; } } void show(const string& prompt) { cout << prompt <<":" << endl; for (auto i = 0; i < socks.size(); i++){ cout << socks[i] <<""; } cout << endl; } void pair() { for (auto i = 0; i < socks.size(); i++) { std::vector<int>::iterator it = find(unpaired_socks.begin(), unpaired_socks.end(), socks[i]); if (it != unpaired_socks.end()) { unpaired_socks.erase(it); paired_socks.push_back(socks[i]); paired_socks.push_back(socks[i]); } else unpaired_socks.push_back(socks[i]); } socks = paired_socks; paired_socks.clear(); } private: int elemCount; vector<int> socks; vector<int> unpaired_socks; vector<int> paired_socks; }; int main() { pileOfsocks socks; socks.show("unpaired socks"); socks.pair(); socks.show("paired socks"); system("pause"); return 0; } |
如果您可以将一对袜子抽象为密钥本身,将另一对作为值,那么我们可以使用散列作为我们的杠杆。好的。
在地板上做两个想象的部分,一个给你,另一个给你的配偶。好的。
从一堆袜子里拿一个。好的。
现在把袜子一个一个地放在地板上,遵循下面的规则。好的。
把袜子当成你的或她的,看看地板上的相关部分。好的。
如果你能在地板上发现这对情侣,把它捡起来打结,或者把它们夹起来,或者在找到一对后做你想做的任何事情,然后把它放在篮子里(从地板上取下)。好的。
把它放在相关的部分。好的。
重复3次,直到所有的袜子都从绒头上掉下来。好的。
说明:好的。
散列和抽象好的。
抽象是一个非常强大的概念,已被用来改善用户体验(UX)。在与计算机的实际交互中,抽象的例子包括:好的。
- 用于在GUI(图形用户界面)中导航以访问地址的文件夹图标,而不是键入实际地址以导航到某个位置。
- GUI滑块用于控制不同级别的音量、文档滚动位置等。
Hashing or other not-in-place solutions are not an option because I am not able to duplicate my socks (though it could be nice if I could).
Ok.
我相信询问者正在考虑应用散列法,以便在放置袜子之前知道每双袜子的插槽。好的。
这就是为什么我建议抽象一个放在地板上的袜子作为散列键本身(因此不需要复制袜子)。好的。
如何定义哈希键?好的。
如果有不止一双相似的袜子,下面对我们的钥匙的定义也会起作用。也就是说,有两对黑男人袜子,一对一对,每一只都叫paira-l,paira-r,pairb-l,pairb-r,所以paira-l可以和pairb-r配对,但是paira-l和pairb-l不能配对。好的。
假设任何袜子都可以被好的。
这是我们的第一个哈希函数。让我们用一个简短的符号来表示这个
另一个消除左属性或右属性的哈希函数是
- 要定位插槽,请使用h2(g_c_m_t1_t2)。
- 一旦找到插槽,则使用h1(x)检查其散列值。如果它们不匹配,你就有一双。否则把袜子扔到同一个槽里。
注意:因为我们一旦找到一对就删除了它,所以可以安全地假设只有一个最大的插槽具有唯一的h2(x)或h1(x)值。好的。
如果每个袜子只有一对匹配,那么使用h2(x)查找位置,如果没有,则需要进行检查,因为可以安全地假设它们是一对。好的。
为什么把袜子放在地上很重要好的。
让我们考虑一个场景,在这个场景中,袜子一堆一堆地堆放在一起(最糟糕的情况)。这意味着我们将别无选择,只能进行线性搜索来找到一对。好的。
将它们铺在地板上可以提供更多的可视性,从而提高发现匹配袜子(匹配哈希键)的机会。当在步骤3中把一只袜子放在地板上时,我们的大脑已经下意识地记录了位置。-因此,如果这个位置在我们的内存中可用,我们可以直接找到匹配的对。-如果位置不被记住,不用担心,那么我们可以恢复线性搜索。好的。
为什么从地板上拆下这双鞋很重要?好的。
- 短期的人类记忆在需要记忆的项目较少时效果最好。这样就增加了我们使用散列法来发现这对数据的可能性。
- 它还将减少使用线性搜索配对时要搜索的项目数。
分析好的。
- 比较上限:o(n^2)。
- 比较下限:(n/2)。(其他每一只德皮纳袜子都是前一只的时候)。
- 比较上限:o(n/2)。
- 比较下限:o(n/2)。
我说的是比较操作,从堆里拣袜子一定是N个操作。所以一个实际的下界是n个迭代和n/2比较。好的。
加快速度好的。
为了获得一个完美的分数,让德普得到O(n/2)比较,我建议德皮纳,好的。
- 花更多的时间来熟悉袜子。是的,这也意味着要花更多的时间在德普的袜子上。
- 像在网格中玩点对这样的内存游戏可以提高短期内存性能,这是非常有益的。
这等价于元素的清晰度问题吗?好的。
我建议的方法是用来解决元素明显性问题的方法之一,将它们放在哈希表中进行比较。好的。
考虑到您的特殊情况,其中只存在一个精确对,它已经变得非常等价于元素独特问题。因为我们甚至可以对袜子进行分类并检查相邻的袜子是否成对(EDP的另一种解决方案)。好的。
但是,如果对于给定的sock有可能存在多对,那么它将偏离EDP。好的。好啊。