【问题标题】:Debugging Simple Graph Algorithm using Breadth First Search (BFS)使用广度优先搜索 (BFS) 调试简单图算法
【发布时间】:2011-11-02 21:28:12
【问题描述】:

我正在构建一个程序来搜索、识别和标记简单二维数组中整数值图的位置。

我手动追踪了第一个示例,它似乎运行准确。话虽如此,我要么编写的代码没有按照我认为的那样做,要么我的手部追踪不准确。

我认为我的代码很接近,我正在寻找一些调试帮助以及对一般风格等的任何想法。

最终,该算法将被修改以找到用于 OCR 的字符像素图。我只是想在用处理图像的代码使事情复杂化之前证明我的算法实现是准确的。

输入数组可能如下所示:

0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 1 0 0
0 0 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0

预期的结果是这样的:

3 3 3 3 3 3
3 0 0 0 0 3
3 0 2 2 0 3
3 0 2 2 0 3
3 0 0 0 0 3
3 3 3 3 3 3

另一个类似的可能性是: 在:

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 0 0 0 0 0 0 0
0 0 0 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
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 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 3 3 3 3 3 3 0 0 0 0 0
0 3 0 0 0 0 3 0 0 0 0 0
0 3 0 2 2 0 3 0 0 0 0 0
0 3 0 2 2 0 3 0 0 0 0 0
0 3 0 0 0 0 3 0 0 0 0 0
0 3 3 3 3 3 3 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 3 3 3 3 3 3 3 3 3 0
0 0 3 0 0 0 0 0 0 0 3 0
0 0 3 0 2 2 2 2 2 0 3 0
0 0 3 0 0 0 0 0 0 0 3 0
0 0 3 3 3 3 3 3 3 3 3 0

基本规则:

  1. 输入文件的数组大小必须与 .cpp 文件中定义的 GS 匹配(H 等于 W 等于 GS)。
  2. 图表定义为一个或多个彼此相邻的“1”值。
  3. 使用简单队列的基本 BFS 技术执行搜索。
  4. 找到图表后,其值将从“1”更新为“2”。
  5. 确定图表中的最终值后,将在图表周围绘制一个由“3”个值组成的边界框。盒子的最小 X 等于图形的最小 X 减 2,盒子的最小 Y 等于图形的最小 Y 减 2。框的最大 X 等于图的最大 X 加二,框的最大 Y 等于图的最大 Y 加二。假设所有图表都有一个从边界到至少两行/列的缓冲区,以允许绘制一个框。

处理这个数组的最新尝试:

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 0 0 0
0 0 0 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 0 0 0 0
0 3 3 3 3 3 0 0
0 3 3 3 3 3 3 0
0 3 3 2 1 3 3 0
0 3 3 2 2 3 3 0
0 3 3 3 3 3 3 0
0 3 3 3 3 3 3 0
0 0 0 0 0 0 0 0

虽然单个数字图效果很好:

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

产生输出:

3 3 3 3 3
3 0 0 0 3
3 0 2 0 3
3 0 0 0 3
3 3 3 3 3

这是我的代码:

#include <iostream>
#include <fstream>
#include <cstdlib>
#include "queue.h"

#define GS 8 /* GRID SIZE */

using namespace std;

void processCmdArgs (ifstream& input, int argc, char* argv[]);
void drawBoundingBox (int arr[][GS], int xLo, int yLo, int xHi, int yHi);
void checkNeighbors (int arr[][GS], bool vis[][GS], queue Q, point* p);
void print (int arr[][GS]);

