第六章节 图

参考:1.数据结构C语言版|第2版;2.力扣;3.2024年数据结构考研复习指导。三个参考分别依次对应文章三个部分。

第一部分

基本概念

图G由两个集合V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合。重要术语1: 子图、完全图、非完全图、稠密图、稀疏图、有向图、无向图、有权图(网)、无权图。重要术语2: 权、邻接点、度、入度、出度。重要术语3: 路径、简单路径、路径长度、简单路径长度、回路(环)、简单回路(简单环)、回路长度(环长度)、简单回路长度(简单环长度)。重要术语4: 连通、连通图、连通分量、强连通、强连通图、强连通分量。重要术语5: 生成树。重要术语6: 有向树、生成森林。

存储结构

邻接矩阵

使用邻接矩阵的存储结构建立无向无权图。

#include<iostream>
#define MAXSIZE 100
struct Graph
{
    int numnodes;
    int nodesvector[MAXSIZE];
    int numedges;
    int edgesmatrix[MAXSIZE][MAXSIZE]={0};
};
int main()
{
    using namespace std;
    Graph G;
    cin>>G.numnodes;
    for (int i=0;i<G.numnodes;i++)
        cin>>G.nodesvector[i];
    cin>>G.numedges;
    for (int i=0;i<G.numedges;i++)
    {
        int a,b;cin>>a>>b;
        G.edgesmatrix[a][b]=1;
        G.edgesmatrix[b][a]=1;
    }
    return 0;
}

优点:1.便于判断 n o d e i node_i nodei n o d e j node_j nodej之间是否有边,如果有,则 m a t r i x i j ≠ 0 matrix_{ij}\neq 0 matrixij=0;如果没有,则 m a t r i x i j = 0 matrix_{ij}=0 matrixij=0。2.便于计算 n o d e i node_i nodei的度,如果是无向图,那么 n o d e i node_i nodei的度为第 i i i行非零元素的个数;如果是有向图,那么 n o d e i node_i nodei的出度为第 i i i行非零元素的个数,入度为第 i i i列非零元素的个数。
缺点:1.难以增加或者删除结点。2.空间复杂度为 O ( n 2 ) O(n^2) O(n2),存储稀疏矩阵时会浪费很多空间。

邻接表

使用邻接表的存储结构建立有向无权图。

#include<iostream>
#define MAXSIZE 100
struct edge
{
    int index;
    edge * next=nullptr;
};
struct node
{
    int data;
    edge * first=nullptr;
};
struct Graph
{
    int numnodes;
    int numedges;
    node lb[MAXSIZE];
};
int main()
{
    using namespace std;
    Graph G;
    cin>>G.numnodes;
    for (int i=0;i<G.numnodes;i++)
        cin>>G.lb[i].data;
    cin>>G.numedges;
    for (int i=0;i<G.numedges;i++)
    {
        int a,b;cin>>a>>b;
        edge * temp=new edge;temp->index=b;temp->next=G.lb[a].first;G.lb[a].first=temp;
    }
    return 0;
}

优点:1.便于增加或者删除结点。2.空间复杂度为 O ( n + e ) O(n+e) O(n+e)
缺点:1.判断 n o d e i node_i nodei n o d e j node_j nodej之间是否有边的时间复杂度较高,需要遍历第 i i i个边表找到 n o d e j node_j nodej或者遍历第 j j j个边表找到 n o d e i node_i nodei。2.在有向图中,难以计算一个结点的入度。

逆邻接表

使用逆邻接表的存储结构建立有向无权图。

#include<iostream>
#define MAXSIZE 100
struct edge
{
    int index;
    edge * next=nullptr;
};
struct node
{
    int data;
    edge * first=nullptr;
};
struct Graph
{
    int numnodes;
    int numedges;
    node lb[MAXSIZE];
};
int main()
{
    using namespace std;
    Graph G;
    cin>>G.numnodes;
    for (int i=0;i<G.numnodes;i++)
        cin>>G.lb[i].data;
    cin>>G.numedges;
    for (int i=0;i<G.numedges;i++)
    {
        int a,b;cin>>a>>b;
        edge * temp=new edge;temp->index=a;temp->next=G.lb[b].first;G.lb[b].first=temp;
    }
    return 0;
}

