【数据结构】图

痞子三分冷 提交于 2020-02-02 04:58:40

【数据结构】图

图的存储

//邻接矩阵,即开一个二维数组
vector<int>Adj[N];

//邻接表
struct Node{
    int v;  //边的终点编号
    int w;  //边的权值
};
vector<Node>Adj[N];

//有顶点1到顶点3的边,权值为4:
Node temp;
temp.v=3;
temp.w=4;
Adj[1].push_back(temp);//通过定义一个临时变量的方式,添加边

//有顶点1到顶点3的边,权值为4:
struct Node{
    int v;
    int w;
    Node(int _v,int _w)
    :v(_v)
    ,w(_w)
    {}  //默认构造函数
}
Adj[1].push_back(Node(3,4));//不需要构造临时变量

图的遍历

const int maxv=1001;    //最大顶点数
const int INF=1000000;  //很大的一个数,来表示,在有向图中,两顶点之间不连通

深度优先遍历

//邻接矩阵版
int n;  //n为顶点数
int G[maxn][maxn];
bool vis[maxn]={false};
void dfs(int u,int depth){  //u为当前访问的顶点编号,depth为深度
    vis[u]=true;
    for(int v=0;v<n;v++){
        if(vis[v]==false && G[u][v]!=INF){
            dfs(v,depth+1);
        }
    }
}
//遍历图G
void dfsTrave(){
    for(int u=0;u<n;u++){
        if(vis[u]==flase){
            dfs(u,1);
        }
    }
}

//邻接表版
int n;
vector<int>Adj[maxn];
bool vis[maxn]={false};
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);
        }
    }
}
//遍历图G
void DFSTrave(){
    for(int u=0;u<n;++u){
        if(vis[u]==false)
            DFS(u,1);
    }
}

广度优先遍历

//邻接矩阵版
int n;
int G[maxn][maxn];
bool inq[maxn]={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 v=0;v<n;v++){
            if(inq[v]==false && G[u][v]!=INF){
                q.push(v);
                inq[v]true;
            }
        }
    }
}

void BFSTrave(){
    for(int u=0;u<n;u++){
        if(inq[u]==false){
            BFS(u);
        }
    }
}
//邻接表版
int n;
vector<int>Adj[maxn];
bool inq[maxn]={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];
            if(inq[v]==false){
                q.push(v);
                inq[v]=true;
            }
        }
    }
}
void BFSTrave(){
    for(int i=0;i<n;i++){
        if(inq[i]==false){
            BFS(u);
        }
    }
}

最短路径

Dijkstra算法
解决单源最短路径问题
邻接矩阵版:

int n;
int d[maxn];//起点到其它各点的最短路径长度
int G[maxn][maxn];
bool vis[maxn]={false};

void Dijkstra(int s){   //s为起点
    fill(d,d+maxn,INF);//fill函数将整个数组赋为INF
    d[s]=0;
    for(int i=0;i<n;i++){
        int u=-1;
        int 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;//找不到小于INF的d[u],说明剩下的点和源点不连通
        vis[u]=true;
        for(int v=0;v<n;v++){
            if(vis[v]==false && G[u][v]!=INF && d[u]+G[u][v]<d[v]){
                d[v]=d[u]+G[u][v];
            }
        }
    }
}
//时间复杂度O(n^2)

邻接表版:

struct Node{
    int v;  //边的目标顶点
    int dis;//边权
};
vector<Node>Adj[maxn];
int n;//顶点数
int d[maxn];
bool vis[maxn]={flase};

void Dijkstra(int s){   //源点
    fill(d,d+maxn,INF);
    d[s]=0;
    for(int i=0;i<n;i++){   //循环n次
        int u=-1;
        int 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;
        vis[u]=true;
        for(int j=0;j<Adj[u].size();++j){
            int v=Adj[u][j].v;
            if(vis[v]==false && d[u]+Adj[u][j].dis < d[v]){
                d[v]=d[u]+Adj[u][j].dis;
            }
        }
    }
}

Dijlstra算法可以很好的解决无负边权的最短路径问题,但如果出现了负边权,Dijksra算法就会失效。
为了更好求解有负边权的情况,使用Bellman-Ford算法,BF算法既能解决单源最短路的情况,也能解决有负边权的情况。
Bellman-Ford算法:

struct Node{
    int v,dis;
};
vector<Node>Adj[maxn];
int n;
int d[maxn];

bool Bellman(int s){
    fill(d,d+maxn,INF);
    d[s]=0;
    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]+dis<d[v]){  //可以松弛,则否
                return false;
            }
        }
    }
    return true;
}

解决全源最短路径问题-Floyd算法
floyd算法代码:

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];
                }
            }
        }
    }
}

示例代码

#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=1000000;
const int maxn=110;
int n,m;    //顶点数和边数
int dis[maxn][maxn];    //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];
                }
            }
        }
    }
} 

int main(){
    int u,v,w;
    fill(dis[0],dis[0]+maxn*maxn,INF);
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++){
        dis[i][i]=0;
    }
    for(int i=0;i<m;i++){
        scanf("%d %d %d",&u,&v,&w);
        dis[u][v]=w;
    }

    floyd();
    for(int i=0;i<n;i++){   //输出dis数组
        for(int j=0;j<n;j++){
            cout<<dis[i][j]<<endl;
        }
    }
    return 0;
}

最小生成树

Prim算法求解最小生成树权值之和
邻接矩阵版,代码:

int n;
int G[maxn][maxn];
int d[maxn];
bool vis[maxn]={flase};

int prim(){  //默认以0号为起始点
    fill(d,d+maxn,INF);
    d[0]=0;
    int ans=0;  //存放最小生成树权值之和
    for(int i=0;i<n;i++){   //循环n次
        int u=-1;
        int 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;//不连通
        vis[u]=true;
        ans+=d[u];
        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];
            }
        }
    }
    return ans;
}

