算法笔记(图专题)
1.图的DFS
//伪代码(通用) DFS(u){//访问顶点u vis[u] = true; //设置u已被访问 for(从u出发能到达的所有顶点v){ if(vis[v] == false) //如果v未被访问 DFS(v); //递归访问v } } DFSTrave(G){ //遍历图G for(G的所有顶点){ if(vis[u] == false) //如果u未被访问 DFS(u); //访问u所在的连通块 } }
邻接矩阵版:
const int MAXV = 1000; const int INF = 10000000; //G[i][j]设为INF表示两点间不通 int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数 bool vis[MAXV] = {false}; //记录顶点i是否已被访问 void DFS(int u, int depth){ //u为当前访问的顶点标号,depth为深度 vis[u] = true; //设置u已被访问 //此处可添加一些对u的操作 for(int v=0; v<n; v++){ //对每个顶点v if(vis[v]==false && G[u][v]!=INF){ //如果v未被访问,且u可到达v DFS(v, depth+1); //访问v,深度加1 } } } void DFSTrave(){ //遍历图G for(int u=0; u<n; u++){ //对每个顶点u if(vis[u] == false){ //如果u未被访问 DFS(u, 1); //访问u和u所在的连通块,1表示初始为第一层 } } }
领接表版:
vector<int> Adj[MAXV]; //图G的领接表 int n; //n为顶点数,MAXV为最大顶点数 bool vis[MAXV] = {false}; //记录顶点i是否已被访问 void DFS(int u, int depth){ vis[u] = true; for(int i=0; i<Adj[u].size(); i++){ int v = Adj[u][i]; if(vis[v] == false){ DFS(v, depth+1); } } } void DFSTrave(){ for(int u=0; u<n; u++){ //对每个顶点u if(vis[u] == false){ //如果u未被访问 DFS(u, 1); //访问u和u所在的连通块,1表示初始为第一层 } } }
2.图的BFS
邻接矩阵版:
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数 bool inq[MAXV] = {false}; void BFS(int u){ queue<int> q; q.push(u); //将初始点u入队 while(!q.empty()){ //只要队列非空 int u = q.front(); //去处队首元素 q.pop(); for(int v=0; v<n; v++){ if(inq[v]==false && G[u][v]!=INF){ q.push(v); //将v入队 inq[v] = true; } } } } void BFSTrave(){ for(int u=0; u<n; u++){ if(inq[u] == false){ BFS(q); } } }
领接表版:
vector<int> Adj[MAXV]; int n; bool inq[MAXV] = {false}; void BFS(int u){ queue<int> q; q.push(u); inq[u] = true; while(!q.empty()){ int u = q.front(); q.pop(); for(int i=0; i<Adj[u].size(); i++){ int v = Adj[u][i]; //结点u的相邻结点 if(inq[v] == false){ q.push(v); inq[v] = true; } } } } void BFSTrave(){ for(int u=0; u<n; u++){ if(inq[u] == false){ BFS(q); } } } //带顶点层号的 struct Node{ int v; //顶点编号 int layer; //顶点层号 }; vector<Node> Adj[N]; void BFS(int s){ //s为起始点编号 queue<Node> q; Node start; start.v = s; //起始顶点编号 start.layer = 0; //起始顶点层号为0 q.push(start); inq[start.v] = true; while (!q.empty()){ Node topNode = q.front(); //取出队首元素 q.pop(); int u = topNode.v; //队首顶点的编号 for(int i=0; i<Ajd[u].size(); i++){ Node next = Adj[u][i]; next.layer = topNode.layer + 1; //next层号等于当前顶点层号+1 if(inq[next.v] == false){ //如果next的编号未被加入过队列 q.push(next); inq[next.v] = true; } } } }
3.单源最短路径
Dijkstra (适合无负权路径)
两个关键:
-
集合S存放已被访问的结点,用 bool vis[] 来实现
-
//伪代码 //G为图,一般为全局变量,数组d为源点到达各顶点的最短路径长度, s为起点 Dijkstra(G[][], d[], s){ 初始化; //(勿忘) for(循环n次){ u = 使d[u]最小的还未被访问的顶点的标号; 记u已被访问; for(从u出发能到达的所有顶点v){ if(v未被访问 && 以u为中介点使s到顶点v的最短距离d[v]更优){ 优化d[v]; } } } }
邻接矩阵版:
const int INF = 10000000; //起点到达各点的最短路径长度 bool vis[MAXV] = {fasle}; //标记数组 int pre[MAXV]; //pre[v]表示起点到顶点v的最短路径v的前一个顶点(*****新增加******) void Dijkstra(int s){ //s为起点 fill(d, d+MAXV, INF); for(int i=0; i<n; i++) pre[i] = i; //pre[]初试状态设每个结点的前驱为自身(*****新增加******) d[s] = 0; //起点s到达自身的距离为0 for(int i=0; i<n; i++){ //循环n次 int u = -1, MIN = INF; //u使d[u]最小,MIN存放该最小的d[u] for(int j=0; j<n; j++){ //找到未访问的顶点中d[]最小的 if(vis[j] == false && d[j] < MIN){ u = j; MIN = d[j]; } } //找不到小于INF的d[u],说明剩下的顶点和起点s不连通 if(u == -1) return; vis[u] = true; //标记u已访问 for(int v=0; v<n; v++){ //(此处可添加应用扩展) //如果v未访问 && u能到达v && 以u为中间点可以使d[v]更优 if(vis[v] == false && G[u][v]!=INF && d[u]+G[u][v] < d[v]){ d[v] = d[u] + G[u][v]; //优化d[v] pre[v] = u; //记录v的前驱顶点是u(*****新增加******) } } } } -------------------------------------------------------------------------------------------------- //应用扩展 //(1)新增边权,用cost[u][v]表示u->v的花费(由题目输入),增加数组c[]表示从起点s到达u的最小花费c[u] //初始化c[s]为0, 其余为INF for(int v=0; v<n; v++){ if(vis[v] == false && G[u][v] != INF){ if(d[u]+G[u][v] < d[v]){ //以u为中间点可以使d[v]更优 d[v] = d[u] + G[u][v]; c[v] = c[u] + cost[u][v]; } else if(d[u]+G[u][v] == d[v] && c[u]+cost[u][v] < c[v]){ c[v] = c[u] + cost[u][v]; //最短距离相同时看能否使c[v]更优 } } } //新增点权,weight[u]表示点u的点权值,并增加一个数组w[],令从起点s到达u的点权值总和为w[u] //初始化w[s]为weight[s]、其余w[u]均为0 for(int v=0; v<n; v++){ if(vis[v] == false && G[u][v]!=INF){ if(d[u]+G[u][v] < d[v]){ //以u为中介点可以使d[v]更优 d[v] = d[u] + G[u][v]; w[v] = w[u] + weight[v]; } else if(d[u]+G[u][v] == d[v] && w[u]+weigh[v] > w[v]){ //(此处可根据需求修改>或<) w[v] = w[u] + weight[v]; //最短距离相同时看能否使w[v]更优 } } } //求最短路径条数,只需增加一个数组num[],令从起点s到达顶点u的最短路径条数为num[u], //初始化时只有num[s]为1、其余num[u]均为0 for(int v=0; v<n; v++){ if(vis[v] == false && G[u][v]!=INF){ if(d[u]+G[u][v] < d[v]){ //以u为中介点可以使d[v]更优 d[v] = d[u] + G[u][v]; num[v] = nun[u]; //num[v] 继承 nun[u] } else if(d[u]+G[u][v] == d[v]){ //(此处可根据需求修改>或<) num[v] += num[u]; //最短距离相同时累加num****** } } }
需要遍历所有边,使用领接表更方便
struct Node{ int v, dis; //v为领接表的目标顶点,dis为领接表的边权 } vector<Node> Adj[MAXV]; //图G的领接表 int n, d[MAXV]; //n为顶点,d[]为起点到各点的最短路径长度 bool Bellman(int s){ //s为源点 fill(d, d+MAXV, INF); //初始化d d[s] = 0; //以下为求解数组d的部分 for(int i=0; i<n-1; i++){ //执行n-1轮操作,n为顶点数 for(int u=0; u<n; u++){ //每轮操作都遍历所有边 for(int j=0; j<Adj[u].size(); j++){ int v = Adj[u][j].v; //领接边的顶点 int dis = Adj[u][j].dis; //领接边的边权 if(d[u] + dis < d[v]){ //以u为中介点可以使d[v]更小 d[v] = d[u] + dis; //松弛操作 } } } } //以下判断负环的代码 for(int u=0; u<n; u++){//对每条边进行判断 for(int j=0; j<Adj[u].size(); j++){ int v = Adj[u][j].v; //邻接边的顶点 int dis = Adj[u][j].dis; //邻接边的边权 if(d[u]+dia < d[v]){ //如果仍可以被松弛 return false; //说明图中有从源点可达的负环 } } } return true; //数组d的所有值都已经到达最优 }
4.全源最短路径
Floyd算法:
-
时复O(n^3),n控制在200以内。
-
用邻接矩阵。
//伪代码 枚举顶点 k ∈ [1, n] 以顶点k作为中介点,枚举所有顶点对i和j (i∈[1,n], j∈[1,n]) 如果dis[i][k] + dis[k][j] < dis[i][j]成立 赋值dis[i][j] = dis[i][k] + dis[k][j]
const int INF = 10000000; const int MAXV = 200; int n, m; //n为顶点数,m为边数 int dis[MAXV][MAXV]; //dis[i][j]表示顶点i和顶点j的最短距离 void Floyd(){ for(int k=0; k<n; k++){ for(int i=0; i<n; i++){ for(int j=0; j<n; j++){ if(dis[i][k]!=INF && dis[k][j]!=INF && dis[i][k] + dis[k][j] < dis[i][j]){//***** dis[i][j] = dis[i][k] + dis[k][j]; //找到更短的路径 } } } } } //最后输出dis[][]数组
-
最小生成树是树,因此其边数等于顶点数减1,且树内一定不会有环;
-
对给定的图G(V,E),其最小生成树可以不唯一,但其边权之和一定是唯一的;
-
由于最小生成树是在无向图上生成的,因此其根结点可以是这棵树上的任意一个结点。
//伪代码 Prime(G, d[]){ 初始化; for(循环n次){ u = 使d[u]最小的还未被访问的顶点的标号; 记u已被访问; for(从u出发能到达的所有顶点){ if(v未被访问 && 以u为中介点使得v与集合S的最短距离d[v]更优){ 将G[u][v]赋值给v与集合的最短距离d[v]; } } } }
邻接矩阵版:
int n, G[MAXV][MAXV]; //n为顶点数 int d[MAXV]; //*****顶点与集合S的最短距离***** bool vis[MAXV] = {false}; //标记数组 int prime(){ //默认0为初始点,函数返回最小生成树的边权之和 fill(d, d+MAXV, INF); d[0] = 0; //只有0号顶点到集合S的距离为0,其余全为INF int ans = 0; //存放最小生成树的边权之和 for(int i=0; i<n; i++){ int u = -1, MIN = INF; for(int j=0; j<n; j++){ if(vis[j] == false && d[j] < MIN){ u = j; MIN = d[j]; } } if(u == -1) return -1;//找不到小于INF的d[u],则剩下的顶点和集合S不连通 vis[u] = true; ans += d[u]; //*****将与集合S最小的边加入最小生成树***** for(int v=0; v<n; v++){ if(vis[v] == false && G[u][v]!=INF && G[u][v] < d[v]){ d[v] = G[u][v]; //(***注意这里与Dijkstra的区别***) } } } return ans; }