数据结构 图

news2024/11/23 0:30:28

目录

  • 第六章 图
  • 6.1 图的基本概念
      • 知识回顾
  • 6.2 图的储存结构(邻接矩阵法)
    • 1. 数组表示法
      • (1) 有向图,无向图的邻接矩阵
    • 2. 定义邻接矩阵的结构
    • 3. 定义图的结构
    • 4. 构造图G
    • 5. 特点
  • 6.3 储存结构(邻接表表示法)
    • 1. 储存方式
    • 2. 结构
    • 3. 图的邻接表存储表示(算法)
    • 4. 结论
    • 5. 邻接矩阵和邻接表的对比
  • 6.4.拓展存储结构(十字链表,邻接多重表)很绕多理解
    • 1. 十字链表(存储有向图)
    • 2. 邻接多重表(存储无向图)
  • 6.5 四种存储结构的对比
  • 6.6. 图的基本操作
    • 1. Adjacent(G,x,y)边的存在
    • 2. Neighbors(G,x):列出图G中与结点x邻接的边
    • 3. InsertVertex(G,x):在图G中插入顶点x
    • 4. DeleteVertex(G,x):从图G中删除顶点x
    • 5. AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边。
    • 6. FirstNeighbor(G,x):求图G中顶点的第一个邻接点
    • 7. NextNeighbor(G,x,y):
      • 8. get_edge_value,set
  • 6.7 图的遍历
    • 1. 图遍历的概述
    • 2.广度优先遍历(队列)
      • (1)算法描述
      • (2)算法实现
      • (3)分析
      • (4)广度优先生成树(森林)
      • (5)BFS 单源最短路径问题
    • 3. 深度优先遍历(栈)
      • (1)算法描述
      • (2)算法实现
      • (3)时间/空间复杂度
  • 6.8 图的应用
    • 一、最小生成树
      • 1、最小生成树的概念
      • 2、求最小生成树的两种方法
    • 二、最短路径
      • 1.无权图的单源最短路径问题——BFS算法
      • 2.单源最短路径问题——Dijkstra算法
      • 3.各顶点间的最短路径问题——Floyd算法
      • 4.最短路径算法比较
    • 三、有向⽆环图描述表达式
      • 1.有向⽆环图
    • 四、拓扑排序
      • 1. AOV网(Activity on Vertex Network,用顶点表示活动的网)
      • 2. 拓扑排序
      • 3. 拓扑排序的实现:
      • 4. 代码实现拓扑排序(邻接表实现):
    • 五、 逆拓扑排序
      • DFS 逆拓扑排序
      • 思考: 如何判断回路
    • 六、关键路径
      • 1. AOE 网
      • 2. 源点 汇点
      • 3. 求关键路径
      • 4. 特性
      • 5. 总结

第六章 图

6.1 图的基本概念

图是一种非线性结构
在这里插入图片描述

图的特点:

  • 顶点之间的关系是任意的
  • 图中任意两个顶点之间都可能相关
  • 顶点的前驱和后继个数无限制
  1. 定义:图是一种数据元素间存在多对多关系的数据结构加上一组基本操作构成的抽象数据类型
  • 定义:图G由顶点集V和边集E组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)集合。若V={V1,V2,……Vn},则用|V|表示图G中顶点的个数,也称图G的阶,E={(u,v)|u∈V,v∈V},用|E|表示图G中边的条数。
  • 注意:线性表可以是空表,树可以是空树,但图不可以为空,即V一定是非空集
  • 弧头和弧尾
    <x,y>表示从顶点到顶点y的一条弧,并称x为弧尾或起始点,称y为弧头或终端点
  1. 无向图,有向图:有向图<u,v>,其中u表示弧尾,v表示弧头
  2. 简单图:简单图:①不存在重复边,② 不存在顶点到自身的边
  3. 多重图:图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联,则G为多重图。
  4. 度,入度,出度:① 无向图:在具有n个顶点,e条边的无向图中,全部顶点的度的和等于边数的2倍(一条边连两个顶点,所以一条边两个度)。②有向图:度之和入度+出度(一条边一个出度一个入度),在具有n个顶点,e条边的有向图中,入度=出度=e
  5. 路径,简单路径,路径长度:① 在路径序列中,顶点不重复出现的路径称为简单路径。②路径长度:路径上边的数目(简单就意味着顶点不重复)。
  6. 回路,简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路。
  7. 点到点到距离:从顶点u出发到顶点v的最短路径若存在,则此路径的长度称为u到v的距离。若u到v根本不存在路径,则记该距离为无穷(∞)
  8. 连通图,强连通图:①若图G中任意两个顶点都是连通的,则称图G为连通图(无向图中的连通就是两个顶点之间路径存在;有向图中的强连通是两个顶点之间存在往返的路径,就是w到v和v到w都有路径,强连通是有向图独有的概念),否则称为非连通图。对于n个顶点的无向图G,若G是连通图,则至少有n-1条边(就是把每个顶点都连线一个顶点,就存在路径了),若G是非连通图,则最多可能有 C n − 1 2 C_{n-1}^{2} Cn12,(就是除去一个顶点,其他顶点两两相连,只要不连接最后一个顶点,这个图就是不连通的最极端情况)。②若图中任何一项顶点都是强连通的,则称为图为强连通图。对于n个顶点的有向图G,若G是强连通图,则最少为n条边(形成回路)。
  9. 连通的补充图解:
  • 连通的最少边数:
    在这里插入图片描述
  • 不连通的最多边数:
    在这里插入图片描述
  • 强连通的最少边数
    在这里插入图片描述
  1. 子图(不用包含所有顶点)、生成子图:若包含所有顶点的子图,就称为生成子图。并不是任意几个点,任意几条边都能构成子图(例如不构成图的情况)。
    在这里插入图片描述

  2. 连通分量:无向图中极大连通子图(子图必须连通,且包含尽可能多的顶点和边)称为连通分量。
    在这里插入图片描述

  3. 强连通分量:有向图中有极大强连通子图(子图必须强连通,同时保留尽可能多的边)称为有向图的强连通分量。
    在这里插入图片描述

  4. 生成树:连通图的生成树是包含图中全部顶点的一个极小连通子图边尽可能的小,但要保持连通若图中顶点数是n,则它的生成树含有n-1条边,对于生成树而言,若砍去一条边,则会变成非连通图,若加上一条边则会形成一个回路(环)

  5. 生成森林:在非连通图中,连通分量的生成树构成了非连通图的生成森林
    在这里插入图片描述

  6. 边的权,带权图(网)
    在这里插入图片描述

  7. 无向完全图:无向图中任意两个顶点之间都存在边。

  8. 有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧。
    在这里插入图片描述

  9. 稀疏图,稠密图:这二者是相对的
    在这里插入图片描述

  10. 树:不存在回路,且连通的无向图,n个顶点的树,必有n-1条边。

  11. 有向树:一个顶点的入度为0,其余顶点的入度均有1的有向图,称为有向树。
    n个顶点的图,若|E|>n-1,则一定有回路。
    在这里插入图片描述