优点:1.便于增加或者删除结点。2.空间复杂度为 O ( n + e ) O(n+e) O(n+e)
缺点:1.判断 n o d e i node_i nodei n o d e j node_j nodej之间是否有边的时间复杂度较高,需要遍历第 i i i个边表找到 n o d e j node_j nodej或者遍历第 j j j个边表找到 n o d e i node_i nodei。2.在有向图中,难以计算一个结点的出度。

十字链表

使用十字链表的存储结构建立有向无权图。

#include<iostream>
#define MAXSIZE 100
struct edge
{
    int index;
    edge * next=nullptr;
};
struct node
{
    int data;
    edge * in_edge=nullptr;
    edge * out_edge=nullptr;
};
struct Graph
{
    int numnodes;
    int numedges;
    node lb[MAXSIZE];
};
int main()
{
    using namespace std;
    Graph G;
    cin>>G.numnodes;
    for (int i=0;i<G.numnodes;i++)
        cin>>G.lb[i].data;
    cin>>G.numedges;
    for (int i=0;i<G.numedges;i++)
    {
        int a,b;cin>>a>>b;
        edge * temp1=new edge;edge * temp2=new edge;
        temp1->index=a;temp1->next=G.lb[b].in_edge;G.lb[b].in_edge=temp1;
        temp2->index=b;temp2->next=G.lb[a].out_edge;G.lb[a].out_edge=temp2;
    }
    return 0;
}

优点:1.便于增加或者删除结点。2.空间复杂度为 O ( n + e ) O(n+e) O(n+e)
缺点:判断 n o d e i node_i nodei n o d e j node_j nodej之间是否有边的时间复杂度较高,需要遍历第 i i i个边表找到 n o d e j node_j nodej或者遍历第 j j j个边表找到 n o d e i node_i nodei
补充:表头结点表、边表。

邻接多重表

使用邻接多重表的存储结构建立无向无权图。

#define MAXSIZE 100
struct edge
{
    int ivex;
    edge * ilink;
    int jvex;
    edge * jlink;
};
struct node
{
    int data;
    edge * first;
};
struct Graph
{
    int numnodes;
    int numedges;
    node lb[MAXSIZE];
};

我承认我写不出来。我要偷懒。会画就行。希望不要埋下下伏笔。口诀:编号、组队、乱连。

经典应用

图的遍历

深度优先搜索
417. 太平洋大西洋水流问题
class Solution {
public:
    int m,n;
    vector<vector<int>> matrix0,matrix1,matrix2;
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        m=heights.size();n=heights[0].size();
        matrix0=heights;matrix1.resize(m,vector<int> (n,0));matrix2.resize(m,vector<int> (n,0));
        for (int i=0;i<m;i++) {dfs(i,0,matrix1);dfs(i,n-1,matrix2);}
        for (int j=0;j<n;j++) {dfs(0,j,matrix1);dfs(m-1,j,matrix2);}
        vector<vector<int>> result;
        for (int i=0;i<m;i++)
            for (int j=0;j<n;j++)
                if (matrix1[i][j] and matrix2[i][j])
                    result.push_back({i,j});
        return result;
    }
    void dfs(int x,int y,vector<vector<int>> & matrix)
    {
        if (matrix[x][y]) return; 
        matrix[x][y]=1;
        if (x+1<m and matrix0[x+1][y]>=matrix0[x][y])
            dfs(x+1,y,matrix);
        if (x-1>=0 and matrix0[x-1][y]>=matrix0[x][y])
            dfs(x-1,y,matrix);
        if (y+1<n and matrix0[x][y+1]>=matrix0[x][y])
            dfs(x,y+1,matrix);
        if (y-1>=0 and matrix0[x][y-1]>=matrix0[x][y])
            dfs(x,y-1,matrix);
    }
};
1020. 飞地的数量
class Solution {
public:
    int m,n;
    vector<vector<int>> matrix1,matrix2;
    int numEnclaves(vector<vector<int>>& grid) {
        m=grid.size();n=grid[0].size();
        matrix1=grid;matrix2.resize(m,vector<int> (n,0));
        for (int i=0;i<m;i++)
        {
            if (matrix1[i][0]) dfs(i,0);
            if (matrix1[i][n-1]) dfs(i,n-1);
        }
        for (int j=0;j<n;j++)
        {
            if (matrix1[0][j]) dfs(0,j);
            if (matrix1[m-1][j]) dfs(m-1,j);
        }
        int num1=0,num2=0;
        for (int i=0;i<m;i++)
            for (int j=0;j<n;j++)
            {
                if (matrix1[i][j]) num1+=1;
                if (matrix2[i][j]) num2+=1;
            }
        return num1-num2;
    }
    void dfs(int x,int y)
    {
        if (matrix2[x][y]) return;
        matrix2[x][y]=1;
        if (x-1>=0 and matrix1[x-1][y]==1)
            dfs(x-1,y);
        if (x+1<m and matrix1[x+1][y]==1)
            dfs(x+1,y);
        if (y-1>=0 and matrix1[x][y-1]==1)
            dfs(x,y-1);
        if (y+1<n and matrix1[x][y+1]==1)
            dfs(x,y+1);
    }
};

