哪些数据结构可以有效地存储二维“网格”数据?

时间:2009-09-08 00:11:40

标签: language-agnostic data-structures

我正在尝试编写一个对数字网格执行操作的应用程序,每次函数运行时,每个单元格的值都会发生变化,每个单元格的值依赖于它的邻居。每个单元格的值都是一个简单的整数。

在这里存储数据的最佳方式是什么?我已经考虑了平面列表/数组结构,但这似乎无效,因为我必须重复进行计算以确定哪个单元格在当前单元格之上(当有任意网格大小时)和嵌套列表,这不是似乎是表示数据的一种非常好的方式。

我不禁觉得必须有更好的方法在内存中表示这些数据以达到这种目的。有什么想法吗?

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

5 个答案:

答案 0 :(得分:59)

以下是一些方法。我将(尝试)用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]

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

完全连锁的细胞(2-d双链表)

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

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

答案 1 :(得分:5)

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

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

对于Life来说这是真的,这是一个大多数假布尔的网格。对于你的问题是否属实,我不知道。

答案 4 :(得分:1)

您应该从存储数据的方式中抽象出来。 如果你需要在数组中进行相对操作,那么 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]; }
    }
}