知识回顾

在这里插入图片描述

6.2 图的储存结构(邻接矩阵法)

在这里插入图片描述

1. 数组表示法

(1) 有向图,无向图的邻接矩阵

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

① 对于无向图,顶点vi的度是邻接矩阵中第i行(或第i列)的元素之和, TD(vi)= ∑ a[i][j]。
② 对于有向图,顶点VI的出度OD(vi)是邻接矩阵中第i行的元素之和顶点vi的出度ID(vi)是邻接矩阵中第j列)的元素之和
邻接矩阵法求顶点的度。入度。出度的时间复杂度为O(|V|)

在这里插入图片描述

2. 定义邻接矩阵的结构

#define INFINITY INT_MAX
#define MAX_VERTEXT_NUM 20
typedef enum{DG,DN,AG,AN}GraphKind;
typedef struct ArcCell{
  VRType adj;//对无向图,用0,1表示是否相邻,对于带权图,为权值
  InfoType *info;//该弧相关信息的指针

}RrcCell,AdjMatrix[MAX_VERTEXT_NUM][MAX_VERTEXT_NUM];//邻接矩阵

3. 定义图的结构

(1)代码:

//定义图的结构
typedef struct {
   VertextType exs[MAX_VERTEXT_NUM]; //顶点
   AdjMatrix arcs[MAX_VERTEXT_NUM][MAX_VERTEXT_NUM]; //边的邻接矩阵
   int vexnum,arcnum; //个数
   GraphKind kind;//有向图?无向图?
}MGraph;

在这里插入图片描述

4. 构造图G

status CreateGraph(MGraph &G)
{
    scanf(&G.kind)
    {
        case DG:return CreateDG(G);
         case DN:return CreateDN(G);
           case UDG:return CreateUDG(G);
             case UDN:return CreateDGN(G);
        default:return ERROR;
    }
}
//用无向图为例

status CreateUDN(MGraph &G)
{
    scanf(&G.arcnum,&G.vexnum,&IncInfo);//输入点数和边数
    //给顶点进行数字化编号
    for(i=0;i<G.vexnum;i++)
    { scanf(&G.exs[i]);//定义顶点数组(如果顶点本身就是1~n的数字无需这一步)
    }
    //初始化邻接矩阵
    for(i=0;i<G.vexnum;i++)
    {
        for(j=0;j<G.vexnum;j++)
        {
            G.arcs[i][j]={ININITY,NULL};
        }
    }
    //通过边数进行遍历
    for(k=0;k<G.arcnum;K++)
    {
        scanf(&V1,&V2,&W);//输入邻接的连个顶点
        i=locatteVex(G,V1);j=locateVex(G,V2);//查找V1,V2的位置
        G.arcs[i][j].adj=w;//给邻接矩阵赋值
        if(IncInfo)
        {
            INPUT(*G.arcs[i][j].info);
        }
        G.arcs[j][i]=G.arcs[i][j];//由于是无向图,对称
    }
    return ok;
}


5. 特点