太简单了,就不做了。

广度优先搜索
1765. 地图中的最高点
class Solution {
public:
    int m,n;
    vector<vector<int>> matrix;
    vector<vector<int>> highestPeak(vector<vector<int>>& isWater) {
        m=isWater.size();n=isWater[0].size();
        matrix.resize(m,vector<int> (n,0));
        queue<pair<int,int>> dl;
        for (int i=0;i<m;i++)
            for (int j=0;j<n;j++)
                if (isWater[i][j])
                    dl.push({i,j});
        while (!dl.empty())
        {
            pair<int,int> temp=dl.front();dl.pop();
            int x=temp.first;int y=temp.second;
            if (x-1>=0 and isWater[x-1][y]==0 and (matrix[x-1][y]==0 or matrix[x-1][y]>matrix[x][y]+1))
            {
                matrix[x-1][y]=matrix[x][y]+1;dl.push({x-1,y});
            }
            if (x+1<m and isWater[x+1][y]==0 and (matrix[x+1][y]==0 or matrix[x+1][y]>matrix[x][y]+1))
            {
                matrix[x+1][y]=matrix[x][y]+1;dl.push({x+1,y});
            }
            if (y-1>=0 and isWater[x][y-1]==0 and (matrix[x][y-1]==0 or matrix[x][y-1]>matrix[x][y]+1))
            {
                matrix[x][y-1]=matrix[x][y]+1;dl.push({x,y-1});
            }
            if (y+1<n and isWater[x][y+1]==0 and (matrix[x][y+1]==0 or matrix[x][y+1]>matrix[x][y]+1))
            {
                matrix[x][y+1]=matrix[x][y]+1;dl.push({x,y+1});
            }
        }
        return matrix;
    }
};

太简单了,就不做了。
补充:深度优先树、广度优先树。邻接矩阵时间复杂度 O ( n 2 ) O(n^2) O(n2)、邻接表时间复杂度 O ( n + e ) O(n+e) O(n+e)。DFS——Depth First Search。BFS——Breadth First Search。

并查集

模板
#include<vector>
using namespace std;
vector<int> parent;
void init(int n)
{
    for (int i=0;i<n;i++)
        parent.push_back(i);
}
int find(int x)
{
    if (x!=parent[x]) parent[x]=find(parent[x]);
    return parent[x];
}
void unite(int x,int y)
{
    parent[find(x)]=find(y);
}
bool same(int x,int y)
{
    return find(x)==find(y);
}
765. 情侣牵手
class Solution {
public:
    vector<int> parent;
    void init(int n)
    {
        for (int i=0;i<n;i++)
            parent.push_back(i);
    }
    int find(int x)
    {
        if (x!=parent[x]) parent[x]=find(parent[x]);
        return parent[x];
    }
    void unite(int x,int y)
    {
        parent[find(x)]=find(y);
    }
    int minSwapsCouples(vector<int>& row) {
        init(row.size()/2);
        for (int i=0;i<row.size();i+=2)
            unite(row[i]/2,row[i+1]/2);
        int count=0;
        for (int i=0;i<row.size()/2;i++)
            if (i==find(i))
                count+=1;
        return row.size()/2-count;
    }
};

