【数据结构】图

总结

图的知识导图

图的性质

此处只列举了我觉得几个不太好记的几个。区分有向无向问题。
简单图: 边不重复,没有单环。
多重图:
简单完全图: 任意两点都有边;无向/有向。
子图: 边是子边,点是子点。
连通,连通图,连通分量: 任意两点可到,条件
强连通图,强连通分量: 两两点直链。
生成树/森林:
度/入度/出度: 无向等,有向2。
简单路径/回路: 定点不重复出现
有向树: 一个顶点入度为0,其余顶点入度都是1的有向图。
 

存储结构

邻接矩阵

#define MaxVertexNum 100
typedef char VertexType;
typedef int EdgeType;
typedef struct{
	VertexType Vex[MaxVertexNum]; // 顶点表 
	EdgeType Edge[MaxVertexNum][MaxVertexNum];
	int vexnum , arcnum; //当前的顶点数和弧数 
}MGraph; 

 
邻接表

//邻接表定义// 
typedef struct VNode{
	VertexType data; //顶点编号// 
	ArcNode *firstedge;//顶点的第一个指针// 
}VNode , AdjList[MaxVertexNum]; //用一个一维数组表示// 
 
//结点定义// 
typedef struct ArcNode{
	int adjvex;	//指向顶点位置// 
	struct ArcNode *next; //指向下一条弧的指针// 
}ArcNode;

 
十字链表

//顶点结点定义//
typedef struct VNode{
	VertexType data;
	ArcNode *firstin , *firstout;
}VNode;
 
//边表结点定义// 
typedef struct ArcNode{
	int tailvex , headvex;//弧头指向下表,弧尾指向下标//
	struct ArcNode *hlink , *rlink;//下一个相同弧头,下一个相同弧尾的边// 	
}ArcNode; 
 
//十字链表定义//
typedef struct{
	VNode xlist[MaxVertexNum];
	int vexnum , arcnum;//顶点数,弧数// 
}; 

 

图的遍历

DFS
基本思路:递归

void DFS(Graph G , int v){
	ArcNode *p; // 每次的指针p都是内部定义的 , 每次重新开始的时候就会设置一次// 
	visit(v);	//拜访该结点// 
	vis[v] = true;	//标记为访问过// 
	p = G->adjlist[v].firstarc; // 设置为第一个结点// 
	while(p != NULL){	//结点不为空的时候// 
		if(!vis[p->adjvex]){	//如果没有访问过// 
			DFS(G , p->adjvex);	// 递归访问这个结点// 
		}
		p = p->nextarc; // 移动到下一个结点// 
	}	
} 
 
void DFS(Graph G){
	for(int i = 0 ; i < vexnum ; i ++){
		vis[i] = false;
	}
	for(int i = 0 ; i < vexnum ; i ++){ // 为了防止非连通图的出现// 
		if(!vis[i])
			DFS(G , i);
	}
}

 
BFS
基本思路:其基本操作类似于树的层序遍历,设置一个队列。代码以邻接表的形式给出。

#define MaxSize 100;
bool vis[MaxSize];
 
void BFS(Graph G , int v){
	ArcNode *p;
	queue<int> q;
	visit(v); // 具体的一个访问结点的操作//
	vis[v] = true; //访问的开始设置为True// 
	q.push(v);
	while(!q.empty()){	//当queue中不为空// 
		int t = q.front(); // 降低一个数据拿出来// 
		q.pop(); // 弹出第一个数据// 
		p = G->adhList[v].firstedge; // 将第一个孩子结点拿出来// 
		while(p){	//当结点不为空的时候// 
			if(!vis[p->adjvex]){ // 如果没有被访问过 
				visit(p->adjvex);
				vis[p->adjvex] = true;	// 设置为拜访过// 
				q.push(p->adjvex); // 将其插入队列中// 
			}
			p = p->next; // 结束之后移向下一个结点// 
		}
	} 
} 
 
