有效地删除数字集内的空白空间

时间:2012-04-22 02:41:53

标签: algorithm packing

我将使用Python语法和对象来表示问题,但实际上它适用于SQL数据库中的模型,使用Python API和ORM。

我有一个这样的数字列表:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

有时会删除一些数字并保留空格:

[0, 1, 2, None, None, 5, 6, None, None, None, 10]

我需要做的是在定期完成的维护步骤中有效地打包这组数字,无论是有序还是无序,这样数字之间不会留有空格:

所以,按照有序的方式,我需要该列表成为:

[0, 1, 2, 5, 6, 10, None, None, None, None, None]

当无序时,每个数字的去向并不重要,只要它们之间没有空格。

数字可以在连续的块中移动,并且向左或向右移动任意数量的位置都是相同的,但是有一个设置和拆卸成本,这使得移动更大的块并在更少的块中实现它更有效尽可能更新。

现在我正在使用最简单的解决方案,查找连续数字的块并将它们一次移动到最近的左边一个块,直到它被打包为止。因此,在示例中,5,6在单个更新中向左移动2个块,然后在另一个更新中向左移动10个块。

[0, 1, 2, None, None, 5, 6, None, None, None, 10]

[0, 1, 2, 5, 6, None, None, None, None, None, 10]

[0, 1, 2, 5, 6, 10, None, None, None, None, None]

这种琐碎的方法似乎是最有效的,当订单很重要时,但实际上我的大部分操作都是无序的,我认为应该有更好的方法。例如,在这种情况下,可以通过在0到10之间移动0,1,2块来将列表打包在单个更新中:

[None, None, None, None, None, 5, 6, 0, 1, 2, 10]

实际上会有数千块,但我事先知道每个块的大小和每个间隙。与组合之间的大小和间隙之间的计算相比,移动块也非常昂贵,因此找到最佳解决方案是理想的。

这似乎是一种装箱问题,但我真的不知道如何找到最佳解决方案。有什么想法吗?

3 个答案:

答案 0 :(得分:3)

对于无序情况,假设某人告诉您最终连续块应填充的空格。然后一个启发式的假设是,如果你先将这个区域之外的最大块移动到它中,那么一切都会适合你,你不必打破任何阻塞。正如评论中所建议的那样,你可以用它来运行A *(或分支和绑定)。然后你的第一个决定是最终连续的块应该在哪里,但这只是A * /分支和绑定的另一个层次 - 实际上在这个启发式下,最有希望的最终连续区域将是当前持有最大数量的填充区域在子区域中,因为您假设您只需要在此区域之外的子区域中移动。

如果你确实发现这太贵了,加速分支和绑定的一种方法是以获得更差的答案为代价,放弃可能的答案,这些答案可以提高到目前为止发现的最佳答案仅为X% X

实际上我认为你可以获得比这更好的下限 - 最大(目标区域中单独的连续间隙的数量,从源区域移入的独立连续区域的数量)应该稍微好一点,因为一个移动可以最好移动到一个连续的数字区域,并在目标区域填补一个空隙。

获得下限的一种简单方法是忽略对问题的足够约束以使其变得容易。假设未知的正确答案仍然是一个可行的解决方案,这必须给你一个下限,因为对于弱化问题的最佳解决方案必须至少与未知的正确答案一样好。您可以通过假装两个更新永远不会相互冲突来将此问题应用于您的gappy更新问题。给定指定的目标区域,计算此启发式等于找到将源区域切割成块的最佳方式,每个块都适合目标区域。您可以使用动态程序解决此问题:通过考虑源区域的最后k个单元格中的所有可能的复制方法,然后增加复制成本,您可以为源区域的前n + 1个单元找出最佳答案在源区的前n + 1-k个单元格中,您已经计算出来了。不幸的是,我不知道这种启发式是否足够强大而且有用。

答案 1 :(得分:2)

您描述的问题称为compaction problem。在经典的压缩问题(有序和无序变体)中,数据移动的成本并不那么令人望而却步。因此,通过使用辅助存储器并在单个线性扫描中将非空条目复制到辅助存储器中,可以简单地解决这个问题。新的压缩存储可以简单地替换原始存储或复制到原始存储,具体取决于上下文。现在,所有这些都可以在线性时间内完成,并且仅使用线性附加存储。因此,在bin-packing的意义上,它不被认为是一个难题。对于豆类包装,无论是否允许线性数量的额外存储,绝对没有简单的解决方案。所以,显然我们在这里处理的不是装箱。

当数据移动成本高时,现在存在额外的约束,即最小化非连续数据块的移动次数。可以将此问题视为两个问题之一的实例:

  1. 二进制数组的就地排序。在这里,您将数组建模为仅包含两种数据 - 0和1。在您的情况下,使用谓词isNull(a)可以很容易地实现这一点,该谓词为空数据条目返回1,对非空数据条目返回0。我能想到的最简单的解决方案是使用Selection Sort来排序二进制数组。在最坏的情况下,它永远不会超过 O(n)数据移动,即使它可以使 O(n 2 的数量为比较,但你不介意,因为你只想最小化数据移动的数量。如果没有数据要移动,它就不会做任何事情!有些改进可能会使事情变得复杂:

    • 交换块而不是单个条目。我的意思是,只有零块更大时才能交换两个块(一个零和另一个块)。您还可以使用贪心启发式,即下一个交换始终是最小化这两个的绝对差异的那个,即abs(len(zeroBlock) - len(oneBlock))。这只适用于您的问题的无序实例。
    • 另外两个优化是进行预处理以确定天气以升序或降序排序。
    • 此外,您可能希望排除列表的连续末尾。
  2. <强> Garbage Compaction 即可。本质上,我们的想法是将自由空间视为内存中需要进行垃圾收集的解除分配空间。为此,让我向您推荐这个有趣的SO discussion threadthis one。您可能还会发现此research paperthis one有用。

  3. 祝你好运!

答案 2 :(得分:1)

#include <stdio.h>
#include <string.h>

#define IS_EMPTY(c) ((c) <= '@')

unsigned moverup(char buff[], unsigned size)
{
unsigned src,dst,cnt;

for (src=dst=cnt=0; src < size; src++ ) {
        if (!IS_EMPTY(buff[src])) { cnt++; continue; }
        if (!cnt) continue;
ugly:
        memmove(buff+dst, buff+src-cnt, cnt );
        dst += cnt;
        cnt = 0;
        }
if (cnt) goto ugly;
return dst;
}

int main(void)
{
unsigned result;
char array[] = "qwe@rty@ui#op";

printf("Before:%s\n", array );

result = moverup (array, strlen (array) );

printf("result:%u\n", result );
// entries beyond result will contain garbage now.
// array[result] = 0;
printf("After:%s\n", array );

return 0;
}