用于在网格上查找路径的大多数内存有效算法

时间:2016-07-24 17:00:07

标签: c algorithm performance memory

可用于查找从一个网格到另一个网格的路径的内存效率最高的算法是什么?网格可能有无法跨越的障碍物。作为最短的路径是没有必要的,但肯定是一个奖励。该算法将用C语言编写(C ++可用,但我避免使用它来减少内存使用)并运行在只有2048字节SRAM的ATmega328芯片上。 CPU效率不是最重要的。

编辑:网格是16 x 32个方格,每个方格用一位表示。因此,总内存使用量为64字节。网格存储为无符号字符的2D数组,并且所有2048字节都可用。输出将是一个引用应该采用的正方形的整数数组。

如果正方形中存在障碍物,则正方形阵列将具有1而不是零。这些方块应该像墙一样处理。

3 个答案:

答案 0 :(得分:5)

对于可能适合2048字节的算法,这是一个未完成的想法,我在尝试查找非递归泛洪填充变体时提出了这个问题。

第一步是创建一个额外的32×16 8位值数组;这使用512个字节。然后,水平迭代网格,并对相邻可到达方块的运行进行编号,如下图所示:

grid path numbered

对于32×16网格,最大运行次数为256(例如,使用棋盘图案或垂直条纹),因此该编号适合8位值。

第二步是垂直迭代网格,并对相邻的运行进行分组:

  

检查垂直线1:
  {0A,11,1A}
  {} 2E
  {} 44,50,5C
  {72}
  {87,8F,98}

  

检查垂直线2后:
  {0A,11,1A,00,24}
  {} 2E
  {44,50,5C,37,69}
  {72}
  {87,8F,98,7C}

  

检查垂直线2后:
  {0A,11,1A,00,24,12,2F}
  {} 2E
  {44,50,5C,37,69,51,73}
  {72}
  {87,8F,98,7C,90}

...依此类推,如果它们通过相邻运行链接,则合并组。如果最后,起始和目标方块的数量在同一组中,则表示存在路径。

现在,如果您将组存储为简单列表,就像上面的示例一样,这并不能真正为您提供路径;它只是告诉你从开始和目标方块可以到达哪些方格,但路径可能不需要跨越所有这些方格。

如果您将组存储在数据结构中,您知道哪些运行相互连接,那么它将在较小的空间中成为“通过图形的最短路径”问题。我不确定哪个数据结构最适合剩下的1536个字节。

(欢迎任何人尝试进一步采纳这个想法。)

此方法可用于在运行其他算法之前简化网格。首先,运行的分组识别网格中无法到达的部分;这些可以标记为原始网格中的墙或其副本。其次,它确定了死胡同;只连接到另一个运行(并且不包含起始或目标方块)的运行是不必要的弯路,也可以这样标记。 (这应该重复:删除单个连接的运行可能会显示另一个单独连接的运行。)

grid path simplified

通过删除无法到达和单链接的运行来简化网格

再次运行算法,但使用垂直运行和水平分组,可以删除额外的死角。

下面的JavaScript代码段是算法第一部分的简单代码示例:使用图像中的示例网格,对运行进行编号,将它们分配给组,在必要时合并组,然后检查是否启动目标方块属于同一组,即是否存在路径。

分组方法可能不是最有效的,特别是在合并组时,但它使用最大256字节的固定大小数组(运行次数×8位值),这在有限内存中可能是最好的情况。

