Algorithm 有效地删除一组数字中的空白

Algorithm 有效地删除一组数字中的空白,algorithm,packing,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] 我需要做的是在定期进行的维护步骤中高效地打包这组数字,无论是有序的还是无序的,这样数字之间就不会有空空格: 因此,以有序的方式,我需要将该列表变成

我将使用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在另一次更新中向左移动5个块

[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]
这种琐碎的方法似乎在顺序重要时最有效,但实际上我的大多数操作都是无序的,我认为应该有更好的方法。例如,在这种情况下,通过在6和10之间移动0、1、2块,可以在一次更新中压缩列表:

[None, None, None, None, None, 5, 6, 0, 1, 2, 10]
实际上会有数千个街区,但我事先知道每个街区和每个缺口的大小。与大小和间隙之间的组合计算相比,移动块也非常昂贵,因此找到最佳解决方案是理想的选择


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

对于无序情况,假设有人告诉您最后一个连续块应该填充哪些空间。然后一个启发式假设是,如果你先把这个区域外最大的块移动到它里面,那么所有的东西都会适合你,你不需要把任何块分开。正如评论中所建议的,您可以使用此命令运行*(或分支和绑定)。然后,您的第一个决定是最终连续块应该位于何处,但这只是*/分支和边界的另一个级别-事实上,在这种启发下,最有希望的最终连续区域将是当前拥有最多填充子区域的区域,因为你假设你只需要在这个区域之外的子区域移动

如果你发现这太贵了,加快分支和绑定速度的一种方法是放弃可能的答案,因为这些答案可能只会使目前为止找到的最佳答案提高X%,而代价是得到更差的答案

事实上,我认为你可以得到一个稍微好一点的下界——max(目标区域中的独立连续间隙数,从源区域移入的独立连续区域数)应该稍微好一点,因为一次移动最多只能在一个连续的数字区域中移动,并填充目标区域中的单个间隙


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

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

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

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

    • 交换块而不是单个条目。我的意思是
      #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;
      }