int main( int argc, char* argv[] ) {

    int xLo = 0;
    int xHi = GS - 1;
    int yLo = 0;
    int yHi = GS - 1;
    ifstream input; /* filestream to read in file to parse */
    int arr[GS][GS]; /* declare array of vals to check for graph */
    bool visited[GS][GS]; /* array of bools to track progress */
    int count = 0; /* number of graphs found */
    processCmdArgs(input, argc, argv);

    /* populate array */
    for (int i = 0; i < GS; i++) {
        for (int j = 0; j < GS; j++) {
            input >> arr[i][j];
        }
    }
    input.close();

    /*init visited */
    for (int y = yLo; y < GS; y++) {
        for (int x = xLo; x < GS; x++) {
            visited[x][y] = false;
        }
    }

    /* print array */
    cout << "The array to find a graph is:\n";
    print(arr);

    /* find graph(s) in array */
    queue Q;
    for (int j = yLo; j < GS; j++) {
        for (int k = xLo; k < GS; k++) {
            if (arr[k][j] == 1) {
                count++;
                xLo = xHi = k;
                yLo = yHi = j;
                point *p = new point(k, j);
                Q.insert(p);
                delete p;
                visited[k][j] = true;
                while (!Q.isEmpty()) {
                    *p = Q.del(); /* does this really work? */
                    int x = p->getx();
                    int y = p->gety();
                    arr[x][y] = 2;
                    if (x < xLo) xLo = x;
                    if (y < yLo) yLo = y;
                    if (x > xHi) xHi = x;
                    if (y > yHi) yHi = y;
                    checkNeighbors(arr, visited, Q, p);
                }
                drawBoundingBox(arr, xLo, yLo, xHi, yHi);
            }
            else {
                visited[k][j] = true;
            }
        }
    }
    cout << "The updated array is:\n";
    print(arr);
    cout << "The number of graphs in arr is " << count << endl;

    return 0;
}
/*** END OF MAIN ***/

/*** START OF FUNCTIONS ***/
void processCmdArgs(ifstream& input, int argc, char* argv[]) {
    /* Check command-line args first to avoid accessing nonexistent memory */
    if (argc != 2) {
        cerr << "Error: this program takes one command-line argument.\n";
        exit(1);
    }
    /* Try to open the file using the provided filename */
    input.open(argv[1]);
    /* Exit with error if it doesn't open */
    if (input.fail()) {
        cerr << "Error: could not open " << argv[1] << ".\n";
        exit(1);
    }
}

void drawBoundingBox (int arr[][GS], int xLo, int yLo, int xHi, int yHi) {
    // draw a box with (lowx-2,lowy-2) as NW and
    // (highx + 2, highy + 2) as SE boundary
    /* draw top and bottom of box */
    for (int x = xLo - 2; x <= xHi + 2; x++) {
        arr[x][yLo - 2] = 3;
        arr[x][yHi + 2] = 3;
    }
    /* draw sides of box */
    for (int y = yLo - 1; y <= yHi + 1; y++) {
        arr[xLo - 2][y] = 3;
        arr[xHi + 2][y] = 3;
    }
}

void checkNeighbors (int arr[][GS], bool vis[][GS], queue Q, point* p) {
    int pX = p->getx();
    int pY = p->gety();
    for (int y = pY - 1; y <= pY + 1; y++) {
        for (int x = pX - 1; x <= pX + 1; x++) {
            if (x == pX && y == pY) {/* easier than opposite boolean logic */ }
            else {
                if (vis[x][y] == false) vis[x][y] = true;
                if (arr[x][y] == 1) {
                    point *n = new point(x, y);
                    Q.insert(n);
                    delete n;
                }
            }
        }
    }
}

