图的基本概念
在线性表中,数据元素之间是被串起来的,仅有线性关系,每个数据元素只有一个直接前驱和一个直接后继。在树形结构中,数据元素之间有着明显的层次关系,并且每一层上的数据元素可能和下一层中多个元素相关,但只能和上一层中一个元素相关。图是一种较线性表和树更加复杂的数据结构。在图形结构中,结点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。
图的定义
图(Graph)是由顶点的有穷非空集合V ( G ) 和顶点之间边的集合E ( G )组成,通常表示为: G = ( V , E ) 其中,G表示图,V 是图G 中顶点的集合,E 是图G 中边的集合。
注意:线性表可以是空表,树可以是空树,但图不可以是空图。就是说,图中不能一个顶点也没有,图的顶点集V一定非空,但边集E可以为空,此时图中只有顶点而没有边。
图的顺序存储
邻接矩阵
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
下图是一个无向图和它的邻接矩阵:
下图是有向图和它的邻接矩阵:
对于带权图而言,若顶点之间有边相连,则邻接矩阵中对应项存放着该边对应的权值
下图是有向网图和它的邻接矩阵:
通过上面对无向图、有向图和网的邻接矩阵比较,可以定义出图顺序存储的结构类型
#define N 1024//最大顶点存储数量
typedef char g_data_t;
typedef struct sqg{
g_data_t v_buf[N];//存放顶点的一维数组
int e_buf[N][N];//存放边的二维数组
int v_number;//顶点的数量
int e_number;//边的数量
int flag[N];//访问标志位
}sg_node, *sg_pnode;
注意:无向图和网的存储邻接矩阵是对称矩阵
下面以一个实例来讲解图的顺序存储实现原理:
如上图所示,其邻接矩阵可以表示为:
图的初始化
sg_pnode create_g()
{
//创建空间
sg_pnode G = (sg_pnode)malloc(sizeof(sg_node));
if(NULL == G)
{
printf("g is NULL\n");
return NULL;
}
//给顶点赋值
printf("please input V number->");
scanf("%d", &(G->v_number));
getchar();
int s;
for(s=0;s<G->v_number;s++)
G->flag[s]=0;
printf("please input V data->");
int i,j;
for(i = 0; i < G->v_number; i++)
{
scanf("%c", &(G->v_buf[i]));
}
getchar();
//给边赋值
printf("please input E number->");
scanf("%d", &(G->e_number));
getchar();
/*
for(i = 0; i < G->v_number; i++)
for(j = 0; j < G->v_number; j++)
{
G->e_buf[i][j] = 0
}
*/
memset(G->e_buf,0,sizeof(G->e_buf));
//for(i=0;i<N;i++)
// G->e_buf[i][i]=1;
for(i = 0; i < G->e_number; i++)
{
printf("please input e data->");
int p, q,x;
scanf("%d%d%d", &p, &q,&x);//x代表权值,实现网的结构
G->e_buf[p][q] = x;//若不表示网,直接赋值为1就行,代表两个顶点连接
G->e_buf[q][p] = x;//无向图才有反向,有向图没有
}
return G;
}
这里初始化的是网这个结构,和无向图的区别在于:对于有关系的两个顶点,若是图,其邻接矩阵中用1来表示,而如果是网,邻接矩阵中存储的就是实际的权值。
打印邻接矩阵
int show_g(sg_pnode G)
{
//printf("V data is\n");
printf("the relationship of the Graph:\n");
printf(" ");
int i,j;
for(i = 0; i < G->v_number; i++)
{
printf("%2c ", G->v_buf[i]);
}
printf("\n");
//printf("E data is\n");
for(i = 0; i < G->v_number; i++)
{
printf("%c:",G->v_buf[i]);
for(j = 0; j < G->v_number; j++)
{
printf("%2d ", G->e_buf[i][j]);
}
printf("\n");
}
return 0;
}
图的广度优先遍历:BFS
图的广度优先遍历需要借助队列来实现,其算法步骤为:
1、首先创建一个队列,随机选取一个顶点入队
2、进入循环,循环结束条件为队列为空
3、出队,若该元素未被访问,则访问该元素,并将其访问标志位置为1表示已访问
4、查找与当前出队元素有关系且未被访问的元素,若有,则入队该元素,
5、重复2至4步,直至遍历完成
注意:遍历完成后需要将所有元素访问标志位恢复为0.
//广度遍历
void BFS(sg_pnode G)
{
qnode Q=queue_init();
push_queue(Q,0);
while(!empty_queue(Q))
{ //puts("!!!");
int data=pop_queue(Q);
if(G->flag[data]==0)
{
printf("%c ",G->v_buf[data]);
G->flag[data]=1;
}
int i;
for(i=0;i<G->v_number;i++)
if(G->e_buf[data][i]>0&&G->flag[i]==0)
push_queue(Q,i);
}
memset(G->flag,0,sizeof(G->flag));
puts("");
}
图的深度优先遍历 :DFS
图的深度优先遍历需要借助栈来实现,其算法步骤为:
1、首先创建一个栈,随机选取一个顶点入栈
2、进入循环,循环结束条件为栈为空
3、访问栈顶元素,若该元素未被访问,则访问该元素,并将其访问标志位置为1表示已访问
4、查找与当前栈顶元素有关系且未被访问的元素,若有,则入栈该元素;若没有,则将栈顶元素出栈
5、重复2至4步,直至遍历完成
注意:遍历完成后需要将所有元素访问标志位恢复为0.
//深度遍历
void DFS(sg_pnode G)
{
p_node S=stack_init();
push_stack(S,0);
while(!empty_stack(S))
{
int data=top(S);
if(G->flag[data]==0)
{
printf("%c ",G->v_buf[data]);
G->flag[data]=1;
}
int i,flag=0;
for(i=0;i<G->v_number;i++)
if(G->e_buf[data][i]>0&&G->flag[i]==0)
{
push_stack(S,i);
flag=1;
}
if(flag==0)
pop_stack(S);
}
memset(G->flag,0,sizeof(G->flag));
puts("");
}
遍历结果