参考: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表示活动)。
第二部分
就是上面那些了吧。
第三部分
没有难题。