最小生成树

Prim算法

题面描述:使用Prim算法求n个结点无向有权图的最小生成树总费用。

#include<iostream>
#include<unordered_set>
#include<vector>
int main()
{
    using namespace std;
    int n;cin>>n;
    int * * matrix=new int * [n];
    for (int i=0;i<n;i++)
    {
        matrix[i]=new int [n];
        for (int j=0;j<n;j++)
            cin>>matrix[i][j];
    }
    unordered_set<int> jh1,jh2,jh3;vector<int> dist(n-1,-1);
    jh1.insert(n-1);for (int i=0;i<n-1;i++) jh2.insert(i);
    int now_node=n-1;
    int result=0;
    for (int iter=0;iter<n-1;iter++)
    {
        vector<int> temp;
        for (auto zz=jh2.begin();zz!=jh2.end();zz++)
            if (matrix[now_node][*zz]!=0)
                temp.push_back(*zz);
        for (int i=0;i<temp.size();i++)
            if (dist[temp[i]]==-1) dist[temp[i]]=matrix[now_node][temp[i]];
            else if (dist[temp[i]]>matrix[now_node][temp[i]]) dist[temp[i]]=matrix[now_node][temp[i]];
        for (int i=0;i<temp.size();i++)
            if (jh3.find(temp[i])==jh3.end())
                jh3.insert(temp[i]);
        int next_node=*jh3.begin();
        for (auto zz=jh3.begin();zz!=jh3.end();zz++)
            if (dist[*zz]<dist[next_node])
                next_node=*zz;
        jh1.insert(next_node);jh2.erase(next_node);jh3.erase(next_node);
        result+=dist[next_node];now_node=next_node;
    }
    cout<<result;
    for (int i=0;i<n;i++)
        delete [] matrix[i];
    return 0;
}
Kruskal算法

题面描述:使用Kruskal算法求n个结点无向有权图的最小生成树总费用。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> parent;
void init(int n)
{
    for (int i=0;i<n;i++)
        parent.push_back(i);
}
int find(int x)
{
    if (x!=parent[x]) parent[x]=find(parent[x]);
    return parent[x];
}
bool same(int x,int y)
{
    return find(x)==find(y);
}
void unite(int x,int y)
{
    parent[find(x)]=find(y);
}
struct edge
{
    int ilink;
    int jlink;
    int value;
};
bool fc(const edge & x,const edge & y)
{
    if (x.value<y.value) return true;
    return false;
}
int main()
{
    int n;cin>>n;
    int * * matrix=new int * [n];
    for (int i=0;i<n;i++)
    {
        matrix[i]=new int [n];
        for (int j=0;j<n;j++)
            cin>>matrix[i][j];
    }
    vector<edge> lb;
    for (int i=0;i<n-1;i++)
        for (int j=i+1;j<n;j++)
            if (matrix[i][j])
                lb.push_back({i,j,matrix[i][j]});
    sort(lb.begin(),lb.end(),fc);
    init(n);
    int count=0;
    for (int i=0;i<lb.size();i++)
        if (!same(lb[i].ilink,lb[i].jlink))
        {
            unite(lb[i].ilink,lb[i].jlink);
            count+=lb[i].value;
        }
    cout<<count;
    for (int i=0;i<n;i++)
        delete [] matrix[i];
    return 0;
}

最短路径

Dijkstra算法

