首先要说的是他的实验报告写的很草率所以我也不确定他到底要求做啥他只说实现区域填充算法就行,那肯定咋简单咋来,所以我就自己写了一个没按照老师发的来,不过算法思维结果都是一样的我这个无论是时间复杂度还是空间复杂度还是实现难度上都比老师发的要优。
种子填充算法本质就是BFS,DFS搜索(和本次实验无关:关于搜索我在B站也有录视频讲过链接,我最近也在深入研究搜索算法有兴趣的朋友可以一起交流),这个在之前的数据结构课是学了的就是广度优先和深度优先,不过老师发的源码让我不能理解的就是他是用栈实现的BFS,因为一般BFS都是队列或者优先队列实现DFS才是递归用栈。
这里我主要说如何用BFS实现。
种子填充算法主要流程是这样的:
队列就是一种满足先进先出的线性表数据结构,就可以理解为一个队列,排在队头的人先出队,队尾的人后出队,按顺序来(就正常逻辑,别想复杂)
c++ 的话可以通过封装好的队列 queue 来达到我们要的效果,这里简单说一下常用的操作
#include //c++标准头文件
#include //封装队列的头文件
using namespace std;//c++ 标准库命名空间
//上面这些不纠结感兴趣自己查不感兴趣就不管加上就行
int main(){
queue <int> q; //声明一个队列 里面装的元素是int
queue <char> q1;//声明一个队列 里面装的元素是char
q.push(1);//将 1 放入队列q
q.push(2);//同上 2 进队
q.push(3);//同上
cout<<q.front()<<endl;//q.front()是取出队头元素
//cout是输出 endl是回车就这么理解虽然不严谨
q.pop();//弹出队头元素 你可以理解为删除
cout<<q.front()<<endl;//输出队头因为原本队头1出队了所以现在是2
if(q.empty()) cout<<"队列为空"<<endl;//q.empty()是判断这个队列是否为空返回值为bool
else cout<<"队列不为空"<<endl; //bool类型就是逻辑变量true 和 false 真或假
cout<<q.size()<<endl;//队列长度
//其他的自己查查吧就不介绍了,嗦一下front 和 pop的区别
//front时取出队头但不删除,pop是删除
return 0;
}
四联通或者说四连接就一个点的上下左右四个点
那你现在再回去看看算法流程就是,先确定一个起始点我们称为种子点(要在填充图形内部),将他先入队,然后每次从队列取出队头元素,将其上色,然后判断这个点的上下左右四个点,看是否满足条件(不是边界点,且没有上色)如果满足将他入队,然后一直这样下去直到队列为空。
用队列的原因就是因为可以将不用的点弹出优化空间,减少内存消耗,不用栈是因为他容易爆而且他先进后出(他凭什么后来居上),如果内存不够就手动调一下堆栈内存或者开全局,全局变量放在堆里的内存管够
八联通和四联通一样就是四个方向多了个左上,左下,右上,右下,
为啥要这样啊因为有这种特殊情况
对于左边那个x点绿色是四联通到的点,橙色是八联通能额外到的点,四联通到不了左上角的空白区域所以无法填充,而八联通可以,右边就是八联通填充效果。
八联通就放向多加四个就行,就这么简单,但是这样也会变慢很多,因为他每次扩展多扩展了一倍
我接下来说的就是我写的优化版本了,如果只是应付实验报告的话,用老师的代码套上面我写的就行。
在我写的时候有个bug不过懒得找反正能跑,而且结果对。
我这里主要优化除了刚刚用BFS队列去优化以外,还实现了四联通转八联通的优化,外加哈希表存储已填充的点。
之所以用八联通就是无法处理上述那种特殊情况,但是八联通很慢也很占内存,因为扩展了八个点,所以我设想能否去优化四联通去实现这种特殊情况,我这里用的方法是四联通但是方向不是上下左右,而是左上左下右上右下四个方向,然后一开始引入两个相邻种子点,为什么呢,看图:
1,2点是一开始入队的两个相邻种子点,黑色点是2好点四联通扩展出的所有点,白色点是1号点四联通扩展出的所有点,所以我们只用了四联通却实现了八联通的效果。切实际情况测试下来,每次扩展其实平均在两次(手动模拟一下就知道了),从八降到了二,无论是空间还是时间都是质的提升。
对于向外扩展的点判断其是否是在填充图形内可以参考之前的有效边表的处理,内部点才入队将其上色
从任意位置p作一条射线,若与该射线相交的多边形边的数目为奇数,则p是多边形内部点,否则是外部点。
首先使多边形的边变为矢量。
将环绕数初始化为零。
再从任意位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当多边形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。
处理完多边形的所有相关边之后,若环绕数为非零,则p为内部点,否则,p是外部点。
这个就是个拓展主要处理区域重叠。
这是我声明的头文件和所有用到的变量啥的我都开全局了,免得放在头文件你们不知道,BFS换成你们的项目名称之前博客也说了的不再过多解释了
#include "pch.h"
#include "framework.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义,并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "BFS.h"
#endif
#include "BFSDoc.h"
#include "BFSView.h"
#include
#include
#include
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
using namespace std;
queue <CPoint> q;
vector <CPoint> v;
int fx[15] = { 1,-1,0,0,1,1 ,-1,-1 };
int fy[15] = { 0,0,1,-1 ,1,-1,1,-1 };
unordered_map <int, unordered_map<int, bool> > vis;//哈希表
CPoint P[105];
初始化
CBFSView::CBFSView() noexcept
{
// TODO: 在此处添加构造代码
while (!q.empty())q.pop();
vis.clear();
//P是存的要绘制的图形的点
//P我开在了头文件,如果你不知道开在哪你就开全局 CPoint P[105]
//如果不知道我说的啥意思就看之前我写的博客这些我都说过很多次了
P[0].x = 1; P[0].y = -1;
P[1].x = 150; P[1].y = -1;
P[2].x = 150; P[2].y = 150;
P[3].x = 0; P[3].y = 150;
P[4].x = 0; P[4].y = 0;
P[5].x = -150; P[5].y = 0;
P[6].x = -150; P[6].y = -150;
P[7].x = 1; P[7].y = -150;
}
OnDraw函数(这个不知道就看我之前的博客)
void CBFSView::OnDraw(CDC* pDC)
{
CBFSDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
CRect rect;
GetClientRect(&rect); //获得客户区的大小
pDC = GetDC(); //定义设备上下文指针
pDC->SetMapMode(MM_ANISOTROPIC); //自定义坐标系
pDC->SetWindowExt(rect.Width(), rect.Height()); //设置窗口比例
pDC->SetViewportExt(rect.Width(), -rect.Height()); //设置视区比例,且x轴水平向右,y轴垂直向上
pDC->SetViewportOrg(rect.Width() / 2, rect.Height() / 2);//设置客户区中心为坐标系原点
rect.OffsetRect(-rect.Width() / 2, -rect.Height() / 2);
//普通的绘制图形交互绘制我讲过了想优化的看之前博客
pDC->MoveTo(P[0]);
for (int i = 1; i < 8; i++) {
pDC->LineTo(P[i]);
}
pDC->LineTo(P[0]);
DrawObject(pDC);//种子填充
}
DrawObject实现
void CBFSView::DrawObject(CDC* pDC)
{
CPoint no, ne;//no是当前点,ne是下一个点
//q是队列,我用队列模拟出了点小bug懒的找就用vector重新存了一遍
q.push({ 100,100 });
q.push({ 101,100 });
v.push_back({ 100,100 });
v.push_back({ 101, 100 });
int cou = 0,cnt=1;
while (cou<v.size()) {
no = v[cou++];
//no = q.front();
//q.pop();
pDC->SetPixelV(no, RGB(255, 0, 0));
for (int i=4;i<8;i++) {
ne.x = no.x + fx[i];
ne.y = no.y + fy[i];
if (!vis[ne.x][ne.y]&&pDC->GetPixel(ne)!=RGB(0,0,0)) {
v.push_back(ne);
//q.push(ne);
vis[ne.x][ne.y] = true;
}
}
}
ReleaseDC(pDC);
}
填充图形都一样的
VID_20220418_163552
VID_20220418_163924