【问题标题】:Dynamic Programming: Bits Array Java动态编程:位数组 Java
【发布时间】:2013-12-03 16:43:39
【问题描述】:

我需要解决一个Dynamic Programming 问题,为此我需要在内存中创建一个N*N 大小矩阵。如果我确实创建了一个byte[][] 的矩阵,大小为N = 100000;,那么它会抛出java.lang.OutOfMemoryError: Java heap space 错误。

在这个矩阵中,我将在特定的 ithjth 索引处存储 0 或 1。所以我的使用仅限于一点,我需要一种方法,以便我可以只使用每个矩阵单元的 1 位大小,而不是过度淹没内存,将 8 位保留在一个只需要 1 位的单元中。

我怎样才能做到这一点?

请注意,我关心的不是增加 JVM 堆,我只是在寻找一种方法来优化解决动态问题。

【问题讨论】:

  • 说明问题本身。
  • 对于大多数 DP 算法,您不需要存储整个网格,只需存储最后一行左右即可。
  • 只是最后一行?但是如果您需要返回查看以前的结果怎么办?
  • 对于某些问题,这样做肯定是不可能的,如果你想要实际的解决方案而不是数值,你可能需要整个网格。这只是要记住的事情。 (由于您没有具体说明实际问题,我们无法告诉您是否可能)。

标签: java algorithm memory matrix dynamic-programming


【解决方案1】:

更新:我刚刚检查过,不幸的是,忘记了BitSet

BitSet 的巧妙 API 建立在 int 索引之上。整数范围超出了N * N 的范围。因此,您需要自己实现一些东西。一个建议:

public class BetterBitSet {

  private final long[] values;

  public BetterBitSet(int dimension) {
    values = new long[(int) ((long) dimension) * dimension / 8 / 64 + 1];
  }

  public void set(int i, int j, boolean value) {
    long index = index(i, j);
    int arrayIndex = arrayIndex(index);
    if (value) {
        values[arrayIndex] = set(values[arrayIndex], offset(index));
    } else {
        values[arrayIndex] = unset(values[arrayIndex], offset(index));
    }
  }

  public boolean isSet(int i, int j) {
    long index = index(i, j);
    return isSet(values[arrayIndex(index)], offset(index));
  }

  private boolean isSet(long value, int bitIndex) {
    return (value & (1L << bitIndex)) != 0;
  }

  private long set(long value, int bitIndex) {
    return value | (1L << bitIndex);
  }

  private long unset(long value, int bitIndex) {
    return value & ~(1L << bitIndex);
  }

  private long index(int i, int j) {
    return j * 8L + i;
  }

  private int arrayIndex(long index) {
    return (int) index / 64;
  }

  private int offset(long index) {
    return (int) index % 64;
  }
}

如有疑问,请查看BitSet 的源代码并尝试类似的方法。

旧答案:问题是byte[][] 的每个索引都需要8 位,而您只存储大约1 位的信息。这需要 9,536 MB 来在堆上分配这个数组,所以空间不足。这个内存量很可能对您的机器来说太多了。但是,使用存储位,您仍然需要 1,192 MB。 (不考虑由BitSet 引起的任何开销。)这仍然是一个很大的数字,因此请确保您的机器提供了足够多的空间,您必须另外分配给您的 JVM 实例。

【讨论】:

  • 我很确定你的计算结果是 8 倍。10000000000 位 = 1250000000 字节 = 1220703 KB = 1192 MB
  • 所以我需要找到其他方法来解决而不是使用矩阵来为单元格保存 0 或 1?
  • 您可以使用带有位集的矩阵,请记住,如果一行中有 4 个元素,则第 5 位必须在第二行。
  • 没错,我算错了。所以不习惯用位而不是字节来计算。
  • 根据您的解释,我知道一件事,由于内存问题,考虑使用位数组解决是行不通的。因为它仍然耗尽了限制。
【解决方案2】:

Java 提供了一种托管的基于位的数据结构,称为BitSet。我说这是托管的,因为 Java 没有用于存储位的本机类型,相反,BitSet 将各个位存储为long 的一个组件,并保留一个long[] 来支持该集合。这是一种非常节省存储的方法,但是implementation of BitSet 在每 64 位中保留 6 位用于“寻址”,这相当于原始存储层中 10% 的开销。

这意味着您可以通过保留并仔细处理自己的long[] 在存储方面击败BitSet,但代价是代码中的一些复杂性和风险。除非您在空间上受到极大限制,否则可能不值得节省 BitSet 的约 10% 的原始开销。

上面描述的两种解决方案都是一维位数组,这可能也毫无价值。您可以通过将一维数组视为行的串联来轻松地将二维数组转换为一维数组(至少,在这种情况下,您似乎假设行宽相等)。处理矩阵中的特定单元格很简单:

row * (row_width) + column

【讨论】:

  • 感谢您的解释。根据您的解释,我知道一件事,由于内存问题,考虑使用位数组解决是行不通的。因为它仍然耗尽了限制。
  • 你和我跑进了同一个陷阱。 BitSet 受 Java 中整数范围的限制...
【解决方案3】:

在许多情况下,您不需要所有子问题来评估当前问题,因此我建议使用 hashmap 的递归自上而下解决 DP 问题来存储解决方案。使用子问题后,您可以将其从 hashmap 中删除以防止溢出。 有时会有轻微的性能损失,但会防止内存已满错误。

Memoization

【讨论】:

  • Vikram dost...main jo 问题解决 kar rha hoon...usme main HashMap 使用 nhi kar sakta。通过上面的所有讨论,一件事是确认我需要使用树结构,它将探索到某个叶子,然后如果在叶子的路径上找不到解决方案,则返回。 WHich 还将帮助我通过释放树返回的节点内存来释放内存。
  • @MasterJ 为什么你不能使用它,你正在使用 JVM 对。 Agar nahi 请告诉您的问题,以便我们可以提供更好的优化。
  • 大多数动态规划问题都使用矩阵或树结构来评估问题并避免重复出现的解决方案。 HashMap 适用于我要查找大数的阶乘等问题。假设是 50,那么我可以在哈希图中存储 50 到 0 的阶乘值。下次当有人要求 52 时,我将只使用 52*51*hashMap.get(50),它将快速返回结果,而无需从 50、49、48....3、2、1 和0!再次。
  • @MasterJ U 可以将 hashmap 用于任何 DP 问题,因为即使使用矩阵,您也需要解决任何遇到的子问题至少一次,因此递归解决方案可以通过存储子问题的解决方案来有效地解决这个问题。在 O(1) 中使用它。此外,您不需要解决递归中未遇到的子问题,而在基于矩阵的解决方案中无法避免。这样可以节省内存。
  • 正如你所建议的,我使用了记忆技术。我给出的阶乘查找示例使用了相同的技术。但是每种技术都有其局限性。我不需要为单个 Key 存储值,但我确实需要跟踪矩阵的整个对角线,并基于索引访问其单元格的值。
猜你喜欢
  • 1970-01-01
  • 2023-03-05
  • 2016-02-22
  • 2013-03-31
  • 1970-01-01
  • 1970-01-01
  • 2013-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多