数据结构-图

数据结构-图 2021/8/12 22:51

图的储存结构

1.邻接矩阵法

#include <iostream>
using namespace std;
typedef char VertexType;
typedef int EdgeType;
#define MaxVertexNum 100//顶点数目的最大值
#define INFINITY 最大的int//宏定义常量无穷
typedef struct {
	VertexType Vex[MaxVertexNum];
	EdgeType Edge[MaxVertexNum][MaxVertexNum];
	int vexnum, arcnum;//图的顶点数和弧数
}MGraph;
//邻接矩阵适合储存稠密图

2.邻接表法

#include <iostream>
using namespace std;
typedef char VertexType;

#define MaxVertexNum 100//顶点数目的最大值
#define INFINITY 最大的int//宏定义常量无穷
//邻接表类似于树的孩子表示法
typedef struct {//用邻接表储存的图
	AdjList vertices;
	int vexnum, arcnum;
}ALGraph;

typedef struct VNode{//顶点
	VertexType data;
	ArcNode* first;
}VNode,AdjList[MaxVertexNum];

typedef struct ArcNode {//弧
	int adjvex;//该弧指向哪个顶点
	struct ArcNode* next;//指向下一条弧的指针
	//Infotype info    //边权值
}ArcNode;

//用领接表表示的图删除结点不容易,因为有两份冗余的数据
//为了解决这个问题,有了十字链表(有向图)和临接多重表(无向图)

/*图的基本操作:
Adjacent(G,x,y):判断图G是否存在边<x,y>或(x,y)
邻接矩阵:判断矩阵对应位置是否为1;  领接表:从顶点x依次往后找
Neighbors(G,x):列出图G中与结点x邻接的边
邻接矩阵:判断矩阵对应行/列是否为1;   领接表:从顶点x依次往后找全部
InsertVertex(G,x):在图G中插入顶点x
邻接矩阵:在矩阵外围增加行和列   邻接表:在顶点表中插入新的顶点
DeleteVertex(G,x):从图G中删除顶点x
邻接矩阵:在矩阵中把顶点对应的行和列删除,在顶点表中将该顶点标志值0(标记该点已删除)  邻接表:在顶点表中把该顶点删除,同时遍历所有结点的弧,将该结点指向的和指向该结点的弧删除
AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边
邻接矩阵:在矩阵中将对应位置标为1即可 邻接表:在结点后边的弧链表中插入该弧
RemoveEdge(G,x,y):若无向边(x,y)或有向边<x,y>存在,则从图G中删除该边
和上面AddEdge相似的逆操作
FirstNeighbor(G,x):求图中顶点x的第一个邻接点
邻接矩阵:将矩阵中结点对应行中第一个值为1的顶点号输出 邻接表:查看该结点弧链表第一条弧即可
NextNeighbor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号
Get_edge_value(G,x,y):获取图G中边(x,y)或<x,y>对应的权值
Set_edge_value(G,x,y,v):设置图G中边(x,y)或<x,y>对应的权值为v。这条和上一条的关键都在于找顶点
*/

图的广度优先遍历

#define MAX_VERTEX_NUM 100
bool visited[MAX_VERTEX_NUM];//访问标记数组
//伪代码
void BFSTraverse(Graph G) {//对图G进行广度优先遍历
	for (i = 0; i < G.vexnum; ++i)
		visited[i] = false;//访问标记数组初始化
	InitQuene(Q);//初始化辅助队列Q
	for (i = 0; i < G.vexnum; ++i)
		if (!visited[i])//对每个连通分量用一次BFS
			BFS(G, i);
}
//广度优先遍历
void BFS(Graph G, int v) { //从顶点v出发,广度优先遍历图G
	visit(v);			//访问初始顶点v
	visited[v] = true;  //对v做已访问标记
	Enquene(Q, v);      //顶点v入队列Q
	while (!isEmpty(Q)) {
		DeQuene(Q, v);  //顶点v出队列
		for(w=FirstNeighbor(G,v);w>=0:w=NextNeighboe(G,v,w))
			//检测v所有的邻接点
			if (!visited[w]) {//w为v的尚未访问的邻接顶点
				visit(w);//访问顶点w
				visited[w] = true;//对w做已访问标记
				EnQuene(Q, w);//顶点w入队列
			}
	}

}
//要会构造广度优先序列、广度优先生成树

图的深度优先遍历

#define MAX_VERTEX_NUM 100
bool visited[MAX_VERTEX_NUM];//访问标记数组
//伪代码
void BFSTraverse(Graph G) {//对图G进行深度优先遍历
	for (i = 0; i < G.vexnum; ++i)
		visited[i] = false;//访问标记数组初始化
	for (i = 0; i < G.vexnum; ++i)
		if (!visited[i])//对每个连通分量用一次BFS
			DFS(G, i);
}
//广度优先遍历
void BFS(Graph G, int v) { //从顶点v出发,深度优先遍历图G
	visit(v);			//访问初始顶点v
	visited[v] = true;  //对v做已访问标记
	for (w = FirstNeighbor(G, v); w >= 0:w = NextNeighboe(G, v, w))
		
		if (!visited[w]) {//w为v的尚未访问的邻接顶点
			DFS(Q, w);
		}
}
//要会构造深度优先序列、深度优先生成树

最小生成树

最小生成树:各个结点都可以连通但是权值最小
1.Prim算法:(普里姆算法)适用于边稠密
从某结点开始构建生成树,每次将代价最小的新结点纳入生成树,直到所有结点都纳入为止。
2.Kruskal算法(克鲁斯卡尔):适用于边稀疏
每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选),直到所有结点都连通.

最短路径

1.广度优先算法求无权图最短路径:(谁先碰到这个点则该路径就是最短路径)

2.Dijkstra迪杰斯特拉算法:
定义三个数组:
标记各顶点是否已找到最短路径final[],初始化为false
当前最短路径的长度dist[],初始化为无穷大
当前最短路径上的前驱path[],初始化为-1
流程:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi,令final[i]true,
检查所有邻接自Vi的顶点,若其final值为false,经过Vi的路径比当前最短路径要短,则更新dist和path值
时间复杂度O(n^2)
Dijkstra算法不适合有负权值的带权图

3.Floyd算法:
定义两个二维数组A和Path
A用来存储两个点互相方向的最短路径
Path用来储存两点之间最短路径的倒数第二个点
流程:
1.若不允许在其他顶点中转,求出最短路径和path
2.若允许在V0中转,求出最短路径和path,能够得到更小的路径则更新两矩阵
3.若允许在V1中转……若允许在Vn-1中转……

//核心代码(伪):
for (int k = 0; k < n; k++) { 考虑以Vk为中转点
	for (int i = 0; i < n; i++) {//遍历整个矩阵,i为行号,j为列号
		for (int j = 0; j < n; j++) {
			if (A[i][j] > A[i][k] + A[k][j]) {//以Vk为中转点的路径更短
				A[i][j] = A[i][k] + A[k][j];//更新最短路径长度
				path[i][j] = k;//更新中转点
			}
		}
	}
}

时间复杂度O(n^3)
弗洛伊德算法可以解决带负权值的图(但解决不了负权回路的图,这种图也没有最短路径)

有向无环图

/*
有向无环图:一个有向图中不存在环,简称DAG图

1.把各个操作数不重复的排成一排
2.标出各个运算符的生效顺序(先后顺序有点出入无所谓)
3.按顺序加入运算符,注意分层
4.操作数不会重复,检查操作符即可。从底向上逐层检查同层的运算符是否可以合体


AOV网(用顶点表示活动的网)有向边<Vi,Vj>表示活动i先于j进行
拓扑排序的实现:
1.从AOV网中选择一个没有前驱(入度为0)的顶点并输出
2.从网中删除该结点和所有以它为起点的有向边
3.重复1、2直到当前AOV网为空或当前网中不存在无前驱的顶点为止
每个AOV网都有一个或多个拓扑排序序列
*/
#define MaxVertexNum 100 //图中顶点的最大值
typedef int VertexType;
typedef struct ArcNode { //边表结点
	int adjvex; //该弧所指向的顶点的位置
	struct ArcNode* nextarc;//指向下一条弧的指针
	//InfoType info;  //网的边权值
}ArcNode;

typedef struct VNode {//顶点表结点
	VertexType data;//顶点信息
	ArcNode* firstarc;//指向第一条依附该顶点的弧的指针

}VNode,AdjList[MaxVertexNum];

typedef struct {
	AdjList Vertices;//邻接表
	int vexnum, arcnum;//图的顶点数和弧数
}Graph;

bool TopologicalSort(Graph G) {
	InitStack(S);//栈的初始化
	for (int i = 0; i < G.vexnum; i++)
		if (indegree[i] == 0)
			Push(S, i);//将所有入度为零的顶点进栈
	int count = 0;//计数,记录当前已经输出的顶点数
	while (!IsEmpty(S)) {//栈不空,则存在入度为0的顶点
		Pop(S, i);//栈顶元素出栈
		print[count++] = i;//输出顶点i
		for (p = G.Vertices[i].firstarc; p; p = p->nextarc) {
			//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈s
			v = p->adjvex;
			if (!(--indegree[v]))
				Push(S, v);//入度为0,则入栈
		}
	}
	if (count < G.vexnum)
		return false; //排序失败,有向图中有回路
	else
		return true;//拓扑排序成功
}
//以上为求拓扑排序序列的算法,同样的思想我们也可以求逆拓扑排序序列,同时我们要认识到使用不同的储存结构会对算法时间复杂度有影响(产出指向顶点的边的时候,邻接表需要遍历整个表)

//逆拓扑排序的实现(DFS算法)思想是在深度优先搜索退栈的时候打印顶点
void DFSTraverse(Graph G) {//对图G进行深度优先遍历
	for (v = 0; v < G.vexnum; ++v)
		visited[v] = false;//初始化已访问标记数据
	for (v = 0; v < G.vexnum; ++v)//本代码从v=0开始遍历
		if (!visited[v])
			DFS(G, v);
}
void DFS(Graph G, int v)//从顶点v出发深度优先遍历图G
{
	visited[v] = True;//设已访问标记
	for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighor(G, v, w))
		if (!visited[w])//w为u的尚未访问的邻接顶点
			DFS(G, w);
	print(v);//输出顶点
}

/*
AOE网:带权有向图中,以顶点表示事件,以有向边表示活动,
以边上权值表示完成该活动的开销(如完成活动所需的时间),
称之为用边表示活动的网络

关键路径:从源点到汇点的有向路径可能有很多条,所有路径中,具有最大路径长度的路径称为关键路径
关键活动:关键路径上的活动称为关键活动

要会求事件的最早发生时间、事件的最迟发生时间、活动的最早发生时间、活动的最迟发生时间、活动的时间余量
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Handsome Wong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值