算法笔记(图专题)

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[] 来实现

  • int d[] 表示起点 s 到达顶点 Vi 的最短距离

//伪代码
//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******
        }
    }
}

Bellman-Ford算法:(能处理带零环或正环的图)

需要遍历所有边,使用领接表更方便

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[][]数组

5.最小生成树

  • 最小生成树是树,因此其边数等于顶点数减1,且树内一定不会有环;

  • 对给定的图G(V,E),其最小生成树可以不唯一,但其边权之和一定是唯一的;

  • 由于最小生成树是在无向图上生成的,因此其根结点可以是这棵树上的任意一个结点。

Prim算法:

//伪代码
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;
}
原文地址:https://www.cnblogs.com/dear_diary/p/8616235.html