优点:

  • 无向图邻接矩阵是对称矩阵,同一条边表示了两次
  • 顶点v的度:等于二维数组对应行(或列)中1的个数
  • 判断两顶点v、u是否为邻接点:只需判二维数组对应分量是否为1
  • 在图中增加、删除边:只需对二维数组对应分量赋值1或清0
  • 占用存储空间只与它的顶点数有关,与边数无关;适用于边稠密的图
  • 对有向图的数组表示法可做类似的讨论’‘

缺点:

  • 不便于删除和增加顶点(增删边简单)
  • 不便于统计边的数目,需要扫描邻接矩阵所有元素才能统计完毕,时间复杂度为O( n 2 n^2 n2 )
  • 空间复杂度高,对于有向图,n个顶点需要 n 2 n^2 n2 个单元存储边,对于无向图,n(n-1)/2个单元,空间复杂度为O( n 2 n^2 n2 )

6.3 储存结构(邻接表表示法)

1. 储存方式

在这里插入图片描述

2. 结构

【1】顶点的结点结构
———————
| data | firstarc |
———————

data数据域:储存顶点vi
firstarc链域:指向链表中第一个结点
【2】弧的结点结构
——————————
| adjvex | info | nextarc |
——————————

adjvex邻接点域:与顶点vi邻接的点在图中的位置
info数据域:储存和边相关的信息,如权值
nextarc链域:与顶点vi的点在图中的位置

3. 图的邻接表存储表示(算法)

#define MAX_VERTEXT_NUM 20
//建立边结点 
typedef struct ArcNode {
      int adjvex;                      // 该弧所指向的顶点的位置
      struct ArcNode *nextarc; // 指向下一条弧
      InfoType  *info;                // 该弧相关信息(可选)
}ArcNode; 
// 顶点结点
typedef struct VNode{
        VertexType data;              // 顶点信息
        ArcNode  *firstarc;          // 指向第一条依附该顶点的弧
}VNode,AdjList[MAX_VERTEXT_NUM];
//邻接表
typedef struct {
      Adjlist vertices;
      int vexnum,arcnum;
      int kind;
}ALGraph; 

//建立邻接表算法
//初始化一个结点总数为num的图,k为图的类型,num为结点总数
void InitG(ALGraph G,enum GraphKind k,int num)
{
    G.kind=k;
    G.vexnum=num;
    G.vertices=new VNode[vexnum];
    for(int i=0;i<G.vexnum;i++)
    {G.vertices[i].Firstarc=NULL;
        cin>>G.vertics[i].data;
    }
}

//有向图(网)增加弧的算法,将弧(from,to,weight)加入图
void InsertArc(ALGragh G,int from,int to,int weight)
{
    ArcNode *s=new ArcNode;
    s->weight=weight;
    s->adjvex=to;
    s->nextarc=G.vertices[from].firstarc;//插到链表vertices[from]的头
    G.vertices[from].firstarc=s;
}


4. 结论

(1)在邻接表中,同一条边对应两个结点。
(2)无向图中顶点v的度:等于v 对应的链表的长度;但是,在有向图中,要求顶点A的的入度,则需要遍历所有的顶点连接的链表,判断有几个存在顶点A;求出度,则是A顶点链表有几个点。
(3)判定两顶点v,w是否邻接:要看v对应的链表中有无对应的结点w(相反判断也行);
(4)对于一个图,给定的邻接表是并不唯一的(区分与邻接矩阵)
(5)增减边:要在两个单链表插入、删除结点;
(6)占用存储空间与顶点数、边数均有关;适用于边稀疏的图

注意,在有向图的邻接表中不易找到指向该顶点的弧。
邻接矩阵表示唯一,邻接表表示不唯一

5. 邻接矩阵和邻接表的对比

在这里插入图片描述

6.4.拓展存储结构(十字链表,邻接多重表)很绕多理解

1. 十字链表(存储有向图)

  • 空间复杂度:O(|V|+|E|)
    在这里插入图片描述
  • 同层可找所有出边即出度(绿色)
  • 不同层相连的即所有入边(橙色)
  • 比邻接表空间复杂度的 v 2 v^2 v2更小
    在这里插入图片描述

2. 邻接多重表(存储无向图)

  • 解决无向图冗余信息的问题,空间大
  • 删除边,删除结点操作更简单(只要顺着指针找,就跟删除链表结点一样)
  • 空间复杂度:O(|V|+|E|),比邻接表的v+2e,因为不存储无关的结点
    在这里插入图片描述

6.5 四种存储结构的对比

在这里插入图片描述

6.6. 图的基本操作

无向图
在这里插入图片描述
有向图==(稀疏图也挺方便)==
在这里插入图片描述

1. Adjacent(G,x,y)边的存在

(无向边)<有向边>

  • 思路:
    ①无向图:邻接矩阵,判断aij是否为1,邻接表,i点的邻接表是否有j点;
    ②有向图类似

  • 时间复杂度
    在这里插入图片描述

2. Neighbors(G,x):列出图G中与结点x邻接的边

  • 思路:
    ①无向图:邻接矩阵,罗列出x点的行为1的所有点,邻接表,遍历x的链表的所有结点;
    ②有向图:邻接矩阵,出边遍历行,入边遍历列,邻接表,(出边)遍历x的链表,(入边)遍历所有的结点查看哪个结点值是x。
  • 时间复杂度
    在这里插入图片描述

