【问题标题】:How to avoid stack overflow in this code, (Recursive function)如何避免此代码中的堆栈溢出,(递归函数)
【发布时间】:2015-11-22 00:49:21
【问题描述】:

我试图解决一个编程竞赛问题。我几乎是这个部门的菜鸟(我想我有很多东西要学)。我试图解决这个问题,其中包括读取二维数组(n x m)并找出其中的斑点。斑点由连续的发光像素形成(由# 表示)。未点亮的像素(由. 表示)。我试图通过使用递归方法Blob::form() 找到一个blob。示例输入可能如下所示

1
6 6
#...#.
.#.#.#
##..#.
......
.#.#.#
#...#.

我匆忙想出了解决方案。而且数量不多。但与往常一样,它在最坏的情况下失败n = m = 1000,所有字符都是#。一个 1000 x 1000 的版本:

1
3 3
###
###
###

我假设的问题是堆栈溢出。我发现程序在形成 blob 时崩溃了。

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>

using namespace std;
int pat[1000][1000],n,m;
char a[1000][1000];

struct point
{
    int x,y;
};

bool inBounds(point p)
{
    if(p.x < n && p.x >=0 && p.y < m && p.y >= 0) return true;
    else return false;
}
bool isAblob(int i,int j)
{
    point p[8];
    p[0].x = i-1; p[0].y =  j;
    p[1].x = i+1; p[1].y =  j;
    p[2].x = i+1; p[2].y =  j+1;
    p[3].x = i-1; p[3].y =  j-1;
    p[4].x = i-1; p[4].y =  j+1;
    p[5].x = i+1; p[5].y =  j-1;
    p[6].x = i; p[6].y =  j-1;
    p[7].x = i; p[7].y =  j+1;

    for(int k=0;k<8;k++)
    {
        if(inBounds(p[k]))
        {
            if(a[p[k].x][p[k].y] == '#') return true;
        }
    }
    return false;
}


class Blob
{
public:
    long long int pow;

    Blob(int i, int j)
    {
        this->pow = 0;
        point po;
        po.x=i;
        po.y=j;
        this->form(&po);
    }

    int getPow()
    {
        return this->pow;
    }

    void form ( point *p)
    {
        if(inBounds(*p))
        {
            if(a[p->x][p->y] == '#' && !pat[p->x][p->y])
            {
                a[p->x][p->y] = 1;
                this->pow++;
                point *e = new point;
                e->x = p->x-1; e->y =  p->y;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x+1; e->y =  p->y;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x+1; e->y =  p->y+1;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x-1; e->y =  p->y-1;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x-1; e->y =  p->y+1;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x+1; e->y =  p->y-1;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x; e->y =  p->y-1;if(pat[e->x][e->y] == 0)form(e);
                e->x = p->x; e->y =  p->y+1;
                if(pat[e->x][e->y] == 0)form(e);
            }
        }
        return;
    }
};

int main()
{
    int t;

    cin >> t;
    for (int q = 0; q < t; q++)
    {
        cin >> n >> m;
        int bnum = 0;
        Blob *b[(n*m)/2];
        vector <int> pows;
        cin.get();
        for(int i=0;i<n;i++)
        {
            for(int j = 0; j<m;j++)
            {
                a[i][j] = cin.get();
                pat[i][j] = 0;
            }
            cin.get();
        }


        for(int i=0;i<n;i++)
        {
            for(int j = 0; j<m;j++)
            {
                if(a[i][j] == '#' && pat[i][j] == 0)
                {
                    if(isAblob(i,j))
                    {
                        bnum++;
                        b[bnum] = new Blob(i,j);
                        pows.push_back(b[bnum]->getPow());
                    }
                    else continue;
                }
                else continue;
            }
        }
        sort(pows.begin(),pows.end());
        cout << endl << bnum;

        for(int i=1;i<=bnum;i++)
        {
            if(i==1) cout << endl;
            if(i!=1) cout << " ";
            cout << pows[i-1];
        }
    }
}

我确信我的代码有缺陷且效率低下。我想知道是否有人可以让我了解如何在未来避免这些问题。更好的实现也会有所帮助。但我正在寻找的是将来避免堆栈溢出的提示。

【问题讨论】:

  • 我可以考虑将你的递归函数变成循环,确保它们是尾递归的(并依赖编译器来优化它们)或为分治算法选择更大的基本情况。例如,在您的方法form 中,我感觉您正在执行 BFS(广度优先搜索,将网格视为图形)(编辑:实际上这是一个 DFS,但您也可以将其变成循环并一个列表)。您可以将其转换为循环,而不是递归函数,该循环更新相邻点列表并在每次迭代时弹出第一个元素。
  • @Caninonos 我会试试的。你认为这可能避免堆栈溢出,因为我们限制了函数调用?
  • 是的。关键是您的列表结构将(可能)将其对象存储在堆而不是堆栈中(编辑:通过“可能”,我的意思是:您当然可以创建一个列表结构在堆栈上保留内存,但这会很奇怪,动态数据结构通常使用堆)。 (此外,在这种特定情况下,我更喜欢 BFS 而不是 DFS,我认为它应该在某些极端情况下使列表增长得更少)
  • e 已构造但从未删除。 bnum 从 1 开始使用 - 即,bnum == 0 从不使用。我不知道这些是否会对 1000 x 1000 产生任何影响,但清理起来不会有什么坏处。
  • 此外,这里不需要那些else continue;(并且无害)。没有它会更简单。

标签: c++ recursion stack-overflow


【解决方案1】:

在不改变程序逻辑的情况下避免递归的一种简单方法是直接使用堆栈数据结构,而不是通过调用堆栈。

这是 Blob 类的修改版本,它在表单函数中使用 std::stack

class Blob
{
public:
    long long int pow;

    Blob(int i, int j)
    {
        this->pow = 0;
        point po;
        po.x=i;
        po.y=j;
        this->form(po);
    }

    int getPow()
    {
        return this->pow;
    }

    void form (point p)
    {
        std::stack<point> s;
        s.push(p);

        while (!s.empty())
        {
            p=s.top();
            s.pop();

            if (!inBounds(p))
                continue;

            if(a[p.x][p.y] == '#' && !pat[p.x][p.y])
            {
                a[p.x][p.y] = 1;
                this->pow++;
                point e;
                e.x = p.x-1;    e.y =  p.y;     if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x+1;    e.y =  p.y;     if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x+1;    e.y =  p.y+1;   if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x-1;    e.y =  p.y-1;   if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x-1;    e.y =  p.y+1;   if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x+1;    e.y =  p.y-1;   if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x;      e.y =  p.y-1;   if(pat[e.x][e.y] == 0)s.push(e);
                e.x = p.x;      e.y =  p.y+1;   if(pat[e.x][e.y] == 0)s.push(e);
            }

        }
    }
};

请注意,这也可以解决您的内存泄漏问题。

一般来说,您要解决的问题似乎是寻找具有方形邻域的“连接组件”。通常,您会使用disjoint-set data-structure 来解决这个问题,它不需要堆栈或递归。这样,您只需扫描一次该字段,您就可以获得所有连接组件的大小,而不仅仅是其中的一个你检查一个blob。

【讨论】:

    【解决方案2】:

    在我看来,整数矩阵pat[][] 被初始化为全零,在多个地方进行了测试,但从未设置为其他任何值。因此,Blob 构造函数调用form(),它几乎无条件地调用自身,直到发生崩溃。我说“几乎”是因为还有其他条件导致递归调用,但最后一次检查(pat 中的值)总是成功。

    我可能读得太快了,如果是这样,我会谦虚地接受我的殴打。 ;-)

    【讨论】:

    • a[x][y] 对于已访问的节点设置为 1,因此不再访问,因为只访问了“#”个。
    • @ltjax,是的,你说的很对。经过仔细检查,我发现“访问标记”在被发现时做得很好(立即,无条件地)。 pat 似乎仍然毫无用处,但我也不再怀疑它会造成任何伤害。
    猜你喜欢
    • 2013-06-28
    • 2011-08-15
    • 1970-01-01
    • 2020-10-03
    • 2010-11-30
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多