zoukankan      html  css  js  c++  java
  • 算法笔记(图专题)

    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;
    }
  • 相关阅读:
    2019阿里系电子书合集来了!
    FTP主动模式和被动模式的区别
    Vsftpd运行的两种模式-xinetd运行模式和 standalone模式
    Logstash组件详解(input、codec、filter、output)
    Docker入门-笔记-1
    K8S命令-Kubectl 命令大全
    Nginx-HTTP Strict Transport Security(HSTS)
    IPV6技术笔记(剖析IPv4toIPv6)
    一个交换机到底能带几台摄像头?结合生产经验咱们来分析!
    ELK查询命令详解
  • 原文地址:https://www.cnblogs.com/dear_diary/p/8616235.html
Copyright © 2011-2022 走看看