3. InsertVertex(G,x):在图G中插入顶点x

  • 思路:
    ① 无向图:邻接矩阵,给矩阵增加一个x行增加一个x列;邻接表,给表的最后一行增加x项,不接任何一个结点,指向NULL。
    ② 有向图:类似
  • 时间复杂度
    在这里插入图片描述

4. DeleteVertex(G,x):从图G中删除顶点x

  • 思路:
    ① 无向图:邻接矩阵,将x的所有行列全部清空为-1,在顶点集中x的值表示为-1或false;邻接表,将所有与x有关的信息删除,需要遍历所有的结点
    ② 有向图:邻接矩阵,与无向图类似,邻接表,删出边需要将x的链表都删除,删入边,需要遍历所有的结点。
  • 时间复杂度
    在这里插入图片描述

5. AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边。

  • 思路:
    ① 无向图,邻接矩阵,将axy的值由0改为1=,邻接表,将x的链表后或前加上结点y==,最好使用头插法
    ② 有向图,类似。
  • 时间复杂度在这里插入图片描述

6. FirstNeighbor(G,x):求图G中顶点的第一个邻接点

  • 思路
    ① 无向图:邻接矩阵,扫描x的一行第一个为1的元素,可能第一个就是也可能到最后一个都没有;邻接表,查看x链表的第一个结点
    ② 有向图:邻接矩阵,找x有关的行列(出边入边),也就是出边入边的第一个邻接点,邻接表,出边寻找简单,但是入边需要遍历所有的边
  • 时间复杂度
    在这里插入图片描述

7. NextNeighbor(G,x,y):

假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1
在这里插入图片描述

8. get_edge_value,set

在这里插入图片描述

6.7 图的遍历

1. 图遍历的概述

1、定义——从某顶点出发,沿着一些边访问连通图中所有顶点,且使每个顶点仅访问一次的运算
2、为避免重复访问,可设置辅助数组Visited[ ],各分量初值为0,当顶点被访问,对应分量被置为1
3、方法

  • 深度优先(depth first search DFS)
  • 广度优先(breadth first search BFS)

2.广度优先遍历(队列)

(1)算法描述

  • 从图中某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。
  • 对于非连通图,可能此时尚有顶点未被访问,则另选图中一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止
  • 因此关键在于:①找到与一个顶点相邻的所有结点;② 标记哪些顶点被访问过;③ 需要一个辅助队列存储。

在这里插入图片描述
在这里插入图片描述
使用邻接矩阵(唯一)所以广度优先遍历序列唯一
使用邻接表(不唯一)所以广度优先遍历序列不唯一

(2)算法实现

采用邻接表存储实现无向图的广度优先遍历

//visited是访问标记数组

//处理非连通图的情况 
bool BFSTraverse(Graph G){
	for(int i=0;i<G.vexnum;++i)
		visited[i] = false;
	InitQueue(Q);
	for(int i=0;i<G.vexnum;++i){
		if(!visited[i])
			BFS(G,i);
	}
}
 
void BFS(Graph G,int v){
	visit(v);				//访问v顶点 
	visited[v] = True;		//修改该顶点对应数组的值为true 
	EnQueue(Q,v);			//入队 
	while(!isEmpty(Q)){		//不空还有未遍历到的节点 
		DeQueue(Q,v);		//出队v 
		for(w = FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))		//找到所有符合条件的邻接节点 
			if(!visited[w]){		//w是否被访问 
				visit[w];			//访问 
				visited[w] = true;	//修改该顶点对应数组的值为true
				EnQueue(Q,w);		//入队 
			}
	}
}

bool BFSTraverse(Graph G,int v){
	for(int i=0;i<G.vexnum;++i)
		visited[i] = false;
	InitQueue(Q);
	for(int i=0;i<G.vexnum;++i){
		if(!visited[i])
				visit(v);				//访问v顶点 
				visited[v] = True;		//修改该顶点对应数组的值为true 
				EnQueue(Q,v);			//入队 
				while(!isEmpty(Q)){		//不空还有未遍历到的节点 
					DeQueue(Q,v);		//出队v 
					for(w = FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))		//找到所有符合条件的邻接节点 
						if(!visited[w]){		//w是否被访问 
							visit[w];			//访问 
							visited[w] = true;	//修改该顶点对应数组的值为true
							EnQueue(Q,w);		//入队 
						}
				}
		}
}

(3)分析

① 如果使用邻接表,则从同一个顶点广度优先遍历序列会随着链接表不同而不同,但是由于邻接矩阵是唯一的,所以从同一个广度优先遍历得到的顺序是唯一的

  • 对于无向图,调用BFS函数的次数=连通分量数
  • 对于有向图而言,若起始顶点到其他顶点都有路径,则只需调用一次BFS/DFS函数。
  • 对于强连通图,从任一结点出发只需调用1次BFS/DFS函数。

