新边表算法
⑴ 区域填充的主要思想和方法
扫描线种子填充算法思想
首先填充种子所在的尚未填充的一区段,然后确定与这一区段相邻的上下两条扫描线上位于该区段内是否存在需要填充的新区段,如果存在,则依次把每个新区段最右端的象素作为种子放入堆栈。反复这个过程,直到堆栈为空。
扫描线种子填充算法步骤 1、初始化堆栈。 2、种子压入堆栈。 3、While(堆栈非空)从堆栈弹出种子象素。
(1)如果种子象素尚未填充,则: ① 求出种子区段:xleft、xright。
② 填充整个区段。 (2)检查相邻的上扫描线的xleft≤x≤xright区间内,是否存在需要填充的新区段,如果存在,则把每个新区段在xleft≤x≤xright范围内的最右边的象素,作为新的种子象素依次压入堆栈。 (3)检查相邻的下扫描线的xleft≤x≤xright区间内,是否存在需要填充的新区段,如果存在,则把每个新区段在xleft≤x≤xright范围内的最右边的象素,作为新的种子象素依次压入堆栈。 }
有关堆栈操作的辅助代码
1、定义栈结构: # define MAX 100 /*定义最大栈空间*/
struct stack
{
int top; /*指向栈顶的计数器*/
int xy[MAX][2]; /*种子点(二维)*/
}s; 2、初始化堆栈 s.top=-1; 3、进栈操作 pushxy(int x,int y)
{
if(s.top= =MAX-1)
{
printf(“Overflow!”);
exit(1);
}
else
{
s.top=s.top+1;
s.xy[s.top][0]=x;
s.xy[s.top][1]=y;
}
} 4、出栈操作 popxy(int *x,int *y)
{
if(s.top<0)
{
printf(“underflow!”);
exit(1);
}
else
{
*x=s.xy[s.top][0];
*y=s.xy[s.top][1];
s.top=s.top-1;
}
} 5、堆栈非空 s.top!=-1 或者 s.top>=0 扫描线种子填充算法伪代码 scanline_seed_fill(int x,int y,int boundarycolor,int newcolor)
{
int savex,xleft,xright,pflag,xenter;
//初始化堆栈;
pushxy(x,y); /*种子压入堆栈*/
while(堆栈非空)
{
popxy(&x,&y); /*栈顶象素出栈*/
savex=x; /*保存种子坐标x分量的值*/
while(getpixel(x,y)!=boundarycolor) /*获取该点的颜色值*/
{
putpixel(x,y, newcolor ); /*填充种子右侧的象素*/
x++;
}
xright=x-1; /*得到种子区段的右端点*/
x=savex-1; /*准备向种子左侧填充*/
while(getpixel(x,y)!=boundarycolor) /*获取该点的颜色值*/
{
putpixel(x,y, newcolor ); /*填充种子左侧的象素*/
x--;
}
xleft=x+1; /*得到种子区段的左端点*/
x=xleft;
y=y+1; /*考虑种子相邻的上扫描线*/
while(x<=xright)
{
pflag=0; /*找到新种子的标志:0为假;1为真*/
while(getpixel(x,y)!=boundarycolor && getpixel(x,y)!=newcolor&& x<xright)
{
if(pflag= =0)
pflag=1;
x++;
}
if(pflag= =1)
{
if((x= =xright)&&(getpixel(x,y)!=boundarycolor)&&(getpixel(x,y)!=newcolor))
pushxy(x,y); /*新区间超过xright,将代表该区段的象素进栈*/
else
pushxy(x-1,y); /*新区段右端点作为种子进栈*/
pflag=0;
}
xenter=x;
while((getpixel(x,y)==boundarycolor||getpixel(x,y)==newcolor)&&x<xright)
{
x++;/*向右跳过分隔带*/
}
if(xenter==x) x++;/*处理特殊情况,以退出while(x<=xright)循环*/
}
x=xleft; /*为下扫描线的处理作准备*/
y=y-2;
/*检查相邻的下扫描线,找新区段,并将每个新区段右端的象素作为种子
入栈,其方法与上扫描线的处理一样,这里省略。要求同学补充完整。*/
}
} 边相关多边形扫描线填充思想
边相关扫描线填充算法的实现需要建立两个表:边表(ET)和活动边表(AET)。
ET用来对除水平边外的所有边进行登记,即建立边的记录。
AET则是在ET建立的基础上进行扫描转换。对不同的扫描线,与之相交的边线也是不同的,当对某一条扫描线进行扫描转换时,我们只需要考虑与它相交的那些边线,为此AET建立了只与当前扫描线相交的边记录链表,以提供对当前扫描线上的区段进行填充。
边相关多边形扫描线填充算法步骤
1、根据给出的顶点坐标建ET表;并求出顶点坐标中最大y值ymax和最小y值ymin。
2、定义AET指针,并使它为空。
3、使用扫描线的yj值作为循环变量,使其初值为ymin。
4、对于循环变量yj的每一整数值,重复作以下事情,直到yj大于ymax,或ET与AET表都为空为止:
① 如果ET中yj桶非空,则将yj桶中的全部记录合并到AET中。
② 对AET链中的记录按x的大小从小到大排序。
③ 依次取出AET各记录中的xi坐标值,两两配对,对每对xi之间的象素填上所要求的颜色。
④ 如果AET中某记录的ymax=yj,则删除该记录。
⑤ 对于仍留在AET中的每个记录,用xi+1/m代替xi,这就是该记录边线与下一条扫描线yj+1的交点。
⑥ 使yj加1,以便进入下一轮循环。
边相关多边形扫描线填充为伪代码 #include <stdlib.h>
#include <graphics.h>
#include <stdio.h>
#define round(x) ((x>0)?(int)(x+0.5):(int)(x-0.5)) /*求舍入的宏*/
struct edge{ /*边记录结构*/
int ymax;
float xi;
float m;
struct edge *next;
};
void poly_fill(int,int *,int);
void main()
{
int polypoints[]={ /*多边形顶点坐标: x0,y0,x1,y1,... */
100,300, 200,200, 300,200, 300,350,
400,250, 450,300, 300,50, 100,150};
int gdriver=DETECT,gmode;
initgraph(&gdriver,&gmode,);
poly_fill(8,polypoints,4); /*用红色填充*/
getch();
closegraph();
}
/*将一条边记录插入边记录构成的链表的表头*/
void insert_et(struct edge *anedge,struct edge **p_edges)
{
struct edge *p;
p=*p_edges;
*p_edges=anedge;
anedge->next=p;
}
/*复制一条边记录插入有效边表,维持有效边表的有序性*/
short insert_aet(struct edge *p,struct edge **p_aet)
{
struct edge *q,*k,*l;
if(!(q=(struct edge *)malloc(sizeof(struct edge))))
{
printf(
OUT MEMORY IN INSERTING EDGE RECORD TO AET
);
return(0);
}
q->ymax=p->ymax; q->xi=p->xi;
q->m=p->m; q->next=NULL;
if(!(*p_aet)||((*p_aet)->xi>q->xi)||(((*p_aet)->xi==q->xi)&&((*p_aet)->m>q->m)))
{
l=*p_aet; *p_aet=q; q->next=l;
}
else
{
l=*p_aet;
k=l->next;
while(k&&(k->xi<q->xi))
{
l=k;
k=k->next;
}
if(k&&(k->xi==q->xi)&&(k->m<q->m))
{
l=k;
k=k->next;
}
l->next=q;
q->next=k;
}
return(1);
}
/*从(x1,y)到(x2,y)用color色绘水平直线*/
void draw_line(int x1,int x2,int y,int color)
{
int i;
y=getmaxy()-y; /*进行坐标变换*/
for(i=x1;i<=x2;i++)putpixel(i,y,color);
}
/*多边形扫描线填充:
numpoint是多边形顶点个数;
points存放多边形顶点坐标(x0,y0,x1,y1,...);
color是填充色*/
void poly_fill(int numpoint,int *points,int color)
{
struct edge **et=NULL,*aet,*anedge,*p,*q;
int i,j,maxy,miny,x1,y1,x2,y2,yi,znum;
maxy=miny=points[1];
znum=2*numpoint;
for(i=3;i<znum;i++)
{
if(maxy<points[i]) maxy=points[i];
else if(miny>points[i])miny=points[i];
i++;
}
if(!(et=(struct edge **)malloc((maxy-miny+1)*sizeof(struct edge *))))
{ /*建立边表ET */
printf(
OUT MEMORY IN CONSTRUCTING ET
);
return;
}
for(i=0;i<maxy-miny+1;i++) et[i]=NULL;
x1=points[znum-2]; y1=points[znum-1];
for(i=0;i<znum;i+=2)
{ /*处理多边形所有边,为每条非水平边建立一个边记录,并将其插到ET表中的合适位置 */
x2=points[i]; y2=points[i+1];
if(y1!=y2) /*只考虑非水平边*/
{
if(!(anedge=(struct edge *)malloc(sizeof(struct edge))))
{
printf(
OUT MEMORY IN CONSTRUCTING EDGE RECORD.
);
goto quit;
}
anedge->m=(float)(x2-x1)/(y2-y1);
anedge->next=NULL;
if(y2>y1) /*处理奇异点*/
{
j=i+1;
do{ /*向后划过所有水平边*/
if((j+=2)>=znum)j-=znum;
}while(points[j]==y2);
if(points[j]>y2) anedge->ymax=y2-1;
/*若(x2,y2)不是局部极值点,边记录的ymax域为y2-1,这样处理
扫描线y=y2时此边记录将不在AET中,从而不会产生交点 */
else anedge->ymax=y2; /*若(x2,y2)是局部极值点,边记录的ymax域为y2,
这样处理扫描线y=y2时此边记录将在AET中,从而会产生一个交点 */
anedge->xi=x1;
insert_et(anedge,&et[y1-miny]);
}
else
{
j=i+1; /*向前划过所有水平边*/
do{
if((j-=2)<0)j+=znum;
}while(points[j]==y1);
if(points[j]>y1) anedge->ymax=y1-1;
/*若(x1,y1)不是局部极值点,边记录的ymax域为y1-1,这样处理
扫描线y=y1时此边记录将不在AET中,从而不会产生交点 */
else anedge->ymax=y1; /*若(x1,y1)是局部极值点,边记录的ymax
域为y1,这样处理扫描线y=y1时此边记
录将在AET中,从而会产生一个交点 */
anedge->xi=x2;
insert_et(anedge,&et[y2-miny]);
}
}
x1=x2;
y1=y2;
}
aet=NULL; /*初始化有效边表AET*/
for(yi=miny;yi<=maxy;yi++) /*从低到高逐条处理扫描线*/
{ /*将ET表中与yi对应的边记录链表中的全部边记录
p=et[yi-miny]; 都按序并入AET中*/
while(p)
{
if(!insert_aet(p,&aet)) goto quit;
p=p->next;
}
p=aet;
while(p) /*依次取出AET各记录中的xi坐标值,两两配对,*/
{/*对每对xi之间的象素填上所要求的颜色*/
draw_line(round(p->xi),round(p->next->xi),yi,color);
p=p->next->next;
}
p=aet;
while(p&&(p->ymax==yi)) /*对AET中的每个记录,若它的ymax==yi, */
{/*则删除该记录,否则用xi+1/m代替xi,这就是该记录所对应的*/
aet=p->next; /*边线与下一条扫描线y=yi+1的交点 */
free(p);
p=aet;
}
while(p)
{
if(p->ymax==yi)
{
q->next=p->next;
free(p);
p=q->next;
}
else
{
p->xi+=p->m;
q=p;
p=p->next;
}
}
}
quit:
if(et) /*释放动态申请的内存*/
{
for(yi=miny;yi<=maxy;yi++)
{
q=p=et[yi-miny];
while(p)
{
q=p->next;
free(p);
p=q;
}
}
free(et);
}
} 边标志填充算法思想
扫描线具有连贯性,这种连贯性只有在扫描线与多边形相交处才会发生变化,而每次的变化结果:无非是在前景色和背景色之间相互“切换”。
边标志填充算法正是基于这一发现,先在屏幕上生成多边形轮廓线,然后逐条扫描线处理。处理中:逐点读取象素值,若为边界色,则对该象素值进行颜色切换。
边标志填充算法步骤 1、用边界色画出多边形轮廓线,也就是将多边形边界所经过的象素打上边标志。
2、为了缩小范围,加快填充速度,须找出多边形的最小包围盒:xmin、ymin、xmax、ymax。
3、逐条扫描线进行处理,初始时标志为假,对每条扫描线依从左往右的顺序,逐个访问该扫描线上的象素。每遇到边界象素,标志取反。然后,按照标志是否为真决定象素是否为填充色。
边标志填充算法伪代码 EdgeMarkFill(int p[][2],int n,int boundarycolor,int newcolor)
{
int i,x,y,flag,xmin,xmax,ymin,ymax;
setcolor(boundarycolor); /*设置画笔色*/
for(i=0 ;i<n;i++)/*画出多边形的n条边*/
line(p[i][0], p[i][1], p[(i+1)%n][0], p[(i+1)%n][1]);
/*用求极值的算法,从多边形顶点数组p中,求出xmin,xmax,ymin,ymax*/
for(y=ymin;y<=ymax;y++)
{
flag=-1;
for(x=xmin;x<=xmax;x++)
{
if(getpixel(x,y)= = boundarycolor) flag=-flag;
if(flag= =1)putpixel(x,y, newcolor);
}
}
}
⑵ 求c语言图的深度优先遍历算法
#define MaxVerNum 100 /* 最大顶点数为*/
typedef enum {False,True} boolean;
#include "stdio.h"
#include "stdlib.h"
boolean visited[MaxVerNum];
typedef struct node /* 表结点*/
{
int adjvex;/* 邻接点域,一般是放顶点对应的序号或在表头向量中的下标*/
char Info; /*与边(或弧)相关的信息*/
struct node * next; /* 指向下一个邻接点的指针域*/
} EdgeNode;
typedef struct vnode /* 顶点结点*/
{
char vertex; /* 顶点域*/
EdgeNode * firstedge; /* 边表头指针*/
} VertexNode;
typedef struct
{
VertexNode adjlist[MaxVerNum]; /* 邻接表*/
int n,e; /* 顶点数和边数*/
} ALGraph; /* ALGraph是以邻接表方式存储的图类型*/
//建立一个无向图的邻接表存储的算法如下:
void CreateALGraph(ALGraph *G)/* 建立有向图的邻接表存储*/
{
int i,j,k;
int N,E;
EdgeNode *p;
printf("请输入顶点数和边数:");
scanf("%d %d",&G->n,&G->e);
printf("n=%d,e=%d\n\n",G->n,G->e);
getchar();
for(i=0;i<G->n;i++) /* 建立有n个顶点的顶点表*/
{
printf("请输入第%d个顶点字符信息(共%d个):",i+1,G->n);
scanf("%c",&(G->adjlist[i].vertex)); /* 读入顶点信息*/
getchar();
G->adjlist[i].firstedge=NULL; /* 顶点的边表头指针设为空*/
}
for(k=0;k<2*G->e;k++) /* 建立边表*/
{
printf("请输入边<Vi,Vj>对应的顶点序号(共%d个):",2*G->e);
scanf("%d %d",&i,&j);/* 读入边<Vi,Vj>的顶点对应序号*/
p=(EdgeNode *)malloc(sizeof(EdgeNode)); // 生成新边表结点p
p->adjvex=j; /* 邻接点序号为j */
p->next=G->adjlist[i].firstedge;/* 将结点p插入到顶点Vi的链表头部*/
G->adjlist[i].firstedge=p;
}
printf("\n图已成功创建!对应的邻接表如下:\n");
for(i=0;i<G->n;i++)
{
p=G->adjlist[i].firstedge;
printf("%c->",G->adjlist[i].vertex);
while(p!=NULL)
{
printf("[ %c ]",G->adjlist[p->adjvex].vertex);
p=p->next;
}
printf("\n");
}
printf("\n");
} /*CreateALGraph*/
int FirstAdjVertex(ALGraph *g,int v)//找图g中与顶点v相邻的第一个顶点
{
if(g->adjlist[v].firstedge!=NULL) return (g->adjlist[v].firstedge)->adjvex;
else return 0;
}
int NextAdjVertex(ALGraph *g ,int vi,int vj )//找图g中与vi相邻的,相对相邻顶点vj的下一个相邻顶点
{
EdgeNode *p;
p=g->adjlist[vi].firstedge;
while( p!=NULL && p->adjvex!=vj) p=p->next;
if(p!=NULL && p->next!=NULL) return p->next->adjvex;
else return 0;
}
void DFS(ALGraph *G,int v) /* 从第v个顶点出发深度优先遍历图G */
{
int w;
printf("%c ",G->adjlist[v].vertex);
visited[v]=True; /* 访问第v个顶点,并把访问标志置True */
for(w=FirstAdjVertex(G,v);w;w=NextAdjVertex(G,v,w))
if (!visited[w]) DFS(G,w); /* 对v尚未访问的邻接顶点w递归调用DFS */
}
void DFStraverse(ALGraph *G)
/*深度优先遍历以邻接表表示的图G,而以邻接矩阵表示时,算法完全相同*/
{ int i,v;
for(v=0;v<G->n;v++)
visited[v]=False;/*标志向量初始化*/
//for(i=0;i<G->n;i++)
if(!visited[0]) DFS(G,0);
}/*DFS*/
void main()
{
ALGraph G;
CreateALGraph(&G);
printf("该无向图的深度优先搜索序列为:");
DFStraverse(&G);
printf("\nSuccess!\n");
}
⑶ 图的五种存储结构
图的邻接矩阵(Adjacency Matrix): 图的邻接矩阵用两个数组来表示图。一个一维数组存储图中顶点信息,另一个二维数组(一般称之为邻接矩阵)来存储图中的边或者弧的信息。从邻接矩阵中我们自然知道一个顶点的度(对于无向图)或者有向图中一个顶点的入度出度信息。
假设图G有n个顶点,则邻接矩阵是一个n*n的方阵。
1.对于如果图上的每条边不带权值来说,那么我们就用真(一般为1)和假(一般为0)来表示一个顶点到另一个顶点存不存在边。下面是一个图的邻接矩阵的定义:
邻接矩阵法实现带权值的无向图的创建如下:
按照如图输入各边(不重复)
测试程序如下:
结果可得该矩阵,证明创建树成功。 假设n个顶点e条边的创建,createGraph算法的时间复杂度为O(n+n*n+e)。如果需要创建一个有向图,那么和上面一样一个一个录入边下标和权值。
邻接矩阵这种存储结构的优缺点: 缺点是对于边数相对顶点较少的稀疏图来说会存在极大的空间浪费。假设有n个顶点,优点是对于有向完全图和无向完全图来说邻接矩阵是一种不错的存储结构,浪费的话也只浪费了n个顶点的容量。
在树的存储结构一节中我们提到对于孩子表示法的第三种:用一段连续的存储单元(数组)存储树中的所有结点,利用一个单链表来存储数组中每个结点的孩子的信息。对于图的存储结构来说,我们也可以利用这种方法实现图的存储
邻接表(Adjacency List): 这种数组与链表相结合的存储方法叫做邻接表。1.为什么不也用单链表存储图的结点信息呢?原因就是数组这种顺序存储结构读取结点信息速率快。对于顶点数组中,每个数据元素还需要存储一个指向第一个邻接顶点的指针,这样才可以查找边的信息2.图中每个顶点Vi(i > 0)的所有邻接点构成一个线性表 (在无向图中这个线性表称为Vi的边表,有向图中称为顶点作为弧尾的出边表) ,由于邻接点的不确定性,所以用链表存储,有多少个邻接点就malloc一个空间存储邻接点,这样更不会造成空间的浪费(与邻接矩阵相比来说)。3.对于邻接表中的某个顶点来说,用户关心的是这个顶点的邻接点,完全可以遍历用单链表设计成的边表或者出边表得到,所以没必要设计成双链表。
邻接表的存储结构:
假设现在有一无向图G,如下图:
从邻接表结构中,知道一个顶点的度或者判断两个顶点之间是否存在边或者求一个顶点的所有邻接顶点是很容易的。
假设现在有一有向图G,如下图:
无向图的邻接表创建示例如下:
假设在上图(无向图)中的V0V1V2V3顶点值为ABCD,则依据下面测试程序可得结果:
邻接表的优缺点: 优点是:邻接表存储图,既能够知道一个顶点的度和顶点的邻接结点的信息,并且更不会造成空间的浪费。缺点是邻接表存储有向图时,如果关心的是顶点的出度问题自然用邻接表结构,但是想了解入度需要遍历图才知道(需要考虑逆邻接表)。
十字链表(Orthogonal List) :有向图的一种存储方法,它把邻接表和逆邻接表结合起来,因此在十字链表结构中可以知道一个顶点的入度和出度情况。
重新定义顶点表的结点如下图:
现在有一有向图如下图:
则它的存储结构示意图为:
其定义如下:
十字链表是用来存储有向图的,这样可以看出一个顶点的出入度信息。对于无向图来说完全没必要用十字链表来存储。
在无向图中,因为我们关注的是顶点的信息,在考虑节约空间的情况下我们利用邻接表来存储无向图。但是如果我们关注的是边的信息,例如需要删除某条边对于邻接表来说是挺繁琐的。它需要操作两个单链表删除两个结点。因此我们仿照十字链表的方式对边表结点结构重新定义如下图:
它的邻接多重表结构为:
多重邻接表的优点:对于边的操作相比于邻接表来说更加方便。比如说我们现在需要删除(V0,V2)这条边,只需将69步骤中的指针改为nullptr即可。
边集数组(edgeset array): 边集数组是由两个数组组成,一个存储顶点信息,另一个存储边的信息,这个边数组中的每个数据元素由起点下标,终点下标,和权组成(如果边上含有权值的话)。
边数组结构如下图:
边集数组实现图的存储的优缺点:优点是对于边的操作方便快捷,操作的只是数组元素。比如说删除某条边,只需要删除一个数组元素。缺点是:对于图的顶点信息,我们只有遍历整个边数组才知道,这个费时。因此对于关注边的操作来说,边集数组更加方便。