void print (int arr[][GS]) {
    /* print array */
    for (int i = 0; i < GS; i++) {
        for (int j = 0; j < GS; j++) {
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }
}
/*** END OF FUNCTIONS ***/

/*** START of QUEUE CLASS ***/

const int MSIZE = 1000;

class point {
private:
    int x; int y;

public:
    point(int p, int q) {
        x = p; y = q;
    }

    int getx() {
        return x;
    }

    int gety() {
        return y;
    }
};

class queue {

private:
    point* Q[MSIZE];

    int front, rear, size;

public:
    queue() {
        // initialize an empty queue
        //front = 0; rear = 0; size = 0;
        front = rear = size = 0;
        for (int j = 0; j < MSIZE; ++j)
            Q[j] = 0;
    }

    void insert(point* x) {
        if (size != MSIZE) {
            front++; size++;
            if (front == MSIZE) front = 0;
            Q[front] = x;
        }
    }

    point del() {
        if (size != 0) {
            rear++; if (rear == MSIZE) rear = 0;
            point temp(Q[rear]->getx(), Q[rear]->gety());
            size--;
            return temp;
        }
    }
    void print() {
        for (int j = 1; j <= size; ++j) {
            int i = front - j + 1;
            cout << "x = " << Q[i]->getx() << " y = " << Q[i]->gety() << endl;
        }
        cout << "end of queue" << endl;
    }
    bool isEmpty() {
        return (size == 0);
    }
};

/*** END of QUEUE CLASS ***/

【问题讨论】:

    标签: c++ graph breadth-first-search


    【解决方案1】:
    1. 此代码无法编译。你遗漏了`queue.h`。我们可以推断出来,但你不应该让我们这样做。
    2. 您在此源文件中有类声明;它们属于头文件(否则拥有头文件没有多大意义)。
    3. 如果您要在源文件中包含类声明,看在上帝的份上,请将它们放在需要它们的代码之前。
    4. `queue::del()` 中有一个简单的编译时错误。要么你的编译器不是很好,要么你关闭了警告,或者你忽略了警告,或者你懒得修复简单的东西。
    5. 您使用数组而不是 STL 容器是否有充分的理由?
    6. 您在堆上声明所有这些点是否有充分的理由?
    7. 我不想草率下结论,但您的主循环中的逻辑看起来非常混乱和过于复杂。
    8. 最重要的一点: 如果您要放弃边界框,我非常怀疑程序是否会无错误地运行,并且错误会更容易找到。 在为边界框编写代码之前,您是否尝试过?您应该在放入每个新行为时对其进行测试,并且永远不要添加到不起作用的代码中。(我经常说我应该开始称它为“贝塔规则”。)

    现在让我们寻找错误...

    1. 在主循环中,您从 `xLo` 和 `yLo` 进行迭代,但在循环中修改了这些变量。
    2. 有时您使用 `[j][k]` 进行索引,有时使用 `[k][j]`。当我清理它时,一些不良行为就会消失。
    3. 您正在围绕图表的每个点 绘制一个单独的边界框。
    4. 边界框例程中有一个简单的错误。

    现在它可以工作了,对于一个图表。我不打算用两个来尝试。

    编辑:
    我不得不吃掉我的一些话:你没有用 [j][k] 索引,我只是对你使用 @ 感到困惑987654324@ 并将其与其他地方的实际错误混为一谈。现在我看到了你在用queue 做什么,但你应该认真研究一下 STL。

    真正严重的错误是在checkNeighbors(...) 的签名中。 你通过值而不是引用传递Q。修复该问题,该代码适用于多个图表。

    编辑:
    是的,另一个错误:queue 存储指向点的指针,而不是点,没有特别的原因(参见上面的“6”),不知何故它弄脏了它们。我没有寻找确切的错误,而是更改了queue 来处理点,并为复杂的图形得到了正确的结果。

    【讨论】:

    • 感谢您的反馈。关于您的第一个 8 cmets,queue.h 实际上包含在代码的底部。我只是不得不说它是 queue.h 并说它是“队列类”。队列是别人写给我用的。我知道这个错误,但它对我的使用没有实际影响。主循环中的逻辑来自我正在使用的伪代码。不确定您是否只是想谴责它,或者您是否有一些有价值的东西要添加。 Beta 的规则是有道理的。
    • 我修复了 xLo/yLo 的使用以保持循环正确递增。据我所知,我实际上并没有使用 [j][k] 和 [k][j] 进行索引。关于边界框例程中的一次性错误,我没有看到。我确实更改了代码第二部分的索引,以免某些值两次更改。这部分:'for (int y = yLo - 1; y
    • 没有。这些图不能靠得很近。目的是修改此算法以使用 EasyBMP 为 OCR 识别字母。该规范假设相邻字符(图形)之间至少有 5 个像素。在我使用简单整数数组的测试用例中,我将输入设置为允许有足够的空间让单独的图表不重叠。其他可接受的输入是THIS。我们仅限于在此测试算法的上下文中使用方形 2D 数组...
    • 好的。取得一些进展。值得称赞的是,我的一些索引是有问题的,我清理了它。这并没有修改功能,但它更加一致且不那么混乱。将 Q 更改为通过引用传递,这产生了很大的不同。请在link 中查看更多示例。第一个显示精确的 20 x 20 简单图形数组。所有的都位于并且都正确装箱。下图显示了一个 15 x 15 阵列和一个复杂的图形。由于我使用的是“8 个邻居”算法,这应该可以工作,但结果是当它只有 1 个时将 2 个图装箱。
    • 感谢所有帮助。最后,我使用了 STL 队列,而不是提供给我的有缺陷的队列实现。我的程序现在可以在整数数组上完美运行。不幸的是,我现在在修改此代码以处理 OCR 的 BMP 文件时遇到了某种错误。战斗还在继续……
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    相关资源
    最近更新 更多