【问题标题】:What data structures can efficiently store 2-d "grid" data?哪些数据结构可以有效地存储二维“网格”数据?
【发布时间】:2009-09-08 00:11:40
【问题描述】:

我正在尝试编写一个对数字网格执行操作的应用程序,其中每次运行函数时,每个单元格的值都会更改,并且每个单元格的值取决于其邻居。每个单元格的值都是一个简单的整数。

在此处存储我的数据的最佳方式是什么?我已经考虑过平面列表/数组结构,但这似乎无效,因为我必须反复进行计算才能确定哪个单元格“高于”当前单元格(当有任意网格大小时)和嵌套列表,这不会这似乎不是一种很好的数据表示方式。

我不禁感到,为了这种目的,必须有更好的方法在内存中表示这些数据。有什么想法吗?

(注意,我不认为这真的是一个主观问题 - 但堆栈溢出似乎认为是......我有点希望有一种可以接受的存储此类数据的方式)

【问题讨论】:

  • 在大多数情况下要求做某事的“最佳”方式主观的。但我认为在这种情况下,它是非常明确的。
  • 你正在做的很像一个细胞自动机。你可以用你最喜欢的语言找到 Conway's Life 的开源实现,看看他们做了什么。

标签: language-agnostic data-structures


【解决方案1】:

这里有一些方法。我将(尝试)用 3x3 网格的表示来说明这些示例。

平面阵列

+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+---+---+---+---+---+---+---+---+---+

a[row*width + column]

要访问左侧或右侧的元素,请减或加 1(注意行边界)。要访问上方或下方的元素,请减去或添加行大小(在本例中为 3)。

二维数组(对于支持此的 C 或 FORTRAN 等语言)

+-----+-----+-----+
| 0,0 | 0,1 | 0,2 |
+-----+-----+-----+
| 1,0 | 1,1 | 1,2 |
+-----+-----+-----+
| 2,0 | 2,1 | 2,2 |
+-----+-----+-----+

a[row,column]
a[row][column]

访问相邻元素只是增加或减少行号或列号。编译器仍在执行与平面数组中完全相同的算术运算。

数组数组(例如Java)

+---+   +---+---+---+
| 0 |-->| 0 | 1 | 2 |
+---+   +---+---+---+
| 1 |-->| 0 | 1 | 2 |
+---+   +---+---+---+
| 2 |-->| 0 | 1 | 2 |
+---+   +---+---+---+

a[row][column]

在此方法中,“行指针”列表(在左侧表示)每个都是一个新的独立数组。与二维数组一样,通过调整适当的索引来访问相邻元素。

完全链接的单元格(二维双向链表)

+---+   +---+   +---+
| 0 |-->| 1 |-->| 2 |
|   |<--|   |<--|   |
+---+   +---+   +---+
 ^ |     ^ |     ^ |
 | v     | v     | v
+---+   +---+   +---+
| 3 |-->| 4 |-->| 5 |
|   |<--|   |<--|   |
+---+   +---+   +---+
 ^ |     ^ |     ^ |
 | v     | v     | v
+---+   +---+   +---+
| 6 |-->| 7 |-->| 8 |
|   |<--|   |<--|   |
+---+   +---+   +---+

此方法的每个单元格最多包含四个指向其相邻元素的指针。通过适当的指针访问相邻元素。您仍然需要保留指向元素的指针结构(可能使用上述方法之一)以避免必须按顺序遍历每个链表。这种方法有点笨拙,但是它在Knuth's Dancing Links算法中确实有一个重要的应用,在算法执行期间修改链接以跳过网格中的“空白”空间。

【讨论】:

  • 哇,这就是答案,伙计。你真的很努力地回答了这个问题。 +1
  • “二维数组”(2da)比“数组数组”(aoa)解决方案快吗?他们似乎在做同样的事情,但以两种不同的方式。第 2 个 (aoa) 接缝需要 2 次查找操作,但是第 1 个 (2da) 接缝是否也需要 2 个操作?
  • @SebastianNorr 这几乎肯定是依赖于语言的。我检查了 python,因为这是我所知道的,我发现访问 10000x10000 ndarray (2da) 中间的元素大约是访问中间元素的两倍半10000x10000 的列表列表 (aoa)。这是一个非常粗略的速度测试,它在不同的系统上可能会有所不同,更不用说使用不同的语言了。
【解决方案2】:

如果查找时间对您很重要,那么二维数组可能是您的最佳选择,因为在给定单元格的 (x,y) 坐标的情况下查找单元格的邻居是一个常数时间操作。

【讨论】:

  • +1 超过约翰的答案,这也是正确的,但根据语言,二维数组和数组数组(锯齿状数组)之间存在差异。在大多数直接支持二维数组的语言中,使用数组数组只是一种痛苦。
  • +1 - 您为“确定当前单元格上方的单元格”所做的计算量是微不足道的 - 它与取消引用指针的情况没有太大区别,并且数组没有几乎与其他一些更广泛链接的结构一样多的开销。
【解决方案3】:

根据我的评论,您可能会发现Hashlife 算法很有趣。

本质上(如果我理解正确的话),您将数据存储在四叉树中,其中包含指向树节点的哈希表。这里的想法是,相同的模式可能在您的网格中出现不止一次,并且每个副本将散列到相同的值,因此您只需计算一次。

这对于 Life 来说是正确的,它是一个大部分为假布尔值的网格。你的问题是否属实,我不知道。

【讨论】:

    【解决方案4】:

    动态分配的数组数组使得指向当前单元格上方的单元格变得微不足道,并且还支持任意网格大小。

    【讨论】:

      【解决方案5】:

      您应该从存储数据的方式中抽象出来。 如果您需要在数组内部进行相关操作,Slice 是常见的操作模式。 你可以有这样的东西:

      public interface IArray2D<T>
      {
          T this[int x, int y] { get; }
      }
      
      public class Array2D<T> : IArray2D<T>
      {
          readonly T[] _values;
          public readonly int Width;
          public readonly int Height;
      
          public Array2D(int width, int height)
          {
              Width = width;
              Height = height;
              _values = new T[width * height];
          }
      
          public T this[int x, int y]
          {
              get
              {
                  Debug.Assert(x >= 0);
                  Debug.Assert(x < Width);
                  Debug.Assert(y >= 0);
                  Debug.Assert(y < Height);
      
                  return _values[y * Width + x];
              }
          }
      
          public Slice<T> Slice(int x0, int y0)
          {
              return new Slice<T>(this, x0, y0);
          }
      }
      
      public class Slice<T> : IArray2D<T>
      {
          readonly IArray2D<T> _underlying;
          readonly int _x0;
          readonly int _y0;
      
          public Slice(IArray2D<T> underlying, int x0, int y0)
          {
              _underlying = underlying;
              _x0 = x0;
              _y0 = y0;
          }
      
          public T this[int x, int y]
          {
              get { return _underlying[_x0 + x, _y0 + y]; }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-09
        • 1970-01-01
        • 2016-09-18
        • 2012-06-02
        • 2016-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多