【数据结构】图
图的存储
//邻接矩阵,即开一个二维数组
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]; //返回关键路径长度
}
来源:CSDN
作者:jingxingv
链接:https://blog.csdn.net/qq_41169447/article/details/104130625