③ 空间复杂度:O(|V|) 辅助队列的最坏的情况(所有结点都入队)
④ 时间复杂度:

  • a.使用邻接矩阵存储的图:访问|V|个顶点的需要O(|V|)的时间,查找每个顶点的邻接点都需要O(|V|)的时间,而总共有|V|个顶点,时间复杂度=O(|V|2)+o(|V|)=O(|V|2)。
  • b.使用邻接表的图:访问|V|个顶点的需要O(|V|)的时间,查找各个顶点的邻接点都需要O(|E|)的时间,时间复杂度=O(|V|)+o(|E|)。

(4)广度优先生成树(森林)

在这里插入图片描述

① 通过广度优先遍历可以的得到一棵遍历树
② 由于邻接表不唯一,则树不唯一;由于邻接矩阵唯一,则树唯一。

在这里插入图片描述

遍历非连通图,可以得到广度优先生成森林。

(5)BFS 单源最短路径问题

在这里插入图片描述

3. 深度优先遍历(栈)

(1)算法描述

从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。
在这里插入图片描述

(2)算法实现

邻接矩阵

int visited[MAX];//设置一个数组,判断是否遍历过,false/1为遍历过
void DFGTraverse(Graph G,int v)
{
    for(v=0;v<G.vexnum;v++)
       visited[v]=0;//初始化判断数组
    for(v=0;v<G.vexnum;v++)
    {
        if(!visited[v])//如果没有遍历过
            DFS(G,V);//进行遍历
    }
}
void DFS(Graph G,int v)//进行递归遍历
{
    visited[v]=1;printf(v);//改变判断数组,输出点
    for(w=FirstVex(G,v);w!=0;w=NextVex(G,v))//从每一行第一个邻接矩阵值为1的,跳转到下一个值为1的
    {
        if(!visited[w])
            DFS(G,v);
    }
}
int FirstVex(Graph G,int v)//判断第一个不是0的
{
    int i;
    for(i=0;i<G.vexnum;i++)
    {
        if(G.arcs[v][i]==1&&visited[i]==False)
            return i;
    }
    return -1;
}
void NextVex(Graph G,int v)//判断下一个不是0的
{
    int i;
    for(i=w;i<G.vexnum;i++)
    {
        if(G.arcs[v][i]==1&&visited[i]!=False)
            return i;
    }
    return -1;
}

2.邻接表

void DFS(Graph G,int v)
{
    cout<<G.vertices[v].data<<"  ";
    visited[v]=true;
    ArcNode *p=G.vertices[v].firstarc;
    while(p!=NULL)
    {
        int w=p->adjvex;
        if(!visited[w])
            DFS(G,w);
        p=p->nextarc;a
    }
}

(3)时间/空间复杂度

在这里插入图片描述

  • 空间复杂度:邻接矩阵,最坏情况O(|V|),最好情况O(1),栈深为1,所有的结点都连着遍历初始的那个结点
  • 时间复杂度:
    T(V)=o(|V|2) 邻接矩阵
    T(V)=O(|E|+n) 邻接表

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

6.8 图的应用

一、最小生成树

1、最小生成树的概念

对于一个带权连通无向图G = (V,E),生成树,每棵树的权(即树中所有边上的权值之和)也可能不同。设R为G的所有生成树的集合,若T为R中边的权值之和最小的生成树,则T称为G的最小生成树(Minimum-Spannino-Tree,MST).。

  • 最小生成树可能有多个,但边的权值之和总是唯一且最小的。
  • 最小生成树的边数 =顶点数 -1。砍掉一条则不连通,增加一条边则会出现回路。
  • 如果一个连通图本身就是一棵树,则其最小生成树就是它本身。
  • 只有连通图才有生成树,非连通图只有生成森林。

2、求最小生成树的两种方法

  • Prim算法
  • Kruskal算法
  1. Prim算法(普里姆):
    从某一个顶点开始构建生成树;每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止

时间复杂度: O(V2)
适合用于边稠密图

在这里插入图片描述

  1. Kruskal算法(克鲁斯卡尔):并查集
    每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选)直到所有结点都连通

时间复杂度: O(l E l * log2l E l )
适合用于边稀疏图

在这里插入图片描述
在这里插入图片描述

二、最短路径

在这里插入图片描述

1.无权图的单源最短路径问题——BFS算法

使用 BFS算法求无权图的最短路径问题,需要使用三个数组
在这里插入图片描述
这是寻找结点2的最短路径的数组情况

  • d[]数组用于记录顶点 u 到其他顶点的最短路径。
  • path[]数组用于记录最短路径从那个顶点过来。
  • visited[]数组用于记录是否被访问过。
#define MAX_LENGTH 2147483647			//地图中最大距离,表示正无穷
 
// 求顶点u到其他顶点的最短路径
void BFS_MIN_Disrance(Graph G,int u){
    for(i=0; i<G.vexnum; i++){
        visited[i]=FALSE;				//初始化访问标记数组
        d[i]=MAX_LENGTH;				//初始化路径长度
        path[i]=-1;						//初始化最短路径记录
    }
    InitQueue(Q);						//初始化辅助队列
    d[u]=0;
    visites[u]=TREE;
    EnQueue(Q,u);
    while(!isEmpty[Q]){					//BFS算法主过程
        DeQueue(Q,u);					//队头元素出队并赋给u
        for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w)){
            if(!visited[w]){
                d[w]=d[u]+1;
                path[w]=u;
                visited[w]=TREE;
                EnQueue(Q,w);			//顶点w入队
            }
        }
    }
}

