part 8 图(略)
假如你要周游中国, 现在在湖北,周边有安徽,江西,湖南,重庆,陕西,河南等城市。你下一步怎么走比较划算?
1 图的概念
1.1 背景
现实生活中,社交,地图导航等多对多的情况。
1.2 定义
图由顶点和边构成,表示为G(V,E)。
G:表示一个图
V:是图中顶点的有穷非空集合
E:是图G中边的集合
顶点:线性表中数据称为元素,树中数据 称为结点,图中数据称为顶点。
存在空表,空树,但图结构中,不允许没有顶点
边:顶点之间的逻辑关系,边集可以为空(两顶点之间没有关系)
1.3 分类
1.3.1 无向图
表示:G_1={ (1,2),((1,5),(1,6),(2,5),(2,3),(5,4),(5,6),(3,4),(4,6)};
无向完全图
任意两个顶点之间都存在边
含有n个顶点的无向完全图有条边 (组合问题)
1.3.2 有向图
表示方法:
= {<B,A>,<B,C>,<A,D>,<C,A> };
有向完全图
n个顶点的有向图,有n*(n-1)条边。
1.3.3 简单图
在图中,不存在顶点到其自身的边,且同一条边不重复出现,称这样的图为简单图
1.3.4 网
权:与图的边相关的数叫做权
带权的图称为网
2 图的存储结构
2.1 邻接矩阵
顶点不分大小,采用一维数组存储
边是顶点与顶点之间的关系,采用二维数组存储
2.1.1 顶点数组
2.1.2 边数组
2.1.3 助解
主对角线:全为0
值:
无边:arc[1][3]=0 因为B到D的边不存在
有边:arc[2][0]=1 因为C到A的边存在
对称矩阵:
由于是无向图,B->D的边不存在,D->B的边也不存在,所以
若:arc[i][j]=0 ,可得arc[j][i]=0
同理:A->B的边存在,B->A的边也存在,所以
若arc[i][j]=1 则arc[j][i]=1成立
2.1.4 结论
(1)顶点的度:这个顶点在邻接矩阵中第i行或者第i列的元素 之和
(2)找顶点的所有邻接点,将矩阵第i行元素扫描一遍,若值为1,则是邻接点
2.2 有向图
$$\begin{matrix}
\\
&&A_0&B_1&C_2&D_3\\
\end{matrix}
\\
\begin{matrix}
A_0\\
B_1\\
C_2\\
D_3\\
\end{matrix}
\begin{bmatrix}
0&0& 0&1\\
1&0&1&0\\
1&1&0&0\\
0&0&0&0
\end{bmatrix}$$
有向图的顶点的度
入度:(指向顶点的)矩阵中所在列之和
出度:(顶点出发)矩阵中所在行之和
查找邻接点:遍历第i行元素
2.3 网
表示计算机允许的,大于所有边上的权值(一个不可能的极限值)(两个数据无关)
0表示:不存在顶点到自身的边(i==j)
2.4 邻接矩阵实现
2.4.1 存储结构
typedef char ver_tex_type;//顶点类型
typedef int edge_type;//边上的权值类型
#define MAXVEX 100 //最大顶点数
#define INFTY 65535//采用65535代表无穷大
typedef struct
{
ver_tex_type vex[MAXVEX];//顶点表
edge_type arc[MAXVEX][MAXVEX];//邻接矩阵
int num_vex,num_edge;//图中当前顶点数,和边数
}mgraph;
2.4.2 邻接矩阵的创建无向网
(1)算法步骤
(1)输入总顶点数和总边数
(2)依次输入点的信息存入顶点表中
(3)初始化邻接矩阵,使每个权值初始化为极大值
(4)构造邻接矩阵。
依次输入每条边依附的顶点,和其权值。确定两个顶点在图中位置后,使相应边赋予相应的权值,同时使其对称边赋予相同的权值。
(2)算法实现
status cteate_mgraph(mgraph *g )
{
//输入总顶点数,总边数
printf("输入顶点数和边数:\n");
scanf("%d %d",&(g->num_vex),&(g->num_edge));
//顶点表:一维数组的获取
int i;
for(i=0;i<g->num_vex;i++)
scanf("%c",&(g->vex[i]));
//初始化邻接矩阵为无穷大
int j;
for(i=0;i<g->num_vex;i++)
for(j=0;j<g->num_edge;j++)
arc[i][j]=INFTY;
//输入每个边权
int k;
for(k=0;k<g->num_edge;k++)
{
printf("输入边(vi,vj)上的下标i,j和权w:")
scanf("%d %d %d",&i,&j,&w);
g->arc[i][j]=w;//无向图,对称矩阵
g->arc[j][i]=w;
}
}
(3)算法分析
时间复杂度O(n+n^2 +e)---->为O(n^2)
2.4.3 构造无向图
(1) 初始化邻接矩阵的值为0
(2)构造邻接矩阵时,修改权值w为1
2.4.4 小结
优点:
(1)便于判断两个顶点之间是否右边
(2)便于计算各个顶点的度
对于 有向图,第i行是其顶点的出度
第i列是其顶点的入度
缺点:
(1)不便于增加和删除顶点
(2)不便于统计边的数据,需要 遍历邻接矩阵所有元素 才能统计完毕,时间复杂度为O(n^2)
(3)空间复杂度高
3 邻接表
邻接表是图 的一种链式存储结构。
表示方法
处理办法:
(1)顶点表:一维数组存储
顶点元素:data :数据元素
firstarc:存储指向第一个邻接点的指针,便于查找该顶点的边信息
(2)边表:由于邻接点的个数不定,所以采用单链表存储
边表结点:adjvex :邻接点域,存储该邻接点在顶点表中的下标
info :数据域(边权)
nextarc:链域(与顶点邻接的下一条边的结点地址)
无向图称为顶点的边表
有向图称为顶点的出边表(顶点为弧尾),可求顶点出度
逆邻接表:以顶点为弧头,可求顶点入度
3.1 邻接表存储结构
#define MAXVEX 100
typedef char ver_tex_type;//顶点类型
typedef int edge_type;//边权
typedef struct edge_node
{
int adjvex;//邻接点域
edge_type weight;//权值,非网图可以不需要
strcut edge_type *next;//链域,指向下一个邻接点
}edge_node;
typedef struct ver_tex_node//顶点结点
{
ver_tex_node data;//顶点域
edge_node * firstarc;//边表头指针
}ver_tex_node,adj_list[MAXVEX];//邻接表元素,邻接表类型
typedef struct //边表的集合
{
adj_list arr_list;//元素是邻接表
int num_vex,num_edge;
}graph_adj_list;
3.2 邻接表的建立---无向图
3.2.1 算法步骤
(1)输入总顶点数和总边数
(2)有依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL
(3)创建邻接表。
依次输入每条边依附的两个顶点,确定顶点序号i和j后,将此边结点分别插入vi和vj对应的两个边链表头部
3.2.2 代码实现
status create_al_graph(graph_adj_list g)
{
int i,j,k;
edge_node *e;
//顶点数和边数
printf("输入顶点数和边数:");
scanf("%d%d",&(g->num_vex),&(g->num_edge));
//建立顶点表
for(i=0;i<g->num_vex;i++)
{
scanf("%c",&(g->arr_listp[i].data));//输入顶点数据域信息
g->arr_list[i].firstarc=NULL;//边表置空
}
//建立边表
for(k=0;k<g->num_vex;k++)
{
printf("输入边(vi,vj)的端/顶点:\n");
scanf("%d %d",&i,&j);
//将此边结点插入vi对应的边链表头部
//无向图:一条边两个端点,循环中,针对两个端点i,j,进行插入
e=(edge_node *)malloc(sizeof(edge_node));//申请边表结点空间
e->adjvex=j;//邻接序号为j;
//头插法。插入结点e
e->next=g->arr_list[i].firstarc;//结点指向当前顶点指向的结点
g->arr_list[i].firstarc=e;//
//将此边结点插入vj对应的边链表头部
e=(edge_node *)malloc(sizeof(edge_node));//申请边表结点空间
e->adjvex=i;//邻接序号为i;
e->next=g->arr_list[j].firstarc;//结点指向当前顶点指向的结点
g->arr_list[j].firstarc=e;//
}
}
3.3 小结
优点
(1)便于增加和删除顶点
(2)便于统计边的数目,按顶点表顺序扫描所有边表,可得到边的数目,时间复杂度 O(n);
(3)空间效率高
缺点
(1)不便于判断两点之间是否存在边
(2)不便于计算图的度,尤其是有向图的入度。
4 图的遍历
4.1 深度优先搜索(DFS)
depth first search
步骤:
(1)任选一顶点,如V1
(2)选择一个邻接点,一路扎到底
(3)返回到未被访问得其他结点,再一路扎到底
4.2 深度优先搜索遍历
4.2.1 邻接矩阵的遍历
算法步骤(略)
(1)从图中某个顶点v出发,访问v,并置visited[i]的值为true
(2)依次检查v的所有false,再从w出发进行递归遍历,直到图中所有顶点都被访问过
深度优先算法
#include<stdbool.h>//TRUE和FALSE
_bool visited[MAX];//访问标志的数组
//从第v个顶点出发,递归,深度优先 搜索
void dfs_mg(mgraph *mg,int v)
{
//置访问标志数组相应下标的元素为true
visited[v]=TRUE;
//打印结点
printf("%c\t",g->vex[i]);//打印顶点数据
int j;
for(j=0;j<g->num_vex;j++)//依次遍历行
if(g->arc[v][j]!=0 && !visit[j])
//g->arcs[v][j]!=0 表示j是v的邻接点,即v->j
//!visited[j];//j没有被访问过
dfs_mg(g,w); //扎到底,找到有关V的邻接点
}
//遍历图g
//另选其他未被访问的顶点作为起始点重复上述深度优先搜索过程
void dfs_travel(mgraph *g )
{
int i;
//初始化所有顶点状态为未访问
for(i=0;i<g->num_vex;i++)
visited[i]=FALSE;
for(i=0;i<g->num_vex;i++)
if(!visited[i])
dfs_mg(g,i);
}
4.2.2 邻接表的dfs
_bool visited[MAX];
void dfs_al(alg *gl,int v)
{
edge_node *p;//边表结点指针
visited[v]=TRUE;//已访问
printf("%c",gl->arr_list[i].data);//打印顶点信息
p=gl->arr_list[i].firstarc;//从第一个邻接点开始
while(p)
{
if(!visited[p->adjvex])//顶点位置域未访问
dfs_al(gl,p->adjvex);//继续访问
p=p->next;
}
}
//从其他未被访问的结点开始
void afs_al_travel(alg *gl)
{
int i;
for(i=0;i<gl->num_vex;i++)
visited[i]=FALSE;
for(i=0;i<gl->num_vex;i++)
if(!visited[i])//若是连通图,则只访问依次,例如丢手绢
dfs_al(gl,i);
}
4.3 广度优先遍历
breadth_first_search
类似于树的层次遍历(从上到下,从左到右)
(1)从某个顶点v出发,访问v
(2)依次访问v的邻接点
(3)依次访问v的邻接点的邻接点
(4)重复,直到所有结点都被访问
4.3.1 邻接矩阵
先进先出----》队列
算法步骤
(1)从图中找到某个顶点V出发,访问V,并置visited[v]值为TRUE
(2)只要队列不空,进行如下操作
·队列顶点u出队
·依次检查u的所有邻接点j,如果visited[j]的值为FALSE,则访问j;并置visited[j]的值为true,然后将j进队
typedef char elem_type;
typedef struct
{
elem_type data[MAXVEX];
int front;//队头
int rear;//队尾
}sq_que;
void bfs_travel(mgraph *mg ,int v)
{
int i,j;
sq_que q;
init_que(&q);//队列初始化
//访问标志的数组置0
for(i=0;i<mg->num_vex;i++)
visited[i]=FALSE;
for(i=0;i<mg->num_vex;i++)//对每个顶点做循环
{
if(!visited[i])//如果未访问
{
visited[i]=TRUE;//更新访问状态
printf("%c",mg->vex[i]);//打印顶点值
en_que(&q,i);//顶点位置域入队
while(q->front!=q->rear)//非空
{
de_que(&q,&i);//出队,赋值给i
for(j=0;j<mg->num_vex;j++)//检查i的邻接点
{
//判断是否为邻接点,且未访问过;
if(mg->arc[i][j]==1 && !visited[j])
{
visited[j]=TRUE;//更新访问状态
printf("%c",mg->vex[j]);//打印顶点
en_que(&q,i);//入队
}
}
}
}
}
}
//初始化队列
init_que(sq_que *q)
{
q->front=0;
q->rear=0;
}
//入队
status en_que(sq_que *q,elem_type e)
{
if((rear+1)%MAXVEX== front)
return ERROR;
q->data[q->rear]=e;
q->rear++;
return OK;
}
status de_que(sq_que *q,elem_type *e)
{
if(q->front == q->rear)//空队
return ERROR;
*e=q->data[q->front];
q->front++;
return OK;
}
邻接表的实现----待补