一.图的定义和术语
完全图任意两个点都有一个边相连
稀疏图 有很少边或弧的图(e<nlogn)
稠密图 有较多边或弧的图
网 边或弧带权的图
邻接 有边或弧相连,两个顶点的关系
关联 边或弧与顶点的关系
顶点的度 与该顶点相关联的边的数目
有向图中,顶点的度等于该顶点的出度和入度之和
路径 接续的边构成的顶点序列
路径长度 路径上边或弧的数目/权值之和
回路(环) 第一个顶点和最后一个顶点相同的路径
简单路径 除了路径起点终点可以相同,其余顶点均不相同的路径
简单回路(简单环) 除了路径起点和终点相同外,其余顶点均不相同的路径
连通图(强连通图) 在无向图(有向图)G中,若对任意两个顶点v,u都存在从v到u的的路径,那么就称G为连通图(强连通图)
权与网图中边或弧所具有的相关数称为权。表明从一个顶点到另一个顶点的距离或者耗费。
带权的图称为网
连通分量(强连通分量)
极小连通子图该子图是G的连通子图,在该子图中删除任何一个顶点子图不再连通。
生成树 包含无向图G所有顶点的极小连通子图
生成森林 对非连通图,由各个连通分量生成树的集合
二.图的抽象数据类型定义
三.图的存储结构
1. 邻接矩阵表示法
建立一个顶点表(记录各个顶点的信息)和一个邻接矩阵(表示各个顶点之间的关系)
- 有向图的邻接矩阵表示法
-
网(有权图)的邻接矩阵表示法
采用邻接矩阵表示法创建无向网1.输入总顶点数和总边数
2.依次输入点的信息存入顶点表中
3.初始化邻接矩阵,每个权值化为最大值
4.构造邻接矩阵
//邻接矩阵的存储表示
#define n 100 //最大顶点数
#define maxint 32767 //表示极大值即无穷大
typedef char VerTexType; //顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct
{
VerTexType vexs[n]; //顶点表
ArcType arcs[n][n]; //邻接矩阵
int vexnum; //图当前的点数
int arcnum; //图当前的边数
}AMGraph;
//邻接矩阵表示法创建无向网
void CreateUDN(AMGraph* G)
{
scanf("%d %d", &(G->vexnum), &(G->arcnum));
//建立顶点表
for (int i = 0; i < G->vexnum; i++)
{
scanf("%c ", G->vexs[i]);
}
//初始化邻接矩阵
for (int i = 0; i < G->vexnum; i++)
{
for (int j = 0; j < G->vexnum; j++)
{
G->arcs[i][j] = maxint; //边的权值均化为最大值
}
}
for (int k = 0; k < G->arcnum; k++)
{
scanf(" %c %c",G->vexs[k], G->vexs[k + 1]);
int w;
scanf("%d", &w);
int i, j;
i = LocateVex(G, G->vexs[k]);
j = LocateVex(G, G->vexs[k+1]);
G->arcs[i][j] = w;
G->arcs[i][j] = G->arcs[j][i];
}
}
//查找顶点
int LocateVex(AMGraph G, VerTexType u)
{
int i;
for (i = 0; i < G.vexnum; i++)
{
if (u == G.vexs[i])
{
return i;
}
}
return -1;
}
-
无向图
初始化邻接矩阵w均为0
构造邻接矩阵w为1 -
有向图
邻接矩阵为非对称矩阵
仅需为G.arcs[i][j]赋值,不需要给G.arcs[j][i]赋值
2.邻接矩阵
1.优点
- 直观,简单,好理解
- 方便检查任一顶点间是否存在边
- 方便找任一顶点所有的邻接点(有边直接相连的顶点)
- 方便计算任一顶点的度
无向图中:对应行(列)非0元素的个数
有向图中:对应行非0元素是出度,对应列非0元素是入度
2.缺点
- 不利于增加和删除结点
- 浪费空间 对于稀疏图来说,存有大量无效元素
- 浪费时间 统计稀疏图中,有多少条边时
3.邻接表表示法(链式)
1.无向图的邻接表
1.邻接表不唯一
2.若无向图有n个顶点,e条边,则邻接表需要n个头结点和2e个表结点。适合存稀疏图。
2.有向图的邻接表
在这个图中,每个结点只存了出度边
- 顶点Vi的出度为第i个单链表中的结点数
- 顶点Vi的入度为整个单链表中邻接点值域是 i-1 的结点个数
逆邻接表
- 顶点Vi的入度为第i个单链表中的结点数
- 顶点Vi的出度为整个单链表中邻接点值域是 i-1 的结点个数
3.邻接表的代码实现
#define MVNum 100
#define VerTexType char //假设顶点类型为char
//顶点的结点结构
typedef struct VNode
{
VerTexType data; //顶点信息
ArcNode *firstarc;//指向第一条依附该顶点的边的指针
}VNode;
VNode AdjList[MVNum];//邻接表的类型为VNode
//边结点的结点结构
typedef struct ArcNode
{
int adjvex; //该边所指向的顶点的位置
struct ArcNode*nextarc;//指向下一条边的指针
OtherInfo info; //和边相关的信息
}ArcNode;
//图的结构定义
typedef struct
{
VNode vertices[MVNum]; //顶点数组
int vexnum; //图当前的顶点数
int arcnum; //弧数
}ALGraph;
邻接表操作举例说明:
ALGragh G;
G.vexum=5;
G.arcnum=6;
G.vertices[1].data='b';
ArcNode*p=G.vertices[1].firtarc; //p指向顶点b的第一条边结点
p->adjvex=4; //p指针指向的边结点是到下标为4结点的边
4.采用邻接表表示法创建无向图
1.算法思想
2.代码实现
void CreateUDG(ALGraph* G)
{
scanf("%d %d", &(G->vexnum), &(G->arcnum));
//构建表头结点表
for (int i = 0; i < (G->vexnum); i++)
{
scanf(" %c", G->vertices[i].data);
G->vertices[i].firstarc = NULL;
}
//构造邻接表
for (int k = 0; k < (G->arcnum); k++)
{
VerTexType v1, v2;
scanf(" %c %c", v1, v2);
int i, j;
i = LocateVex(G, v1);
j = LocateVex(G, v2);
ArcNode* p1 = (ArcNode*)malloc(sizeof(ArcNode)); //生成一个新的边结点p1
p1->adjvex = j; //邻接点序号为j
//往下标为i的顶点加
p1->nextarc = G->vertices[i].firstarc;
G->vertices[i].firstarc = p1;
ArcNode* p2 = (ArcNode*)malloc(sizeof(ArcNode)); //因为无向图生成另一个对称的新的边结点p2
p2->adjvex = i;
p2->nextarc = G->vertices[j].firstarc;
G->vertices[j].firstarc = p2;
}
}
//查找结点,返回顶点表中结点的下标
int LocateVex(ALGraph* G, VerTexType v)
{
for (int i = 0; i <(G->vexnum); i++)
{
if (G->vertices[i].data == v)
return i;
}
return -1;
}
5.邻接表特点
1.方便找任一顶点的所有邻接点
2.节约稀疏图的空间
3.方便计算顶点的度
4.邻接矩阵和邻接表表示的关系
2.区别:
- 对于任意的无向图,邻接矩阵唯一,但邻接表不唯一
- 邻接矩阵的空间复杂度为(n^2),邻接表的空间复杂度为O(n+e)
- 故邻接矩阵多用于稠密图,邻接表多用于稀疏图
5.十字链表
6.邻接多重表
四.图的遍历
(从已知连通图中某一顶点出发,沿着一些边访问图中所有顶点,并且每个顶点只被访问一次,叫做图的遍历)所以必须防止重复访问:设置visited辅助数组
1.深度优先搜索(DFS)Depth
1.思想
一条道走到黑
2.算法
1.邻接矩阵的无向图深度遍历实现
int visited[100] = { 0 };
void DFS(AMGraph* G, int v)
{
visited[v] = 1;
for (int w = 0; w < (G->vexnum); w++)
{
if ((G->arcs[v][w] != 0) && (visited[w] == 0))
DFS(&G, w);
//w为v的邻接点,如果w未访问,则递归调用DFS
}
}
2.邻接表深度遍历实现
值得注意:从顶点表回去该链全部被访问完了之后,回到入口
3.分析
2.广度优先搜索(BFS)Breadth
跟树的层次遍历相似,利用到队列
3.DFS和BFS算法比较
- 空间复杂度相同,都是O(n) 借用了栈或者队列
- 时间复杂度只与存储结构(邻接矩阵O(n^2) 邻接表O(n+e) ) 与搜索路径DFS还是BFS无关
五.图的应用
1.最小生成树
1.生成树
所有顶点均由边连接在一起,但不存在回路的图
- 一个图可以有不同棵生成树
- 生成树的顶点个数与图的顶点个数相同
- 生成树是图的最小连通子图
- 生成树中任意两个顶点间路径是唯一的
- 生成树含有n个顶点n-1条边
2.最小生成树–MST性质
在利用DFS或者BFS给定一个无向网络中,使得各边权值之和最小的生成树称为最小生成树,也叫最小代价生成树
-
MST性质
3.最小生成树的应用
-
准备在n个城市间建立通信网,则n个城市应铺n-1条线路
数学模型
顶点–表示城市有n个,边–表示线路有(n-1)条,边的权值–表示线路的经济代价,连通网–表示n个城市间的通信网
4.构造最小生成树
1.Prim算法
2.Kruskal算法
3.两者比较
2.最短路径
1.典型案例
交通网络的应用–从甲地到乙地是否有公路连接?有多条通路情况下,哪一条最短?
交通网络用有向网络表示
顶点–表示地点
弧–表示两个地点有路连通
弧上面权值–表示两地之间的距离,交通费,途中花费的时间
最短路径问题:
如何让从甲地到乙地运输时间最短或者花费最少?
1.单源最短路径–Dijkstra算法
时间复杂度(n^2)
2.所有顶点间的最短路径–Floyd算法
加入顶点试探
3.针对有向无环图 AOV网,AOE网
1.拓扑排序(AOV网络)
AOV类似于上课顺序,表明某项活动有先决条件,所以不允许有回路
AOV网络的拓扑排序是不唯一的
检测AOV网络中是否存在环
对于有向图构造其顶点的拓扑有向序列,若网中所有顶点都在拓扑有序序列中,则AOV必不存在环
2.AOE网络–关键路径
1.例子
2.关键路径:
1.完成整项活动至少需要多少时间
2.哪些活动是影响工程进度的关键
关键路径-----路径最长的路径
路径长度-----路径上各活动持续时间之和