void BFS(Graph G){
	for(int i = 0 ; i < G->vexnum ; i ++){ // 初始化// 
		vis[i] = false;
	}
	for(int i = 0 ; i < G->vexnum ; i ++){ // 为了防止非连通图的问题// 
		if(!vis[i]){
			BFS(G , i);
		}
	}
}

 

图的应用

1.最小生成树MST

含义: 一个图中的极小连通子图,包含所有顶点,并且边尽可能少。最小生成树不一定是唯一的,但是权值之和必是唯一的。
 
特殊方法: 破圈法。任取一圈,去掉边中最大的那个边,依次将所有的圈都干掉,得到答案。
 
Prim算法
基本思路:在集合中找这几个里面最接近的那个。(点少边多图)
注意:当图中的所有闭环中边的大小都不一样,此时生成的MST是唯一的;无符号整型的int最大的数值是65535 , 而普通的int占32位的时候,最大可以赋值为:21 4748 3647,约为20亿,2*10^9。

void Prim(MGraph G){
	int adjvex[Maxvex];	 // adjvex代表此时访问这个点是从那个点来的。0代表访问结点已经访问过了 // 
	int lowcost[Maxvex];//	lowcost是访问当前结点的数值和距离。0代表已经加入生成树,无穷代表无关,有数据代表与当前 i 的距离  
	
	lowcost[0] = 0;//最初的时候 
	adjvex[0] = 0; 
	
	//初始化操作
	for(int i = 1 ; i < G.vexnum ; i ++){	/ 
		lowcost[i] = G.arc[0][i];	//这个代表的是从初始结点开始到各个结点的路径长 
		adjvex[i] = 0; // 保存邻接顶点下标 
	}
	
	//Core
	for(int i = 0 ; i < G.vexnum ; i ++){
		int min = 65535; // 无符号整型的最大值为65535
		int k = 0;
		
		//找顶点 
		for(int j = 0 ; j < G.vexnum ; j ++){
			if(lowcost[j] != 0 && lowcost[j] < min){	//尚未加入生成树,并且是当前最短的 
				min = lowcost[j];	//	距离当前 i 的最短的路径 
				k = j;	//	最短路径到达的位置 
			}
		}
		printf("%d --> %d\n" , adjvex[k] , k);
		lowcost[k] = 0; //标记为访问过 
		
		//更新表数据 
		for(int j = 0 ; j < G.vexnum ; j ++){
			if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){	//尚未加入生成树 , 并且可以更新为更短的 
				lowcost[j] = G.arc[k][j];	 
				adjvex[j] = k;	// 那些修改的点的下一坐标都改成从当前 k 开始 
			}
		} 
	} 
}

 
Kruskal算法
基本思想:利用并查集实现算法。打碎然后找复合条件的边(边少点多图。)

typedef struct{
	int a , b;
	int weight;
}Edge;	//边结构体
 
//并查集:并 
int Find(int *parent , int x){	//指针 
	while(parent[x] >= 0){
		x = parent[x];
	} 
	return x;
} 
 
Edge edges[MaxVex];
int parent[MaxVex];
 
void Kruskal(MGraph G){
	sort(edges);//	堆排序:权值递增排序 
	//并查集:初始化 
	for(int i = 0 ; i < G.vexnum ; i ++){
		parent[i] = -1;  
	}
	//搜索所有的边// 
	for(int i = 0 ; i < G.arcnum ; i ++){
		n = Find(parent , edges[i].a);
		m = Find(parent , edges[i].b);
		if(n != m){	//当边的结点不在一个圈子内,合并起来 
			parent[n] = m;	//并查集:并 
			printf("%d --> %d" , edges[i].a , edges[i].b);
		} 
	}
}

 

2.最短路径

Dijkstra算法
基本思路:不断更新,记得不能负权边。
注意:Dijkstra区别于Prim算法,所以Dijkstra不能得到一棵最小生成树。
Dijkstra 算法可以视为一个带有权重版本的 BFS。

#define INF 65535
 
/*
 * v代表当前访问开始的结点
 * path[]当前结点的来源
 */ 
 
