6.4图的存储
图的存储 | |
---|---|
顺序存储 | 邻接矩阵、边集数组 |
链式存储 | 邻接表、链式前向星、十字链表、邻接多重表 |
1.邻接矩阵
邻接矩阵(Adjacency Matrix)是表示顶点之间相邻关系的矩阵。设G(V, E)是具有n个顶点的图,则G的邻接矩阵是具有如下性质的n阶方阵
![img](https://i-blog.csdnimg.cn/blog_migrate/ddc2923466b48b254e09ccbae8415406.png)
若G是网,则邻接矩阵可以定义为
![img](https://i-blog.csdnimg.cn/blog_migrate/777ad5901aa4a6270edf14b1460e47e1.png)
其中,wi, j表示边上的权值;表示计算机允许的、大于所有边上权值的数。例如,图6.10所示为一个有向网和它的邻接矩阵。
1. 1邻接矩阵的表示方法
无向图、有向图和网的邻接矩阵
1)
M[i] [j] | 举例图示 | 特点 | |
---|---|---|---|
无向图的邻接矩阵 | 若从节点vi到节点vj有边,则邻接矩阵M[i] [j]=M[j] [i]=1,否则M[i] [j]=0。![]() | ![]() | (1)对称矩阵且唯一(2)第i行或第i列非零元素的个数=第i个节点的度。上图中的邻接矩阵,第3列非零元素的个数为2,说明第3个节点c的度为2。 |
有向图的邻接矩阵 | 若从节点vi到节点vj有边,则邻接矩阵M[i] [j]=1,否则M[i] [j]=0。(不必考虑M[j] [i])![]() | ![]() | (1)邻接矩阵不一定对称(2)第i行非零元素的个数=第i个节点的出度,第i列非零元素的个数=第i个节点的入度。上图中的邻接矩阵,第3行非零元素的个数为2,第3列非零元素的个数也为2,说明第3个节点c的出度和入度均为2。 |
网的邻接矩阵 | 网是带权图,需要存储边的权值,则邻接矩阵表示为![]() | wij表示边上的权值,∞表示无穷大。当i=j时,wii也可被设置为0。![]() | 在该网中,从节点a到节点b有边,且该边的权值为2,节点a、b在一维数组中的存储位置分别为0、1,因此M[0] [1]=2。从节点b到节点a没有边,因此M[1] [0]=∞。 |
注意:以尖括号<vi,vj>表示的是有序对,以圆括号(vi,vj)表示的是无序对,后同。
![image-20231113235234858](https://i-blog.csdnimg.cn/blog_migrate/5f59e9fa51e777438b8aff069af21a4b.png)
1.1图的邻接矩阵存储表示
//-----图的邻接矩阵存储表示-----
#include "../Define.h"
#define MaxInt 32767 //表示极大值,即∞(只是因为不存在这个很大的值,所以就给一个数值)
//#define MaxInt 6
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct
{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph
1.2邻接矩阵的数据结构定义
`typedef struct
{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph`
1.2采用邻接矩阵表示法创建无向网
已知一个图的点和边,使用邻接矩阵表示法来创建此图的方法比较简单,下面以一个无向网为例来说明创建图的算法。
算法步骤:
(1)输入节点数和边数;
(2)依次输入节点信息,将其存储到节点数组Vex[]中;
(3)初始化邻接矩阵,如果是图,则将其初始化为0;如果是网,则将其初始化为∞;
(4)依次输入每条边依附的两个节点,如果是网,则还需要输入该边的权值。
• 如果是无向图,则输入ab,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=Edge[j] [i]=1。
• 如果是有向图,则输入ab,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=1。
• 如果是无向网,则输入a b w,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=Edge[j] [i]=w。
• 如果是有向网,则输入a b w,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=w。
1.2.1无向图/网的完美图解和效果:
一个无向图如下图所示,其邻接矩阵的存储过程如下所述。(参考《算法训练营:海量图解+竞赛刷题(入门篇)》6.1)
![image-20231113234010044](https://i-blog.csdnimg.cn/blog_migrate/e5648c7b5eceb3e7c2cef58bf92486b9.png)
1.2.2邻接矩阵的代码实现
// Created by lijing on 23-11-13.
//-----图的邻接矩阵存储表示-----
#include "../Define.h"
#define MaxInt 32767 //表示极大值,即∞(只是因为不存在这个很大的值,所以就给一个数值)
//#define MaxInt 6
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct
{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph
/**2.采用邻接矩阵表示法创建无向网
【算法步骤】
① 输入总顶点数和总边数。
② 依次输入点的信息存入顶点表verxs[i]中。
③ 初始化邻接矩阵,使每个权值初始化为极大值MaxInt。
④ 构造邻接矩阵。依次输入每条边依附的顶点和其权值,
确定两个顶点在图中的位置之后,使相应边赋予相应的权值,同时使其对称边赋予相同的权值。
**/
//在图中查找顶点
int LocateVex(AMGraph G,VerTexType x){
for (int i = 0; i < G.vexnum; ++i) {//查找顶点信息的下标
if(x == G.vexs[i]) return i;
}
return -1;//没找到
}
//输出邻接矩阵
void print(AMGraph G){
cout<<"图的邻接矩阵为:\n";
for (int i = 0; i < G.vexnum; ++i) {
for (int j = 0; j < G.vexnum; ++j) {
cout<<G.arcs[i][j]<<"\t";
}
cout<<endl;
}
}
//将最大值
void printGraph(AMGraph G){
cout<<"邻接矩阵:"<<endl;
for(int i=0;i<G.vexnum;++i){
for(int j=0;j<G.vexnum;++j){
if(j!=G.vexnum-1){
if(G.arcs[i][j]!=MaxInt){
cout<<G.arcs[i][j]<<"\t";
}
else{cout<<"∞"<<"\t";}
}
else
{
if(G.arcs[i][j]!=MaxInt){
cout<<G.arcs[i][j]<<endl;
}
else{
cout<<"∞"<<endl;
}
}
}
}
cout<<endl;
}
Status CreateUDN(AMGraph &G)
{//采用邻接矩阵表示法,创建无向网G
int i,j,k;
cout<<"请输入节点数 边数:";
cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数
for(i=0;i<G.vexnum;i++) { //依次输入点的信息,存在一维数组里
cout<<"请输入第"<<i+1<<"个节点:";
cin >> G.vexs[i];
}
cout<<"顶点数组"<<endl;
for(i=0;i<G.vexnum;++i)
{
cout<<G.vexs[i]<<"\t";
}
cout<<endl;
for(i=0;i<G.vexnum;i++) //初始化邻接矩阵所有值为0,如果是网则初始化边的权值均置为极大值MaxInt,
for(int j=0;j<G.vexnum;j++)
G.arcs[i][j]=MaxInt;
// G.arcs[i][j]=0;
cout<<"初始化的邻接矩阵为MaxInt"<<endl;
print(G);
for(k=0;k<G.arcnum;++k) //构造邻接矩阵
{
VerTexType v1, v2;//傻逼在这里声明了int类型是不对的!!
ArcType w;//图则w=1,网则w=权值,可以定死也可以输入
cout<<k+1<< "请依次输入顶点1 顶点2 权值"<<endl;
cin >> v1 >> v2 >> w; //输入一条边依附的顶点v1,v2及权值w
i = LocateVex(G, v1);
j = LocateVex(G, v2); //确定v1和v2在G中的位置,即顶点数组的下标
if (i != -1 && j != -1) {
G.arcs[i][j] = w; //边<v1, v2>的权值置为w
G.arcs[j][i] = G.arcs[i][j]; //置<v1, v2>的对称边<v2, v1>的权值为w
}
else
{
cout << "输入顶点信息错!请重新输入!"<<endl;
k--;//本次输入不算
}
}
return OK;
}
int main(){
AMGraph G;
CreateUDN(G);
print(G);
printGraph(G);
return 0;
}
//-------#include "../Define.h"-------
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//strcpy()的库
#include <iostream>
using namespace std;
#include <assert.h>//断言函数,不符合要求的会输出警告
2.邻接表
2.1邻接表的存储表示
邻接表 | 图例 | 特点 |
---|---|---|
无向图邻接表 | 一个节点的所有邻接点构成一个单链表![]() | (1)如果无向图有n个节点、e条边,则节点表有n个节点,邻接点表有2e个节点。(2)节点的度为该节点后面单链表中的节点数。 |
有向图的邻接表(出弧) | 注意:对有向图中节点的邻接点,只看该节点的出边(出弧)。![]() | (1)如果有向图有n个节点、e条边,则节点表有n个节点,邻接点表有e个节点。(2)节点的出度为该节点后面单链表中的节点数。(3)找节点出度易,但是找入度很难。 |
有向图的逆邻接表(入弧) | 为了方便得到节点的入度,可以建立一个有向图的逆邻接表:![]() | (1)如果有向图有n个节点、e条边,则节点表有n个节点,邻接点表有e个节点。(2)节点的入度为该节点后面的单链表中的节点数。 |
有向图的邻接表(出弧)解释:
• 节点a的邻接点(只看出边,即出弧)是节点b、c、e,其邻接点的存储下标为1、2、4,按照头插法(逆序)将其放入节点a后面的单链表中;
• 节点b的邻接点是节点c,其邻接点的存储下标为2,将其放入节点b后面的单链表中;
• 节点c的邻接点是节点d、e,其邻接点的存储下标为3、4,按头插法将其放入节点c后面的单链表中;
• 节点d的邻接点是节点e,其邻接点的存储下标为4,将其放入节点d后面的单链表中;
• 节点e没有邻接点,其后面的单链表为空。
有向图的逆邻接表(入弧)解释:
• 节点a没有逆邻接点(只看入边,即入弧),其后面的单链表为空;
• 节点b的逆邻接点是节点a,其邻接点的存储下标为0,将其放入节点b后面的单链表中;
• 节点c的逆邻接点是a、b,其邻接点的存储下标为0、1,按照头插法将其放入节点c后面的单链表中;
• 节点d的逆邻接点是节点c,其邻接点的存储下标为2,将其放入节点d后面的单链表中;
• 节点e的逆邻接点是节点a、c、d,其邻接点的存储下标为0、2、3,按照头插法(逆序)将其放入节点e后面的单链表中。注意:对有向图中节点的逆邻接点,只看该节点的入边(入弧)。
2.2图的邻接表存储表示和结构定义
//- - - - -图的邻接表存储表示- - - - -
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;
typedef char OtherInfo;
//边结点:边指向的顶点的位置+结点(包含指向下一个边结点的指针)+其他信息(比如权值)
typedef struct ArcNode
{
int adjvex; //该边所指向的顶点的位置
struct ArcNode * nextarc; //指向下一条边/邻接点的指针
OtherInfo info; //和边相关的信息
}ArcNode;
//顶点:数据+结点(指向边结点的指针)
typedef struct VNode
{
VerTexType data;
ArcNode *firstarc; //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum]; //AdjList表示邻接表类型,[MVNum]表示的一个数组
//图的邻接表的结构定义:顶点数组,顶点数和边(边结点)数
//顶点数组里的每一个顶点后面跟着链表,在遍历的时候注意即可
typedef struct
{
AdjList vertices; //vertex的复数
int vexnum,arcnum; //图的当前顶点数和边数
}ALGraph;
2.3采用邻接表表示法创建有向图
2.3.1有向图的完美图解及运行效果
2.3.2代码实现
// Created by lijing on 23-11-14.
#include "../Define.h"
//- - - - -图的邻接表存储表示- - - - -
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;
typedef char OtherInfo;
//边结点:边指向的顶点的位置+结点(包含指向下一个边结点的指针)+其他信息(比如权值)
typedef struct ArcNode
{
int adjvex; //该边所指向的顶点的位置
struct ArcNode * nextarc; //指向下一条边/邻接点的指针
OtherInfo info; //和边相关的信息
}ArcNode;
//顶点:数据+结点(指向边结点的指针)
typedef struct VNode
{
VerTexType data;
ArcNode *firstarc; //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum]; //AdjList表示邻接表类型,[MVNum]表示的一个数组
//图的邻接表的结构定义:顶点数组,顶点数和边(边结点)数
//顶点数组里的每一个顶点后面跟着链表,在遍历的时候注意即可
typedef struct
{
AdjList vertices; //vertex的复数
int vexnum,arcnum; //图的当前顶点数和边数
}ALGraph;
//在邻接表中的顶点数组里查找顶点
int LocateVex(ALGraph G,VerTexType x){
for (int i = 0; i < G.vexnum; ++i) {//查找顶点信息的下标
if(x == G.vertices[i].data) return i;
}
return -1;//没找到
}
//插入一个边结点(头插法)a=i,b=j
void insertArcNode(ALGraph &G,int a,int b){
ArcNode *v=new ArcNode;
v->adjvex=b;
v->nextarc=G.vertices[a].firstarc;
G.vertices[a].firstarc=v;
}
//打印图的邻接表
void printGraph(ALGraph G){
cout<<"图的邻接表为:\n";
//本质是链表的遍历
for (int i = 0; i < G.vexnum; ++i) {
cout<<G.vertices[i].data<<"\t";
ArcNode *p=G.vertices[i].firstarc ;
while (p){
cout<<p->adjvex<<"\t";
p=p->nextarc;//修改指针
}
cout<<endl;
}
}
/*
void ListTraverse(LinkList L) {
cout<<"遍历链表:\n";
LNode *p=L->next; //p从第一个数据结点(首元结点)开始
// cout<<"输出数据元素的值:\n";
while (p){
cout<<p->data;
cout<<"\n";
p=p->next;//修改指针
}
}*/
Status CreateUDG(ALGraph &G)
{//采用邻接表表示法,创建无向图G
int i,j,k;
cout<<"请输入总节点数 边数:";
cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数
for(i=0;i<G.vexnum;++i) //输入各点,构造表头结点表
{
cout<<"请输入第"<<i+1<<"个节点:";
cin>> G.vertices[i].data; //输入顶点值:图G中第i个顶点的值
G.vertices[i].firstarc=NULL; //初始化表头结点的指针域为NULL
} //for
cout<<"顶点数组"<<endl;
for(i=0;i<G.vexnum;++i)
{
cout<<G.vertices[i].data<<"\t";
}
cout<<endl;
for(k=0;k<G.arcnum;++k) //输入各边,构造邻接表
{
VerTexType v1,v2;
cout<<k+1<< "请依次输入顶点1 顶点2"<<endl;
cin>>v1>>v2; //输入一条边依附的两个顶点
i=LocateVex(G,v1); j=LocateVex(G,v2);
//确定v1和v2在G中位置,即顶点在G.vertices中的序号
if (i!=-1&&j!=-1){
insertArcNode(G,i,j);
printGraph(G);
// insertArcNode(G,j,i);//无向图还需要插入一条边
}
/*//生成一个新的边结点*p1
ArcNode *p1=new ArcNode;
p1->adjvex=j; //邻接点序号为j
p1->nextarc=G.vertices[i].firstarc;
G.vertices[i].firstarc=p1;
//将新结点*p1插入顶点vi的边表头部
//无向图:对称所以再重复一次,有向图就无需下方
ArcNode *p2=new ArcNode; //生成另一个对称的新的边结点*p2
p2->adjvex=i; //邻接点序号为i
p2->nextarc=G.vertices[j].firstarc;
G.vertices[j].firstarc=p2;
//将新结点*p2插入顶点vj的边表头部*/
} //for
return OK;
}
int main(){
ALGraph G;
CreateUDG(G);
printGraph(G);
return 0;
}
*.邻接矩阵与邻接表表示法的关系和比较
优点 | 缺点 | 对于任一确定的无向图 | 空间复杂度 | 使用 | 使用DFS遍历的效率 | |
---|---|---|---|---|---|---|
邻接矩阵 | •快速判断在两节点之间是否有边。在图中,Edge[i] [j]=1,表示有边;Edge[i] [j]=0,表示无边。在网中,Edge[i] [j]=∞表示无边。时间复杂度为O(1)。•方便计算各节点的度。在无向图中,邻接矩阵第i行元素之和=节点i的度;在有向图中,第i行元素之和=节点i的出度,第i列元素之和=节点i的入度。时间复杂度为O(n)。 | • 不便于增删节点:需要改变邻接矩阵的大小;• 不便于访问所有邻接点。访问第i个节点的所有邻接点时,需要访问第i行的所有元素,时间复杂度为O(n)。访问所有节点的邻接点,时间复杂度为O(n2)。• 浪费空间—存稀疏图(点多边少,无效元素多),O(n2)。 | 唯一(行列号与顶点编号一致) | O(n2) | 稠密图 | 遍历每一个顶点都要重头扫描 |
邻接表 | • 便于增删节点。• 便于访问所有邻接点。访问所有节点的邻接点,其时间复杂度为O(n+e)。• 空间复杂度低。节点表占用n个空间,无向图的邻接点表占用n+2e个空间,有向图的邻接点表占用n+e个空间,总体空间复杂度为O(n+e)。而邻接矩阵的空间复杂度为O(n2)。• 无向图中方便判断任一顶点的“度”: | • 不便于判断在两个节点之间是否有边(需要遍历该节点后面的邻接点链表);• 有向图邻接表中只能计算出度;需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”。 | 不唯一(链接次序与顶点编号无关) | O(n+e) | 稀疏图 | 有2e个表节点,但只需要扫描e个结点即可完成遍历 |
n:顶点表节点;e:边表结点;稠密图适于在邻接矩阵上进行深度遍历;稀疏图适于在邻接表上进行深度遍历 | 虽然以邻接表访问单条边的效率不高,但是访问同一节点的所有关联边时,仅需访问该节点后面的单链表,时间复杂度为该节点的度O(d(vi));而以邻接矩阵访问同一节点的所有关联边时,时间复杂度为O(n)。总体上,邻接表比邻接矩阵效率更高。 |
更新:
4.十字链表
4.1十字链表的存储结构表示
//- - - - -有向图的十字链表存储表示- - - - -
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;
typedef char InfoType;
#define MAX_VERTEX_NUM 20
typedef struct ArcBox
{
int tailvex,headvex; //该弧的尾和头顶点的位置
struct ArcBox *hlink, *tlink; //分别为弧头相同和弧尾相同的弧的链域
InfoType *info; //该弧相关信息的指针
}ArcBox;
typedef struct VexNode
{
VerTexType data;
ArcBox *firstin,*firstout; //分别指向该顶点第一条入弧和出弧
}VexNode;
typedef struct
{
VexNode xlist[MAX_VERTEX_NUM]; //表头向量
int vexnum, arcnum; //有向图的当前顶点数和弧数
}OLGraph;
4.2十字链表的存储流程图解
1.顺序:a的出度 、入度
2.b的出度没有,入度有两个
3.c的出度、入度
5.邻接多重表
6.5图的遍历
图的遍历也是从 图中某一顶点出发,按照某种方法对图中所有顶点访问且仅访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
类似于树的 | 连通图 | 非连通图 | 邻接矩阵 | 邻接表 | 空间复杂度 | 时间复杂度 | |
---|---|---|---|---|---|---|---|
深度优先遍历(DFS) | 先序遍历 | 相同 | 相同 | ||||
广度优先遍历(BFS) | 层次遍历 |
1深度优先搜索
图6.17 遍历图的过程
(1)从顶点v1出发,访问v1。(2)在访问了顶点v1之后,选择第一个未被访问的邻接点v2,访问v2。以v2为新顶点,重复此步,访问v4,v8、v5。在访问了v5之后,由于v5的邻接点都已被访问,此步结束。(3)搜索从v5回到v8,由于同样的理由,搜索继续回到v4,v2直至v1,此时由于v1的另一个邻接点未被访问,则搜索又从v1到v3,再继续进行下去。由此,得到的顶点访问序列为:v1→v2→v4→v8→v5→v3→v6→v7
图6.17(b)中所示的所有顶点加上标有实箭头的边,构成一棵以v1为根的树,称为深度优先生成树,如图6.18(a)所示。
1.1深度优先搜索遍历连通图
若把2当做起点,访问了就把辅助数组的值修改为1,再从邻接点中找一个没有被访问过的 ,切换到对应的行,再选择矩阵中有1的,且访问数组里的值为0的点访问,再把访问数组里的数值修改为1;
1.2深度优先搜索非连通图的遍历
访问完一个连通分量,就访问下一个
1.3深度优先搜索无向图代码和效果
效果:
代码:
bool visited[MVNum]; //访问标志数组,其初值为“false”
//深度优先遍历by顶点位置
void DFS_AM2(AMGraph G,int v)
{//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
cout<<G.vexs[v];
visited[v]=true; //访问第v个顶点,并置访问标志数组相应分量值为true
for(int w=0;w<G.vexnum;w++) //依次检查邻接矩阵v所在的行
if((G.arcs[v][w]!=0)&&(!visited[w])) DFS_AM2(G,w); //G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS
}
// Created by lijing on 23-11-13.
//-----图的邻接矩阵存储表示-----
#include "../Define.h"
#define MaxInt 32767 //表示极大值,即∞(只是因为不存在这个很大的值,所以就给一个数值)
//#define MaxInt 6
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct
{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph
//在图中查找顶点
int LocateVex(AMGraph G,VerTexType x){
for (int i = 0; i < G.vexnum; ++i) {//查找顶点信息的下标
if(x == G.vexs[i]) return i;
}
return -1;//没找到
}
//输出邻接矩阵
void printGraph(AMGraph G){
cout<<"邻接矩阵:"<<endl;
for(int i=0;i<G.vexnum;++i){
for(int j=0;j<G.vexnum;++j){
if(j!=G.vexnum-1){
if(G.arcs[i][j]!=MaxInt){
cout<<G.arcs[i][j]<<"\t";
}
else{cout<<"∞"<<"\t";}
}
else
{
if(G.arcs[i][j]!=MaxInt){
cout<<G.arcs[i][j]<<endl;
}
else{
cout<<"∞"<<endl;
}
}
}
}
cout<<endl;
}
bool visited[MVNum]; //访问标志数组,其初值为“false”
//深度优先遍历by顶点位置
void DFS_AM2(AMGraph G,int v)
{//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
cout<<G.vexs[v];
visited[v]=true; //访问第v个顶点,并置访问标志数组相应分量值为true
for(int w=0;w<G.vexnum;w++) //依次检查邻接矩阵v所在的行
if((G.arcs[v][w]!=0)&&(!visited[w])) DFS_AM2(G,w); //G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS
}
Status CreateUDN(AMGraph &G)
{//采用邻接矩阵表示法,创建无向网G
int i,j,k;
cout<<"请输入节点数 边数:";
cin>>G.vexnum>>G.arcnum;//输入总顶点数,总边数
for(i=0;i<G.vexnum;i++) {//依次输入点的信息,存在一维数组里
cout<<"请输入第"<<i+1<<"个节点:";
cin >> G.vexs[i];
}
cout<<"顶点数组"<<endl;
for(i=0;i<G.vexnum;++i)
{
cout<<G.vexs[i]<<"\t";
}
cout<<endl;
for(i=0;i<G.vexnum;i++)
for(int j=0;j<G.vexnum;j++)
G.arcs[i][j]=MaxInt;
// G.arcs[i][j]=0; //无向图初始化矩阵w=0
cout<<"初始化的邻接矩阵为MaxInt"<<endl;
for(k=0;k<G.arcnum;++k) //构造邻接矩阵
{
VerTexType v1, v2;//傻逼在这里声明了int类型
ArcType w;//构建邻接矩阵图则w=1,网则w=权值
cout<<k+1<< "请依次输入顶点1 顶点2 权值"<<endl;
cin >> v1 >> v2 >> w;
i = LocateVex(G, v1);
j = LocateVex(G, v2); 标
if (i != -1 && j != -1) {
G.arcs[i][j] = w;
G.arcs[j][i] = G.arcs[i][j];
}
else
{
cout << "输入顶点信息错!请重新输入!"<<endl;
k--;//本次输入不算
}
}
return OK;
}
int main(){
AMGraph G;
CreateUDN(G);
printGraph(G);
//深度优先搜索
int a;
cout<<"请输入深度遍历的起点位置:";
cin>>a;
DFS_AM2(G,a);
return 0;
}
1.4采用邻接表表示图的深度优先搜索遍历
//未实践版
bool visited[MVNum]; //访问标志数组,其初值为“false”
void DFS_AL (ALGraph G,int v)
{//图G为邻接表类型,从第v个顶点出发深度优先搜索遍历图G
cout<<v;
visited[v]=true; //访问第v个顶点,并置访问标志数组相应分量值为true
p=G.vertices[v].firstarc; //p指向v的边链表的第一个边结点
while(p!=NULL) //边结点非空
{
w=p->adjvex; //表示w是v的邻接点
if(!visited[w]) DFS_AL(G,w); //如果w未访问,则递归调用DFS_AL
p=p->nextarc; //p指向下一个边结点
} //while
}
2广度优先搜索
广度优先搜索(Breadth First Search,BFS)遍历类似于树的按层次遍历的过程。
点亮第一个灯后,点亮连接着的三个灯;再去访问这些邻接点的邻接点。
2.1广度优先搜索遍历连通图具体过程
广度优先搜索遍历的过程如下:
(1)从图中某个顶点v出发,访问v。
(2)依次访问v的各个未曾访问过的邻接点。
(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤(3),直至图中所有已被访问的顶点的邻接点都被访问到。
对图a进行广度优先搜索遍历的过程具体过程如下。
(1)从顶点v1出发,访问v1。
(2)依次访问v1的各个未曾访问过的邻接点v2和v3。
(3)依次访问v2的邻接点v4和v5,以及v3的邻接点v6和v7,最后访问v4的邻接点v8。
由于这些顶点的邻接点均已被访问,并且图中所有顶点都被访问,由此完成了图的遍历。得到的顶点访问序列为:v1→v2→v3→v4→v5→v6→v7→v8。
图所示的所有顶点加上标有实箭头的边,构成一棵以v1为根的树,称为广度优先生成树.
2.2广度优先搜索遍历连通图算法
广度优先搜索遍历的特点是:尽可能先对横向进行搜索。设x和y是两个相继被访问过的顶点,若当前是以x为出发点进行搜索,则在访问x的所有未曾被访问过的邻接点之后,紧接着是以y为出发点进行横向搜索,并对搜索到的y的邻接点中尚未被访问的顶点进行访问。也就是说,先访问的顶点其邻接点亦先被访问。为此,算法实现时需引进队列保存已被访问过的顶点。
看书去吧,要补队列的实现!!!
从v1开始访问,下标为0入队,尾指针R后移,Visited[0]变为1,访问v1:顺着头结点访问第一条弧1、第二条弧2,弧1是v2顶点入队(下标为1入队),弧3是v3顶点入队(下标为2入队),尾指针后移。v1访问完了后数字0出队。
访问v2时:visitd[1]=1,看邻接点,弧0已访问,弧3即v4顶点未访问入队(visited[3]=1,数字3入队),弧4即v5顶点未访问入队(visited[4]=1,数字4入队),v2访问完了数字1出队。
![image-20231115210412381](https://i-blog.csdnimg.cn/blog_migrate/79e9f84e1357a282d568ee08e98629d4.png)
访问v3:看邻接点,弧0,弧5即v6未访问入队(visited[5]=1),弧6即v7未访问入队(visited[6]=1,下标6入队),v3访问完出队。
访问栈中的下一个顶点即v4,弧1已访问,弧7即v8未访问入队(visited[7]=1,下标7入队),v4访问完数字3出队,如下图。
访问栈中的下一个顶点即v5,弧1已访问,弧7已访问,v5访问完数字4出队,F上移。
访问栈中的下一个顶点即v6,弧2已访问,弧5已访问,v6访问完数字5出队。
访问栈中的下一个顶点即v7,
2.3非连通图的广度优先遍历次序
链表
6.6图的应用
现实生活中的许多问题都可以转化为图来解决:
以最小成本构建一个通信网络|计算地图中两地之间的最短路径|复杂活动中各子任务的完成寻找一个较优的顺序等
1最小生成树
概念回顾:
生成树:第2、3、4是图的子图,是第一张图的生成树。
注意:含有n个顶点,n-1条边的不一定是生成树。
无向图的生成树:
典型应用: