使用广度优先搜索(BFS)调试简单图算法

时间:2011-10-16 15:41:47

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

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

我手动追踪了第一个例子,它看起来准确无误。有了这个说我写的代码不能做我认为它做的事情或我的手跟踪是不准确的。

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

最终将修改此算法以查找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加2,方框中最大的Y等于图中最大的Y加2。假设所有图形都具有距边框至少两行/列的缓冲区,以允许绘制框。
  6. 处理此阵列的最新尝试:

    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 ***/
    

1 个答案:

答案 0 :(得分:2)

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

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

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

现在它适用于一个图表。我不打算用两个来试试。

修改:
我必须吃掉我的一些话:你没有用[j][k]索引,我只是因为你使用(k,j) <=> (x,y)而感到困惑,并把它与实际的bug混淆了别处。现在我看到你正在用queue做什么,但是你应该认真看待STL。

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

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