void Dijkstra(MGraph G , int v , int path[] , int dist[]){
	bool vis[Maxsize];	//那些被访问最短路径,访问过的设置为true , 未访问的false
	int u;
	
	//初始化记录表 
	for(int i = 0 ; i < G.vexnum ; i ++){
		dist[i] = G.edge[v][i];	//	到初始的结点的距离 
		vis[i] = false;	//	所有的结点都是尚未访问的 
		if(G.edge[v][i] < INF)
			path[i] = v;	//	与初始结点联通的数据写入path 
		else
			path[i] = -1
	} 
	vis[v] = true;	//	根结点已经访问过 
	path[v] = -1;
	
	//核心
	for(int i = 0 ; i < G.vexnum ; i ++){
		int min = INF;
		
		//核心:找最小值 
		for(int j = 0 ; j < G.vexnum ; j ++){
			if(vis[j] == false && dist[j] < min){	//找未访问过的结点中的最小值 
				min = dist[j];
				u = j;
			}
		}
		
		//核心:更新数组 
		vis[u] = true;	//	该结点访问过 
		for(int j = 0 ; j < G.vexnum ; j ++){
			if(vis[j] == false && dist[u] + G.edges[u][j] < dist[j]){	//	找那些能与当前结点 
				dist[j] = dist[j] + G.edges[u][j];
				path[j] = u;	//	更新,代表这条路径的来源 
			}
		}
	} 	
}

 
Floyd算法
基本思想:每次拉入一个新的点更新,但是不允许有负值环路。

void Floyd(MGraph G , int Path[][]){
	int A[Maxsize][Maxsize];
	
	//初始化 
	for(int i = 0 ; i < G.vexnums ; i ++){
		for(int j = 0 ; j < G.vexnums ; j ++){
			A[i][j] = G.Edges[i][j];
			Path[i][j] = -1; 
		}
	}
	
	//更新数值 
	for(int k = 0 ; k < G.vexnums; k ++){
		for(int i = 0 ; i < G.vexnums ; i ++){
			for(int j = 0 ; j < G.vexnums ; j ++){
				if(A[i][j] > A[i][k] + A[k][j]){	//	从 i 到 k 到 j 的距离 < i 到 j 的距离 
					A[i][j] = A[i][k] + A[k][j];	
					Path[i][j] = k;	//	中间穿插的路径 
				}
			}
		}
	}
}

 

3.拓扑排序AOV

基本思路: 入度为0,然后依次删除。
时间复杂度: O(n + e)

bool TopologicalSort(Graph G){
	stack<int> s;	
	
	//找到一个入度为 0 的结点 
	for(int i = 0 ; i < G.vexnum ; i ++){
		if(indrgee[i] == 0){
			s.push(i);	
		} 
	}
	
	int count = 0;	//	记录当前已经输出的顶点数 
	while(!s.empty()){
		int k = s.top();
		s.pop();
		count ++; 
		printf("%d " , G.adlist[k]);
		for(ArcNode *p = G.vertices[i].firstarc ; p ; p = p->nextarc){	//遍历这个结点的孩子链表 
			v = p->adjvex;
			if(!( --indegree[v]))	//	如果它的结点的数值减到 0 了 入栈 
				s.push(v);
		}
	}
	if(count < G.vexnum)	//	输出的结点数比原来的少,则存在回路	
		return false;	//	有环的话,入度结点不肯能有1 -> 栈空了 -> count的个数不如原有的多 
	else
		return true;
} 

 

4.关键路径(AOE)

基本思路:
加速某个关键活动不一定缩短整个工程的工期,因为关键路径可能不止一条。需要缩短那些关键路径公共部分的那些步骤,才能缩短关键路径。
计算要点:

所属分类内容计算
事件最早开始时间前往后,max
事件最迟发生时间后往前,min
活动最早开始时间事件开始时间
活动最迟发生时间最迟发生时间 - 花费时间
活动机动时间最迟看开始时间 - 最早开始时间
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值