题面描述:在n个结点的有向有权图中求出指定结点到其余所有结点的最小距离。时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<iostream>
#include<vector>
#include<unordered_set>
using namespace std;
int find(const vector<int> & lb,const unordered_set<int> & jh)
{
    int node=-1;
    for (auto zz=jh.begin();zz!=jh.end();zz++)
        if (lb[*zz]!=-1)
        {
            if (node==-1) node=*zz;
            else if (lb[*zz]<lb[node]) node=*zz;
        } 
    return node;
}
int main()
{
    int n,s;cin>>n>>s;
    int * * matrix=new int * [n];
    for (int i=0;i<n;i++)
    {
        matrix[i]=new int [n];
        for (int j=0;j<n;j++)
            cin>>matrix[i][j];
    }
    vector<int> lb(n,0);unordered_set<int> jh;
    for (int i=0;i<n;i++)
        if (i!=s)
        {
            if (matrix[s][i]) lb[i]=matrix[s][i];
            else lb[i]=-1;
            jh.insert(i);
        }
    for (int iter=0;iter<n-1;iter++)
    {
        int node=find(lb,jh);jh.erase(node);
        if (node==-1) break;
        for (int i=0;i<n;i++)
            if (i!=node and matrix[node][i])
            {
                if (lb[i]==-1) lb[i]=lb[node]+matrix[node][i];
                else if (lb[i]>lb[node]+matrix[node][i]) lb[i]=lb[node]+matrix[node][i];
            }
    }
    for (int i=0;i<n;i++)
        cout<<lb[i]<<" ";
    for (int i=0;i<n;i++)
        delete [] matrix[i];
    return 0;
}
Floyd算法

题面描述:在n个结点的有向有权图中求出所有任意两个结点之间的最短距离。时间复杂度 O ( n 3 ) O(n^3) O(n3)

#include<iostream>
#include<climits>
int main()
{
    using namespace std;
    int n;cin>>n;
    int * * matrix=new int * [n];
    for (int i=0;i<n;i++)
    {
        matrix[i]=new int [n];
        for (int j=0;j<n;j++)
        {
            int temp;cin>>temp;
            if (i==j) matrix[i][j]=temp;
            else matrix[i][j]=temp==0?INT_MAX:temp;
        }
    }
    for (int k=0;k<n;k++)
        for (int i=0;i<n;i++)
            for (int j=0;j<n;j++)
                if (i==j or i==k or j==k)
                    continue;
                else if (matrix[i][k]!=INT_MAX and matrix[k][j]!=INT_MAX and matrix[i][k]+matrix[k][j]<matrix[i][j])
                    matrix[i][j]=matrix[i][k]+matrix[k][j];
    for (int i=0;i<n;i++)
    {
        for (int j=0;j<n;j++)
            cout<<matrix[i][j]<<" ";
        cout<<endl;
    }
    for (int i=0;i<n;i++)
        delete [] matrix[i];
    return 0;
}

拓扑排序

#include<iostream>
#include<vector>
#include<stack>
int main()
{
    using namespace std;
    int n;cin>>n;
    int * * matrix=new int * [n];
    for (int i=0;i<n;i++)
    {
        matrix[i]=new int [n];
        for (int j=0;j<n;j++)
            cin>>matrix[i][j];
    }
    int * lb=new int [n];
    for (int i=0;i<n;i++)
        lb[i]=0;
    for (int i=0;i<n;i++)
        for (int j=0;j<n;j++)
            if (matrix[i][j])
                lb[j]+=1;
    vector<int> result;
    stack<int> z;
    for (int i=0;i<n;i++)
        if (!lb[i])
        {
            lb[i]=-1;z.push(i);
        }
    while (!z.empty())
    {
        int top=z.top();
        z.pop();result.push_back(top);
        for (int i=0;i<n;i++)
            if (matrix[top][i])
                lb[i]-=1;
        for (int i=0;i<n;i++)
            if (!lb[i])
            {
                lb[i]=-1;z.push(i);
            }
    }
    int count=0;
    for (int i=0;i<n;i++)
        count+=lb[i];
    if (count!=-n) cout<<"ERROR";
    else
    {
        for (int i=0;i<n;i++)
            cout<<result[i]<<" ";
    }
    for (int i=0;i<n;i++)
        delete [] matrix[i];
    delete [] lb;
    return 0;
}

关键路径

我要偷懒。
能做题就可以了吧。
使用顺序拓扑序列求出最早时间,使用逆序拓扑序列求出最晚时间,两个时间相同就是关键路径。
补充:DAG图、AOV——网(V表示活动)、AOE——网(E表示活动)。

第二部分

就是上面那些了吧。

第三部分

没有难题。

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值