邻接表版,代码:

struct Node{
    int v;
    int dis;
};
vector<Node>Adj[maxn];
int n;
ind d[maxn];
bool vis[maxn]={false};

int prim(){  //默认以0号为起始点
    fill(d,d+maxn,INF);
    d[0]=0;
    int ans=0;  //存放最小生成树权值之和
    for(int i=0;i<n;i++){   //循环n次
        int u=-1;
        int 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;//不连通
        vis[u]=true;
        ans+=d[u];
        for(int j=0;j<Adj[u].size();j++){
            int v=Adj[u][j].v;
            if(vis[v]==false && Adj[u][j].dis<d[v]){
                d[v]=Adj[u][j].dis;
            }
        }
    }
    return ans;
}

Kruskal算法求解最小生成树
kruskal算法采用边贪心的策略,对边权值排序,使用并查集来判断两个端点是否在同一个集合中。
代码:

struct edg{
    int u,v;    //边的两个端号
    int cost;   //边权
}E[maxn];
bool cmp(edg a,edg b){
    return a.cost<b.cost; 
}
int kruskal(int n,int m){   //n为顶点个数,m为图的边数
    int ans=0;  //边权之和
    int numEdg=0;   //当前生成树的边数
    for(int i=1;i<=n;i++){  //初始化
        father[i]=i;
    }
    sort(E,E+m,cmp);
    for(int i=0;i<m;i++){ //所有边
        int fa=findFather(E[i].u);
        int fb=findFather(E[i].v);
        if(fa!=fb){
            father[fa]=fb;
            ans+=E[i].cost;
            numEdg++;
            if(numEdg==n-1) break;
        }
    }
    if(numEdg!=n-1) return -1;
    else return ans;
}

拓扑排序

有向无环图(Directed Acyclic Graph,DAG)是指:如果一个有向图的任意顶点都无法通过一些有向边回到自身,
那么称这个有向图为有向无环图。
拓扑排序是将有向无环图的所有顶点排成一个线性序列,使得图G中的任意两个顶点u、v,如果存在边u->v,则在
序列中u一定在v之前。这个序列称为拓扑排序。
拓扑排序代码:

vector<int>G[maxn];
int n,m,inDegree[maxn];//顶点数、边数、入度

bool topologicalSort(){
    int num=0;  //记录加入拓扑序列的顶点数
    queue<int>q;
    for(int i=0;i<n;i++){
        if(inDegree[i]==0){
            q.push(i);
        }
    }
    while(!q.empty()){
        int u=q.front();
        cout<<u<<endl;  //输出拓扑序列中的顶点
        q.pop();
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i];
            inDegree[v]--;
            if(inDegree[v]==0){
                q.push(v);
            }
        }
        G[u].clear();   //清除顶点u的所有出边
        num++;
    }
    if(num==n) return true;
    else return false;
}

拓扑排序很重要的一个应用就是判断给定的图是否是有向无环图

关键路径

Activity On Vertex,AOV网是指用顶点表示活动,而用边集表示活动间优先关系的有向图。
Activity On Edge,AOE网是指用带权的边集表示活动,而用顶点表示事件的有向图。
AOE网是基于工程提出的,需要着重解决两个问题:

1.工程起始到中止需要的时间
2.那些路径上的活动是影响整个工程的关键

两者都是有向无环图。
AOE网中的最长路径被称为关键路径,,关键路径上的活动成为关键活动,关键活动影响整个工程进度。
ve[r]表示活动ar的最早开始时间
vl[r]表示活动ar的最迟开始时间
求出两个数组之后,判断e[r]==l[r]是否成立来确定活动ar是否是关键活动。
代码:

//保存拓扑序列
stack<int>topOrder;

//拓扑排序,并求取ve数组
bool topologicalSort(){
    queue<int>q;
    for(int i=0;i<n;i++){
        if(inDegree[i]==0){
            q.push(i);
        }
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        topOrder.push(u);
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i].v;
            inDegree[v]--;
            if(inDegree[v]==0){
                q.push(v);
            }
            //用ve[u]来更新u的所有后继结点v
            if(ve[u]+G[u][i].w > ve[v]){
                ve[v]=ve[u]+G[u][i].w;
            }
        }
    }
    if(topOrder.size()==n) return true;
    else return false;
}

//使用得到的拓扑序列,逆序求vl
fill(vl,vl+n,ve[n-1]);

//topOrder出栈即为逆拓扑序列,求解vl数组
while(!topOrder.empty()){
    int u=topOrder.top();
    topOrder.pop();
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i].v;
        if(vl[v]-G[u][i].w < vl[u]){
            vl[u]=vl[v]-G[u][i].w;
        }
    }
}

求解关键路径
代码:

int CriticalPath(){
    memset(ve,0,ve+sizeof(ve));
    if(topologicalSort()==false){
        return -1;
    }
    fill(vl,vl+n;ve[n-1]);
    //求解vl数组
    while(!topOrder.empty()){
        int u=topOrder.top();
        topOrder.pop();
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i].v;
            if(vl[v]-G[u][i].w < vl[u]){
                vl[u]=vl[v]-G[u][i].w;
            }
        }
    }

    //遍历邻接表的所有边,计算活动的最早开始时间e和最迟开始时间l
    for(int u=0;u<n;u++){
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i].v;
            int w=G[u][i].w;
            int e=ve[u],l=vl[v]-w;
            if(e==l){
                cout<<u<<"->"<<v<<endl;//输出关键活动
            }
        }
    }
    return ve[n-1]; //返回关键路径长度
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!