递归转迭代实操记录

针对那些经典的像素游戏设计的自动切图工具里用到种子填充算法的实现。

一开始是用递归实现的,后来遇到一些头像之类的比较大一点的图素,运行的时候经常占满C#默认的1M线程栈内存而崩溃。尝试使用各种多线方式改造并没有成功,于是干脆改成迭代形式,创建一个Stack自己完全精确控制其中的数据操作。

这里截取一段改成迭代后的代码,这是窗体的事件处理用cs代码。因为只是个小工具,没有完全把界面和逻辑分离。

        //堆栈最大深度
        private const int MaxStackFrames = 640 * 480;
        private Stack<FillStackFrame> mStack = new Stack<FillStackFrame>(MaxStackFrames);

        private void fillRegion(int x, int y)
        {
            //防止多线程操作堆栈引起的问题
            lock (this)
            {
                //从UI初始化本次调用不会变的参数
                int distance = Convert.ToInt32(txtDistance.Text);

                //复位堆栈并且压入初始数据
                mStack.Clear();
                var frame = new FillStackFrame();
                frame.x = x;
                frame.y = y;
                mStack.Push(frame);

                //堆栈为空即所有连接区域被填充完毕,执行结束
                while (mStack.Count > 0)
                {
                    //弹出堆栈数据
                    var popped = mStack.Pop();
                    x = popped.x;
                    y = popped.y;

                    //递归终止条件1 遇到填充区域越界
                    if (x < 0 || y < 0 || x >= srcImg.Width || y >= srcImg.Height) continue;
                    //递归终止条件2 遇到背景色,或者已搜索像素
                    if (!isTransparent(srcImg, x, y) && tmpImg.GetPixel(x, y).A != 255)
                    {
                        tmpImg.SetPixel(x, y, fillColor);
                        if (maxX < x) maxX = x;
                        if (minX > x) minX = x;
                        if (maxY < y) maxY = y;
                        if (minY > y) minY = y;
                        for (int i = 1; i <= distance; ++i)
                        {
                            if (mStack.Count >= MaxStackFrames) throw new StackOverflowException("填充蒙版时堆栈溢出!");
                            //八个方向延伸搜索,这里的所有堆栈Push操作换回FillRegion方法的调用,就是原本的递归写法了
                            mStack.Push(new FillStackFrame() { x = x - i, y = y});
                            mStack.Push(new FillStackFrame() { x = x + i, y = y});
                            mStack.Push(new FillStackFrame() { x = x, y = y - i});
                            mStack.Push(new FillStackFrame() { x = x, y = y + i});

                            mStack.Push(new FillStackFrame() { x = x - i, y = y - i});
                            mStack.Push(new FillStackFrame() { x = x + i, y = y + i});
                            mStack.Push(new FillStackFrame() { x = x - i, y = y + i});
                            mStack.Push(new FillStackFrame() { x = x + i, y = y - i});
                        }
                    }
                }
            }
        }

总结一下递归转迭代的操作步骤:

  1. 初始化函数调用前用到的类的成员变量和常量,放在类的成员变量或者常量直接初始化就可以。
  2. 初始化在本次调用不变的数据比如这里的延伸距离distance,放在进入迭代循环之前。
  3. 把递归函数的参数合成一个类型Frame,创建一个Stack<Frame>来代替运行时提供的栈内存。这个Stack根据具体情况可以是类的成员变量也可以是函数的局部变量。
  4. 压入初始的传入参数帧
  5. 进入迭代循环,迭代循环基本就是原本函数的递归执行体改造过来。
  6. 迭代循环中把所有递归调用自身的函数换成新参数构建成帧并且压入Stack。
  7. 原有的return改成对迭代循环的continue。
  8. 如果有跳出所有原来递归的需要,在迭代循环中加入break。
  9. 其他操作顺序维持不变。

 

posted @ 2019-08-26 14:07  FancyBit  阅读(203)  评论(0编辑  收藏  举报