function gridPath(grid, x1, y1, x2, y2) {
    var runs = [], rcount = 0;
    for (var i = 0; i < 16; i++) {           // number runs
        var start = true; runs[i] = [];
        for (var j = 0; j < 32; ++j) {
            if (grid[i][j] == 0) {           // found empty cell
                if (start) ++rcount;         // start of new run
                runs[i][j] = rcount - 1;
                start = false;
            }
            else start = true;               // found blocked cell
        }
    }
    var groups = [], gcount = 0;
    for (var i = 0; i < rcount; i++) groups[i] = 0xFF;

    for (var j = 0; j < 32; ++j) {           // assign runs to groups
        var g = [];
        for (var i = 0; i < 16; ++i) {
            if (grid[i][j] == 0) g.push(runs[i][j]);
            if ((grid[i][j] == 1 || i == 15) && g.length > 0) {
                insertGroup(g);
                g = [];
            }
        }
    }     
    return groups[runs[y1][x1]] == groups[runs[y2][x2]];

    function insertGroup(g) {
        var matches = [];
        for (var i = 0; i < g.length; i++) { // check if runs are already in group
            if (groups[g[i]] != 0xFF && matches.indexOf(groups[g[i]]) < 0) {
                matches.push(groups[g[i]]);
            }
        }
        if (matches.length == 0) matches.push(gcount++); // start new group
        for (var i = 0; i < g.length; i++) { // add runs to group
            groups[g[i]] = matches[0];
        }
        if (matches.length > 1) {            // merge groups
            for (var i = 0; i < rcount; i++) {
                if (matches.indexOf(groups[i]) > 0) groups[i] = matches[0];
            }
        }
    }
}

var grid = [[1,0,1,0,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0],
            [0,0,0,1,0,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,1,0],
            [0,1,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,1,0,0,0,1,0,0],
            [0,0,1,0,1,0,1,0,1,0,0,1,0,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1,0,0,1],
            [1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0],
            [0,1,0,0,0,1,0,0,0,0,1,0,1,0,0,1,1,1,0,0,1,0,1,1,0,0,0,0,0,1,0,1],
            [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,0,1,1,1,1,0,1,0],
            [0,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0,0],
            [0,1,0,0,0,1,0,0,0,1,1,0,1,0,0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0],
            [0,0,1,0,1,0,1,0,1,0,1,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,1,0,0],
            [1,0,0,1,0,0,0,1,0,0,0,1,0,0,1,1,1,0,0,1,0,0,1,0,0,0,1,0,1,0,0,1],
            [0,1,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0,1,0,0,0,1,0,0,1,0],
            [1,0,1,0,1,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,1,0,0,0,1,0,1,0,1,0,0,1],
            [0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,1,0,0],
            [0,1,0,0,0,1,0,0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0],
            [0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0]];
document.write(gridPath(grid, 0, 15, 15, 7));

答案 1 :(得分:4)

如果您只想找到目标,但不想记住所采用的路径,那么随机搜索几乎是最佳记忆。它不需要记住以前的状态,所以内存使用是不变的。 (另一方面,时间复杂性是无限的,这不是很好,但并不排除在你的要求之外)

如果你确实需要记住所采用的路径,那么你就不能使用一个完整的算法来降低线性空间复杂度 - 即总是找到一个存在的路径。广度和深度优先搜索都具有线性空间复杂度,因此它们将与最优完整算法在同一类中渐近。

由于内存非常有限,您可能更喜欢使用内存有界算法,它为内存使用提供了恒定的上限,但不保证找到可能存在的路径。我推荐Simplified Memory Bounded A *。

答案 2 :(得分:1)

我研究了使用Dijkstra(由Weather Vane建议),这将要求每个网格单元存储到起点的距离和前一个单元的方向。

不幸的是,32x16网格上的路径可能有大于255的距离;我找到的最长路径距离为319(见左下图)。这意味着距离不符合8位,距离矩阵的大小为1024字节。

longest path and largest queue

左:最长路径(距离= 319)。右:最大数量的等距细胞(距离16处72个细胞)

然而,在所有距离都等于1的方格中,你可以将Dijkstra简化为广度优先搜索,它不使用距离矩阵;如果您使用fifo队列,则按照到起始单元格的距离的顺序访问单元格,因此您无法找到已访问过的单元格的较短路径。

fifo队列将包含一定距离的每个单元格,然后逐渐过渡到距离+ 1,依此类推。队列的最大大小取决于可以有多少等距单元;我发现的最大值是72(见上图,右图),并且在从前一个距离的过渡期间,这需要一个可以容纳76个单元或152个字节的坐标的队列。