2.单源最短路径问题——Dijkstra算法

BFS算法的局限性:BFS算法求单源最短路径只适⽤于⽆权图,或所有边的权值都相同的图

Dijkstra算法能够很好的处理带权图的单源最短路径问题,但不适⽤于有负权值的带权图

使用 Dijkstra算法求最短路径问题,需要使用三个数组:

  • final[]数组用于标记各顶点是否已找到最短路径。
  • dist[]数组用于记录各顶点到源顶点的最短路径长度。
  • path[]数组用于记录各顶点现在最短路径上的前驱。
#define MAX_LENGTH = 2147483647;
 
// 求顶点u到其他顶点的最短路径
void BFS_MIN_Disrance(Graph G,int u){
    for(int i=0; i<G.vexnum; i++){		//初始化数组
        final[i]=FALSE;
        dist[i]=G.edge[u][i];
        if(G.edge[u][i]==MAX_LENGTH || G.edge[u][i] == 0)
            path[i]=-1;
        else
            path[i]=u;
        final[u]=TREE;
    }
 
  	for(int i=0; i<G.vexnum; i++){
        int MIN=MAX_LENGTH;
        int v;
		// 循环遍历所有结点,找到还没确定最短路径,且dist最⼩的顶点v
        for(int j=0; j<G.vexnum; j++){
	        if(final[j]!=TREE && dist[j]<MIN){
 	            MIN = dist[j];
                v = j;
            }
        }
        final[v]=TREE;
        // 检查所有邻接⾃v的顶点路径长度是否最短
        for(int j=0; j<G.vexnum; j++){
	        if(final[j]!=TREE && dist[j]>dist[v]+G.edge[v][j]){
            	dist[j] = dist[v]+G.edge[v][j];
                path[j] = v;
            }
        }
	}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.各顶点间的最短路径问题——Floyd算法

Floyd算法:求出每⼀对顶点之间的最短路径,使⽤动态规划思想,将问题的求解分为多个阶段。
在这里插入图片描述

Floyd算法可以⽤于负权值带权图,但是不能解决带有“负权回路”的图(有负权值的边组成回路),这种图有可能没有最短路径
在这里插入图片描述

Floyd算法使用到两个矩阵:

  • dist[][]:目前各顶点间的最短路径。
  • path[][]:两个顶点之间的中转点。
int dist[MaxVertexNum][MaxVertexNum];
int path[MaxVertexNum][MaxVertexNum];
 
void Floyd(MGraph G){
	int i,j,k;
    // 初始化部分
	for(i=0;i<G.vexnum;i++){
		for(j=0;j<G.vexnum;j++){
			dist[i][j]=G.Edge[i][j];		
			path[i][j]=-1;
		}
	}
    // 算法核心部分
	for(k=0;k<G.vexnum;k++){
		for(i=0;i<G.vexnum;i++){
			for(j=0;j<G.vexnum;j++){
	   	    	if(dist[i][j]>dist[i][k]+dist[k][j]){
	   		    	dist[i][j]=dist[i][k]+dist[k][j];
	   		    	path[i][j]=k;
                }
			}
        }
    }
}

4.最短路径算法比较

在这里插入图片描述
在这里插入图片描述

三、有向⽆环图描述表达式

1.有向⽆环图

若⼀个有向图中不存在环,则称为有向⽆环图,简称 DAG图(Directed Acyclic Graph)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、拓扑排序

1. AOV网(Activity on Vertex Network,用顶点表示活动的网)

用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行。

在这里插入图片描述

2. 拓扑排序

在图论中,由⼀个有向⽆环图的顶点组成的序列,当且仅当满⾜下列条件时,称为该图的⼀个拓扑排序:

  • 每个顶点出现且只出现⼀次;
  • 若顶点 A 在序列中排在顶点 B 的前⾯,则在图中不存在从顶点 B 到顶点 A 的路径。存在则有环
  • 或定义为:拓扑排序是对有向⽆环图的顶点的⼀种排序,它使得若存在⼀条从顶点 A 到顶点 B 的路径,则在排序中顶点 B 出现在顶点 A 的后⾯。每个 AOV ⽹都有⼀个或多个拓扑排序序列。

3. 拓扑排序的实现:

  • 从AoV网中选择一个没有前驱 (入度为0) 的顶点并输出
  • 从网中删除该顶点和所有以它为起点的有向边
  • 重复D和2直到当前的AOV网为空或当前网中不存在无前驱的顶点为止

在这里插入图片描述
保存度为零的顶点可用队列,栈,数组
在这里插入图片描述

4. 代码实现拓扑排序(邻接表实现):

#define MaxVertexNum 100			//图中顶点数目最大值
 
typedef struct ArcNode{				//边表结点
    int adjvex;						//该弧所指向的顶点位置
    struct ArcNode *nextarc;		//指向下一条弧的指针
}ArcNode;
 
typedef struct VNode{				//顶点表结点
    VertexType data;				//顶点信息
    ArcNode *firstarc;				//指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
 
typedef struct{
    AdjList vertices;				//邻接表
    int vexnum,arcnum;				//图的顶点数和弧数
}Graph;								//Graph是以邻接表存储的图类型
 
// 对图G进行拓扑排序
bool TopologicalSort(Graph G){
    InitStack(S);					//初始化栈,存储入度为0的顶点
    for(int i=0;i<g.vexnum;i++){
        if(indegree[i]==0)
            Push(S,i);				//将所有入度为0的顶点进栈
    }
    int count=0;					//计数,记录当前已经输出的顶点数
    while(!IsEmpty(S)){				//栈不空,则存入
        Pop(S,i);					//栈顶元素出栈
        print[count++]=i;			//输出顶点i
        for(p=G.vertices[i].firstarc;p;p=p=->nextarc){
            //将所有i指向的顶点的入度减1,并将入度为0的顶点压入栈
            v=p->adjvex;
            if(!(--indegree[v]))
                Push(S,v);			//入度为0,则入栈
        }
    }
    if(count<G.vexnum)
        return false;				//排序失败
    else
        return true;				//排序成功
}

五、 逆拓扑排序

在这里插入图片描述
实现方法一:将上述代码改为出度为零
在这里插入图片描述

DFS 逆拓扑排序

在这里插入图片描述
在这里插入图片描述

思考: 如何判断回路

在这里插入图片描述

六、关键路径

1. AOE 网

在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如 完成活动所需的时间),称之为⽤边表示活动的⽹络,简称 AOE ⽹ (Activity On Edge NetWork)。

在这里插入图片描述

2. 源点 汇点

在 AOE ⽹中仅有⼀个⼊度为 0 的顶点,称为开始顶点(源点),它表示整个⼯程的开始;== 也仅有⼀个出度为 0 的顶点,称为结束顶点(汇点)==,它表示整个⼯程的结束。

  • 从源点到汇点的有向路径可能有多条,所有路径中,具有最⼤路径⻓度的路径称为关键路径,⽽把关键路径上的活动称为关键活动
  • 完成整个⼯程的最短时间就是关键路径的⻓度,若关键活动不能按时完成,则整个 ⼯程的完成时间就会延⻓。

在这里插入图片描述

3. 求关键路径

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. 特性

在这里插入图片描述
在这里插入图片描述

5. 总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1006723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

揭秘:Wasserstein GAN与梯度惩罚(WGAN-GP)

一、说明 什么是梯度惩罚&#xff1f;为什么它比渐变裁剪更好&#xff1f;如何实施梯度惩罚&#xff1f;在提起GAN对抗网络中&#xff0c;就不能避免Wasserstein距离的概念&#xff0c;本篇为系列读物&#xff0c;目的是揭示围绕Wasserstein-GAN建模的一些重要概念进行探讨。 图…

建筑模板桉木好还是松木好?

在建筑行业中&#xff0c;选择合适的建筑模板材料对于确保施工质量和效率至关重要。桉木和松木是常见的木质建筑模板材料&#xff0c;但在两者之间做出选择时&#xff0c;桉木往往是更好的选择。本文将探讨桉木建筑模板的优势&#xff0c;并为您提供选择建筑模板的指导。 桉木作…

从人工测绘到无人机航测:探索测绘技术的巨大变革

在测绘领域&#xff0c;随着科技的不断进步&#xff0c;测绘技术也在不断演进。人工测绘、传统航测以及如今的无人机航测&#xff0c;每一次进步都为我们提供了更加精确和高效的地图数据。本文将带你深入探索这些技术的演变及其应用。 一、人工测绘的时代 早期的地图制作依赖于…

公司的固定资产怎么管理的

固定资产的管理是公司运作的重要组成部分。它不仅影响到公司的运营效率&#xff0c;也直接影响到公司的财务状况。然而&#xff0c;如何有效地管理公司的固定资产&#xff0c;仍然是一个需要深入研究和解决的问题。  有效的固定资产管理需要建立一个全面、系统的管理体系。这…

【数据分享】2006-2021年我国省份级别的排水和污水处理相关指标(20多项指标)

《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况&#xff0c;在之前的文章中&#xff0c;我们分享过基于2006-2021年《中国城市建设统计年鉴》整理的2006—2021年我国省份级别的市政设施水平相关指标、2006-2021年我国省份级别的各类建设用地面积数…

台积电的战略布局:“曲线”抢单 | 百能云芯

郭明錤最新的分析引发了广泛关注&#xff0c;他指出台积电采取了一系列重大战略投资举措&#xff0c;旨在争夺未来的半导体订单&#xff0c;尤其是来自苹果和英伟达的12纳米订单。这些战略举措包括认购英特尔手中的IMS Nanofabrication Global股权以及投资安谋&#xff08;Arm&…

这4款优秀的国产开源低代码开发平台,值得开发者尝试!

这4款优秀的国产开源低代码开发平台有哪些&#xff1f; 以下是一些常用的开源团队协作平台和项目管理工具&#xff1a; 我们现在用的就是简道云这样的低代码平台&#xff0c;有免费版可以使用&#xff0c;灵活简单&#xff0c;比开源系统的安全性也更高 1、简道云 官网&…

Window10安装PHP7.4

1. 下载PHP 7 首先需要下载PHP 7的安装包&#xff0c;可以从PHP官网&#xff08;https://www.php.net/downloads.php&#xff09;或者Windows下的PHP官网&#xff08;http://windows.php.net/download/&#xff09;下载Windows版本的PHP 7安装包。根据自己的系统架构&#xff…

多商家多店铺多用户商城_入驻审核系统源码_OctShop

多商家多店铺入驻是指一个可以支持多商家入驻&#xff0c;入驻的商家可以开多个店铺的多用户多店商城电商平台系统&#xff0c;这样的商城系统主要是以商家入驻为核心&#xff0c;以店铺为基础&#xff0c;集合互联网移动模式&#xff0c;OctShop商城把B2B2C模式和O2O模式集合在…

Gartner 2023API管理市场指南重磅发布,得帆云iPaaS标杆入榜

中国API管理-市场指南 Market Guide for API Management, China 是由全球最具权威的IT咨询与研究机构Gartner发布、聚焦中国API管理市场领域的专业研究报告&#xff0c;通过对市场概况以及代表厂商的分析&#xff0c;为企业决策者提供重要依据与参考。 得帆云iPaaS融合集成平台…

问道管理:头肩底形态选股公式?

股票投资中有许多技能剖析办法&#xff0c;其中头肩底形状是一种很重要的形状。头肩底形状是预示着股票价格将会回转的技能形状之一&#xff0c;它涵盖了价格走势、成交量的改变以及相应的趋势线。本文将从成交量的视点、价格走势的视点和头肩底形状的特色三个方面&#xff0c;…

SpringBoot 集成 SpringSecurity 从入门到深入理解

完整的目录 介绍 SpringSecurity简述 SpringSecuritySpringSecurity 的主要功能说明 项目源码入门案例项目工程路径第一步&#xff1a;加载依赖第二步&#xff1a;创建核心的配置类第三步&#xff1a;增加controller第三步&#xff1a;启动程序小结界面跳转说明密码生成说明 重…

【2023集创赛】信诺达杯:基于Sinodyne ST3020的音频功放芯片LM386N-1的测试

本文为2023年第七届全国大学生集成电路创新创业大赛&#xff08;“集创赛”&#xff09;信诺达杯全国三等奖作品分享&#xff0c;参加极术社区的【有奖征集】分享你的2023集创赛作品&#xff0c;秀出作品风采&#xff0c;分享2023集创赛作品扩大影响力&#xff0c;更有丰富电子…

唯品会的两个常用API分享(商品详情和关键字搜索)

万邦vip API 接入说明 API地址:https://api-test.cn/vip/ 调用示例&#xff1a; -- 请求示例 url 默认请求参数已经URL编码处理 curl -i "https://api-服务器.cn/vip/item_get/?key<您自己的apiKey>&secret<您自己的apiSecret>&num_iid1710613157-…

YSA Toon (Anime/Toon Shader)

这是一个Toon着色器/Cel阴影着色器,用于Unity URP 此着色器的目的是使角色或物体阴影实时看起来尽可能接近真实的动画或卡通效果 可以用于游戏,渲染,插图等 着色器特性,如:面的法线平滑、轮廓修复、先进的边缘照明、镜面照明、完全平滑控制 这个文档包括所有的功能https:/…

基于SSM的健康综合咨询问诊平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【算法专题突破】滑动窗口- 将 x 减到 0 的最小操作数(12)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;1658. 将 x 减到 0 的最小操作数 - 力扣&#xff08;Leetcode&#xff09; 这道题并不难理解&#xff0c;其实就是在数组里找值&#xff0c;直到把x减成0&#xff0c; 这…

电压放大器和电荷放大器的区别是什么

电压放大器和电荷放大器一样吗&#xff1f; 电压放大器和电荷放大器不是一样的。你要明白&#xff0c;电压放大器是将输入信号的电压进行放大的装置&#xff0c;通常直接连接到信号源&#xff0c;不需要电容来耦合信号。而电荷放大器是将输入信号的电荷量进行放大的装置&#x…

更安全、更清晰、更高效——《C++ Core Guidelines解析》

由资深技术专家Rainer Grimm撰著的《C Core Guidelines解析》&#xff0c;从内容上说&#xff0c;选取了现代C语言最核心的相关规则;从篇幅上说&#xff0c;对软件工程师非常友好。以“八二原则”看&#xff0c;这个精编解析版是一-个非常聪明的选择。同时&#xff0c;Rainer G…

软件报错提示vcomp140.dll丢失怎么办?这5个修复方法可帮到你

随着科技的飞速发展&#xff0c;电脑已经成为人们日常生活和工作中不可或缺的重要工具。然而&#xff0c;在使用电脑的过程中&#xff0c;难免会遇到一些问题&#xff0c;如电脑报错 vcomp140.dll 丢失。这给许多用户带来了困扰&#xff0c;那么&#xff0c;究竟该如何解决这个…