递归转迭代实操记录

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

一开始是用递归实现的,后来遇到一些头像之类的比较大一点的图素,运行的时候经常占满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. 其他操作顺序维持不变。

原文地址:https://www.cnblogs.com/fancybit/p/11412216.html

时间: 2024-10-10 10:58:05

递归转迭代实操记录的相关文章

SFUD+FAL+EasyFlash典型场景需求分析,并记一次实操记录

SFUD+FAL+EasyFlash典型场景需求分析:用整个flash存储数据,上千条数据,读取得时候用easyflash很慢,估计要检索整个flash太慢了. 改进方法:分区检索. 1存数据时,根据数据特征进行划分,划分到特定的某个区,分区存储这些数据. 2检索数据时,首先根据待检索数据的特征,获取具体需要检索哪个分区.然后在该分区内使用easyflash提供的kv查询接口进行检索. /***********************************************下面开始实操 

exchange 2010 新建角色实操记录

环境:windows server 2008 r2 企业版64位, exchange2010sp2 需求:设立exchange日常操作员角色,仅做新用户邮箱的创建和更改配置,关闭代理发送.管理完全访问权限等其他所有权限. 问题:系统内置的recipient management角色组包含角色太多,如下: Recipient Management分配的角色: Distribution Groups Mail Enabled Public Folders Mail Recipient Creatio

华硕主板搭配pike 2008阵列卡实操记录

说来惭愧,我这个做运维的人员,对于服务器还不是很了解,软硬件都不是很了解: 生命在于折腾,成功在于实践! 环境:华硕z9pr主板,pike 2008阵列卡,4块使用了10年的SATA口500G 7200转机械硬盘: 搭建教程可以参考这个:https://zhidao.baidu.com/question/366202204853583852.html 如果实在不行,我个人建议就是不怕死,就直接干,出现了Ctrl+c或者其他提示的时候不要犹豫,按就是了.然后看不懂英文没关系,有道翻译App你值得拥

实操记录之-----Ant Design of Vue 增强版动态合并单元格,自动根据数据进行合并,可自定义横纵向合并

前几天搞了个简易版的动态合并单元格 但是需求有变化,就只能稍微改改了~~ 欢迎路过的各位大佬指出我代码的问题~~~~ 另: 代码执行效率不是很高,如果需要大量渲染更多数据建议可以直接使用原生 <template> <page-view :title="title"> <h1>第一種數據結構,前端渲染</h1> <div class="snall-table-spacing"> <a-table :co

Mysql MHA(GTID)配置(实操)

实现环境 centos6.7 MYSQL5.6.36 主:192.168.1.191 从1:192.168.1.145 从2:192.168.1.146 监测:放在从2上 192.168.1.146 虚拟IP:192.168.1.222 准备软件包:下载链接: https://pan.baidu.com/s/1jHYafcU 密码: irbv epel-release-6-8.noarch.rpm   (所有服务器上都要) mha4mysql-node-0.56-0.el6.noarch.rpm

【基础服务】简单理解DNS的递归、迭代查询 - DNS(一)

DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串.通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析).DNS协议运行在UDP协议之上,使用端口号53.在RFC文档中RFC 2181对DNS有规范说明,RFC 2136对DNS的动态更新进行说明,RFC 2308对DNS查询的反向缓存进行说明. 简单理解DNS的递归.迭代查询过程: 客户端发

nginx实操(2)配置文件&内核&日志说明

优化内核参数 cat /etc/sysctl.conf net.ipv4.ip_forward = 0 表示开启路由功能,0是关闭,1是开启 net.ipv4.conf.default.rp_filter = 1 开启反向路径过滤 net.ipv4.conf.default.accept_source_route = 0 处理无源路由的包 net.ipv4.tcp_max_tw_buckets = 6000 表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT

Docker 学习笔记【3】 Docker 仓库、数据卷、数据卷容器,网络基础实操。高级网络配置学习

Docker 学习笔记[4] 高级网络配置实操,实战案例实验 =========================================================================== Docker 学习笔记[2] Docker 仓库实操,创建私有仓库,实操数据卷.数据卷容器,记录开始 =========================================================================== 被格式化的脚本内容: #开头代表

介绍RecoveryManager Plus几个备份虚拟机的最佳实操

介绍RecoveryManager Plus几个备份虚拟机的最佳实操虚拟机的架构与传统本地环境有很大的不同,并且需要不同的数据备份技术.本篇博客为您介绍几个备份虚拟机的最佳实操. 1.采取增量备份提高备份速度块修改跟踪 (CBT)可以显著加速备份速度.CBT持续跟踪自最后一次备份后数据变动的每一个存储块.您配置的备份申请程序查询虚拟化层,查找修改的块信息,备份改动模块,促进更快的增量备份. 快照没有备份快照不会拷贝所有VM数据.管理程序创建一个不同的磁盘:一个特殊类型的虚拟硬盘,与主虚拟硬盘存在