算法返回的路径是一个包含最多320个单元格坐标的数组,因此它的最大大小为640字节。在构造此数组之前,可以丢弃队列,因此只有方向网格和路径同时在内存中。

以下是仅具有方向矩阵和fifo队列的简化算法的代码示例;它可以在很多方面得到改进,但它证明了这个想法。 findPath()函数使用最少664个,最多1152个字节的已分配内存(取决于路径长度)加上大约20个字节的附加变量。

这可以进一步减少,例如通过将方向矩阵存储为4位半字节,将其大小从512字节减小到256字节(但需要更多计算),或者将路径作为向上/向右/向下/向左方向的序列而不是单元格坐标返回,这将是每步仅需要2位,将其最大大小从640减少到80字节。

#include <stdlib.h>                                         //  gcc -std=c99

short int findPath(char grid[][32], char x1, char y1, char x2, char y2, char **path) {
    char (*dir)[16][32] = calloc(512, 1);                   // allocate direction matrix: 512 bytes (zeros)
    (*dir)[y2][x2] = 5;                                     // mark starting cell as visited (search backwards)
    char *queue = malloc(152);                              // allocate fifo queue: 152 bytes
    queue[0] = x2; queue[1] = y2;                           // put starting cell in queue (search backwards)
    unsigned char qRead = 0, qWrite = 2;                    // queue pointers
    char qCurSize = 1, qNextSize = 0;                       // queue size per distance
    short int distance = 0;                                 // distance to current cell
    char dx[4] = {0, 1, 0, -1};                             // up, right, down, left
    while (qRead != qWrite && !(*dir)[y1][x1]) {            // until queue empty (fail) or target reached
        char x = queue[qRead++], y = queue[qRead++];        // take oldest cell from queue
        qRead %= 152;                                       // wrap-around queue pointer
        for (char i = 0; i < 4; i++) {                      // check 4 neighbouring cells
            char nx = x + dx[i], ny = y + dx[3 - i];        // coordinates of neighbouring cell
            if (nx >= 0 && nx < 32 && ny >= 0 && ny < 16    // coordinates not off-grid
            && !grid[ny][nx] && !(*dir)[ny][nx]) {          // traversable unvisited cell
                (*dir)[ny][nx] = i + 1;                     // store direction 1-4
                queue[qWrite++] = nx; queue[qWrite++] = ny; // put cell in queue
                qWrite %= 152;                              // wrap-around queue pointer
                ++qNextSize;                                // increment queue size for next distance
            }
        }
        if (!--qCurSize || (*dir)[y1][x1]) {                // current distance done or target reached
            qCurSize = qNextSize;                           // switch to distance + 1
            qNextSize = 0;
            ++distance;
        }
    }
    free(queue);                                            // free up queue memory for path
    if (!(*dir)[y1][x1]) distance = -1;                     // no path found
    else {                                                  // path found
        *path = malloc(distance * 2 + 2);                   // allocate path array: 2 bytes per step
        (*path)[0] = x1; (*path)[1] = y1;                   // starting position (forward)
        for (short int i = 1; i <= distance; i++) {         // retrace steps
            char d = (*dir)[y1][x1] - 1;                    // direction of previous step 0-3
            x1 -= dx[d]; y1 -= dx[3 - d];                   // go back to previous position
            (*path)[i * 2] = x1; (*path)[i * 2 + 1] = y1;   // add cell to path
        }
    }
    free(*dir);                                             // discard direction matrix
    return distance + 1;                                    // return number of cells in path
}

int main() {
    char grid[][32] = // max queue size: 76
        {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
         {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
    char x1 = 31, y1 = 0, x2 = 16, y2 = 7, *path = NULL;
    short int steps = findPath(grid, x1, y1, x2, y2, &path);
    // do stuff
    free(path);                                             // discard path array

    return 0;
}