当C程序运行时,它通常通过参数ARGC(参数计数)和argv(参数向量)接收任何命令行参数。该程序是解释该字符串数组的内容。
有很多方法可以这样做,其中一个更简单的解决方案是getoptfunction(以下,getopt将引用getopt和getopt_long).one扩展一些getopt实现优惠,它们在处理它时重新排序Argv的argnectents,导致数组首先出现所有选项,然后是非活动。
此重新排序分区数组。并且我们想要一个稳定的分区,所以所有选项的相关顺序,并且所有的非活动都是相同的。
去年我发布了Parg,一个简单的解析器,用于argv,它与getopt类似。它有一个单独的函数parg_reorder(),它执行此功能。我使用了一个简单的算法 - 分配临时数组的argv大小,解析参数,并从新阵列的末尾填写startand非活动的选项。然后将它们复制回来,将非活动倒在过程中的原始订单中。在后威尔,我可以立即将期权移入argv的最后一个地方。
这及时运行,但也需要额外的空间。条件要求可能是一个问题(例如嵌入式系统),索勒格看看我们是否可以做得更好。
以下代码示例是伪代码,并浏览一天。有关工作C ++代码,请参阅GitHub上的示例。
我们注意,如果我们已经分区了数组的两半,我们可以通过交换和来计算两者的分区。在阵列中交换相邻块有时旋转,并且可以在线性时间完成,例如使用反转(观察)。
所以我们可以使用鸿沟和征服。使用递归函数,该函数将在中间的阵列中进行,这会及时运行并需要堆栈空间。
stable_partition_recuraive(第一,n,pred){if(n == 1){return pred(first)?第一+ 1:第一; left = stable_partition_rocuraive(第一,n / 2,pred);中间=第一+ n / 2;右= stable_partition_rocuraive(中间,n - n / 2,pred);旋转(左,中,右);返回左+(右中间); }
更好,但理想情况下,我们希望只使用恒定的额外空间。要实现这一点,我们可以使用与自下而上的合并排序相同的技术。我们首先在尺寸2的块中处理阵列,然后在大小4的块中(其大小2的两半是alereaparted的两半),等等,直到我们处理整个阵列作为一个块。
stable_partition_bottomup(first,n,pred){for(width = 1;宽度 从某种意义上说,我们支付的价格避免递归是我们不记得两半的分区点,需要再次找到它们。易于使用谓词时间而不是。 但是,由于两半已经分区,我们可以使用二进制搜索来查找分区点。这减少了谓词ApplicationSto(自从)的数量,我们具有与递归算法的相同时间复杂性,但使用恒定的额外空间。 stable_partition_bsearch(first,n,pred){for(宽度= 1;宽度 如果数组几乎分区,则这些算法仍然通过EveryStep。我们可以做一些类似于自然合并排序的东西,并重复需要旋转的下一个地方,如果我们发现没有,则停止。虽然必须小心,但如果我们只是寻找区域并旋转它们,我们会在交替上获得二次时间模式。相反,我们可以寻找并旋转中间两个,就像上面一样。 stable_partition_natural(首先,最后一个,pred){do {next = first;变化= false; do {left = std :: find_f_not(下一个,最后一个,pred);中间= std :: find_if(左,最后,pred);右= std :: find_f_not(中间,最后一个,pred); next = std :: find_if(右,最后,pred);如果(左!=中间&中间!=右){旋转(左,中,右);变化=真; }} while(下一个!=最后); } while(改变); } 尽可能在此过程中,虽然最坏的情况复杂性是相同的自下而上版本。由于宽度不再是固定的大小,因此我们对分区点的线性搜索拓扑进行了拓扑搜索,因此备注的数量返回。此外,我们必须使用predicateo找到中间和下一个起点,所以我们可能会使用比自下而上的版本更多的档案。 有一种算法可以在使用Conforstant额外空间(PDF)中执行稳定的分区,但它有些涉及,并且给定特定任务的约束不实际。 让我们回到重新排序的论点问题。这里有一个问题是WeCannot判断任何给定的元素是否是一个选项,选项参数或anonoption,而无需将整个数组解析到该点(即,我们不能随机访问)。 这是因为任何给定的元素都可以通过使用它的选项来之前,它选项参数(查看前一个元素是不够的,真的可能是一个选项参数而不是一个选项)。或者可能存在 - 某个地方,这意味着剩下的元素是非选择(除非该剩余元素实际上是前一个选项的选项参数)。 这使得自然算法具有良好的契合,因为它在每个循环中从左到右施加雷尼比。 那么这一切如何与Getopt实现进行比较? 通过扫描来自ChiCurrent位置的任何非活动,然后将其扫描到每个呼叫中的两个呼叫中的重新排序,然后将它们旋转到阵列的末尾。 此效率地构建了最后的非活动阵列,同时向下移动尚未处理的部分。 这需要恒定的额外空间,但需要最坏的情况。这可能理论上可以在算法复杂性攻击中使用,大多数系统以某种方式限制命令行参数的大小。 作为最坏情况行为的一个例子,下行使用200,000nOnOptions运行LS(重定向缺少文件的错误消息),需要大约0.3秒(Fedora 24在VM中运行): 而这一行以相同数量的参数运行LS,但allowaneNonOptions和选项(-a选项启用启动句点的文件,并且在这种情况下没有效果),并且需要11秒: