定义1.2: 图
定义1.3: 网络
定义1.4: 入度
定义1.5:子图$G{'}=<V{'},E^{'}> G=<V,E>
定义1.6:简单图指没有重边和自环的图。有向无环图(DAG)指一个不包含环的有向图。完全图指每一对不同节点均恰有一条边连接彼此的图。连通图指如果一个无向图的任意一节点到其余剩余节点均只有一条通路的图。
定义1.7:连通图的生成树是包含图中全部顶点的一个极小连通子图。若图中顶点数为
其他概念详见参考资料数据结构:图论
邻接矩阵存储图的节点与边信息是最稳定的做法之一,但其缺点也相当的明显:限于二维数组的超大存储空间缺陷以及此方法对空间的巨额浪费,邻接矩阵一般情况只能满足1000个以下点的存储,超过此限度可能内存爆炸。
int connects[1001][1001];
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;//起点,终点,边权
connects[u][v]=connects[v][u]=w;//无向图,有向图只有一个
}
相较于邻接矩阵存储,链式邻接表存储更便于统计边的数目,时间复杂度是
struct nexts
{
int pos;
int data;
}
vector<nexts> connects[10001];
for(int i=1,u,v;i<=m;i++)
{
cin>>u>>v>>w;
connects[u].push_back({v,w});
connects[v].push_back({u,w});
}
静态建立的邻接表,时间复杂度为
相对中规中矩的数据存储结构。但有一个略微致命的缺陷,不采用起终节点为下标存储边会导致找不到同起点的所有边的问题。因此,在前向星中添加一个
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100001;//点数最大值
int n, m, cnt;//n个点,m条边
struct Edge
{
int to, w, next;//终点,边权,同起点的上一条边的编号
}edge[maxn];//边集
int head[maxn];//head[i],表示以i为起点的第一条边在边集数组的位置(编号),初始化为-1.
void init()//初始化
{
for (int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
edge[cnt].to = v; //终点
edge[cnt].w = w; //权值
edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
head[u] = cnt++;//更新以u为起点上一条边的编号
}
int main()
{
cin >> n >> m;
int u, v, w;
init();//初始化
for (int i = 1; i <= m; i++)//输入m条边
{
cin >> u >> v >> w;
add_edge(u, v, w);//加边
/*
加双向边
add_edge(u, v, w);
add_edge(v, u, w);
*/
}
}
#include<bits/stdc++.h>
using namespace std;
vector<int> connect[10001];
bool visit[10001];
int n,e;
void dfs(int target)
{
cout<<target<<" ";
for(int i=0;i<connect[target].size();i++)
{
if(!visit[connect[target][i]])
{
visit[connect[target][i]]=1;
dfs(connect[target][i]);
}
}
}
int main()
{
cin>>n>>e;
for(int i=1,u,v;i<=e;i++)
{
cin>>u>>v;
connect[u].push_back(v);
connect[v].push_back(u);
}
visit[1]=true;
dfs(1);
system("pause");
return 0;
}
输出顺序会和上述方法略有不同。链式前向星式存储会按照每个节点输入边的倒序遍历。
比如说,你的输入顺序为
1 2
1 3
1 4
在邻接表或者邻接矩阵下,搜索的遍历顺序为2-3-4;而前向星为4-3-2.
#include <bits/stdc++.h>
using namespace std;
struct edge
{
int terminal;
int next;
int w;
};
int head[10001];
bool visit[10001];
edge connects[10001];
int m, n;
int ans = 0;
void add(int u, int v)
{
connects[ans].terminal = v;
connects[ans].next = head[u];
head[u] = ans++;
return;
}
void dfs(int start)
{
cout << start << " ";
visit[start] = true;
for (int i = head[start]; i != -1; i = connects[i].next)
if (!visit[connects[i].terminal])
{
visit[connects[i].terminal] = 1;
dfs(connects[i].terminal);
}
}
void init()
{
memset(head, -1, sizeof(head));
ans = 0;
}
int main()
{
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++)
{
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1);
cout<<endl;
system("pause");
return 0;
}
#include<bits/stdc++.h>
using namespace std;
struct point
{
int data;
vector<int>connect;
}s[10001];
bool visit[10001];
void bfs(point start)
{
queue<point> q;
q.push(start);
while(!q.empty())
{
point tem=q.front();
q.pop();
cout<<tem.data<<" ";
for(int i=0;i<tem.connect.size();i++)
{
if(!visit[tem.connect[i]])
{
visit[tem.connect[i]]=1;
q.push(s[tem.connect[i]]);
}
}
}
}
int main()
{
int n,e;
cin>>n>>e;
for(int i=1;i<=n;i++)
{
s[i].data=i;
}
for(int i=1,u,v;i<=e;i++)
{
cin>>u>>v;
s[u].connect.push_back(v);
s[v].connect.push_back(u);
}
visit[1]=true;
bfs(s[1]);
cout<<endl;
system("pause");
return 0;
}
#include <bits/stdc++.h>
using namespace std;
struct edge
{
int terminal;
int next;
int w;
};
int head[10001];
bool visit[10001];
edge connects[10001];
int m, n;
int ans = 0;
void add(int u, int v)
{
connects[ans].terminal = v;
connects[ans].next = head[u];
head[u] = ans++;
return;
}
void bfs(int u) /// 队列实现广度优先搜索
{
queue<int> q;
visit[u] = true;
q.push(u);
while (!q.empty())
{
int k = q.front();
q.pop();
cout << k << " ";
for (int i = head[k]; ~i; i = connects[i].next)
{
if (!visit[connects[i].terminal])
{
q.push(connects[i].terminal);
visit[connects[i].terminal] = true; /// 因为不是递归实现,所以每次放入队列后都需要立即标记。
}
}
}
}
void init()
{
memset(head, -1, sizeof(head));
ans = 0;
}
int main()
{
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++)
{
cin >> u >> v;
add(u, v);
add(v, u);
}
bfs(1);
system("pause");
return 0;
}
关于
邻接矩阵写法
#include<bits/stdc++.h>
using namespace std;
const int INF=0x7f7f7f;
int connects[1001][1001];
int dist[1001];
bool visit[1001];
int prim(int n)
{
dist[1]=0;//最小生成树,起点随意选,默认选一
int ans=0;
for(int i=1;i<=n;i++)
{
int location=-1;
for(int j=1;j<=n;j++)
{
if(!visit[j]&&(location==-1||dist[j]<dist[location]))
{
location=j;
}
}//从dist集合中选出要放入集合S的点(距离集合S最近的一个点)
if(dist[location]>=INF)return -1;//如果没有就剪枝,不可能存在。
ans+=dist[location];
visit[location]=true;
for(int k=1;k<=n;k++)
{
if(!visit[k])dist[k]=min(dist[k],connects[location][k]);
}
}
return ans;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
visit[i]=false;
dist[i]=INF;
for(int j=1;j<=n;j++)
{
connects[i][j]=INF;
}
}
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
connects[u][v]=w;
connects[v][u]=w;
}
int sum=prim(n);
if(sum==-1)cout<<"impossible"<<endl;
else cout<<sum<<endl;
}
system("pause");
return 0;
}
邻接表写法
#include<bits/stdc++.h>
using namespace std;
struct connect
{
int pos;
int w;
};
vector<connect> connects[10001];
int dist[10001];
bool visit[10001];
const int INF=0x7f7f7f;
int Prim(int n)
{
dist[1]=0;
int ans=0;
for(int i=1;i<=n;i++)
{
int location=-1;
for(int j=1;j<=n;j++)
{
if(!visit[j]&&(location==-1||dist[j]<dist[location]))
location=j;
}
if(dist[location]>=INF)return -1;
ans+=dist[location];
visit[location]=true;
for(int j=0;j<connects[location].size();j++)
{
if(!visit[connects[location][j].pos])dist[connects[location][j].pos]=min(dist[connects[location][j].pos],connects[location][j].w);
}
}
return ans;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
visit[i]=false;
dist[i]=INF;
}
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
connects[u].push_back({v,w});
connects[v].push_back({u,w});
}
int sum=Prim(n);
if(sum==-1)cout<<"impossible"<<endl;
else cout<<sum<<endl;
system("pause");
}
链式前向星
#include <bits/stdc++.h>
using namespace std;
struct nexts
{
int terminal;
int next;
int w;
} connects[10001];
bool visit[10001];
int head[10001];
int cnt;
int n, m;
int dist[10001];
void initset()
{
cnt = 1;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w)
{
connects[cnt].terminal = v;
connects[cnt].next = head[u];
connects[cnt].w = w;
head[u] = cnt++;
}
int Prim(int n)
{
int sum = 0;
dist[1] = 0;
for (int i = 1; i <= n; i++)
{
int location = -1;
for (int j = 1; j <= n; j++)
{
if (!visit[j] && (location == -1 || dist[j] < dist[location]))
location = j;
}
if (dist[location] >= 0x7f7f7f)
return -1;
visit[location] = true;
sum += dist[location];
for (int ij = head[location]; ij!= -1; ij = connects[ij].next)
{
if(!visit[connects[ij].terminal])dist[connects[ij].terminal]=min(dist[connects[ij].terminal],connects[ij].w);
}
}
return sum;
}
int main()
{
cin >> n >> m;
initset();
for (int i = 1, u, v, w; i <= m; i++)
{
cin >> u >> v >> w;
addedge(u, v, w);
addedge(v, u, w);
}
memset(dist, 0x7f7f7f, sizeof(dist));
int sum = Prim(n);
if (sum == -1)
cout << "impossible" << endl;
else
cout << sum << endl;
system("pause");
}
/*
测试样例
7 12
1 2 23
1 7 36
1 6 28
2 7 1
6 7 25
2 3 20
6 5 17
7 3 4
7 4 9
7 5 16
3 4 15
5 4 3
*/
采用并查集和类似链式前向星的数据结构维护的最小生成树。
并查集相关知识敬请参见22232程序设计原理·数据结构扩展
#include<bits/stdc++.h>
using namespace std;
struct information
{
int x,y,w;
}point[10001];
bool compare(information a,information b)
{
return a.w<b.w;
}
int father[10001];
int getfather(int x)
{
if(x!=father[x])father[x]=getfather(father[x]);
return father[x];
}
int calc,p,q,result;
int m,s;
void build()
{
for(int i=1;i<=s;i++)
{
p=getfather(point[i].x);
q=getfather(point[i].y);
if(p==q)continue;//如果p和q的父亲节点相同,证明p和q均已经被取用并入同一集合S
father[q]=p;//并入集合S
result+=point[i].w;
calc++;//计数器,剪枝
if(calc==m-1)break;
}
}
int main()
{
int n;
cin>>n;
while(n--)
{
calc=p=q=result=0;
cin>>m>>s;
for(int i=1;i<=m;i++)father[i]=i;
for(int i=1,u,v,l;i<=s;i++)
{
cin>>u>>v>>l;
point[i].x=u;point[i].y=v;point[i].w=l;
}
sort(point+1,point+s+1,compare);
build();
if(calc==m-1)cout<<result<<endl;
else cout<<"-1"<<endl;
}
system("pause");
return 0;
}
迪杰斯特拉算法和
#include<bits/stdc++.h>
using namespace std;
bool visit[1001];
int prefix[1001];
int connects[1001][1001];
int dist[1001];
int n,m;
const int INF=0x7f7f7f;
void Dijkstra(int startpoint)//和Prim算法极其相似
{
dist[startpoint]=0;
prefix[startpoint]=startpoint;//前缀终点,结束路径输出
for(int i=1;i<=n;i++)
{
int location=-1;
for(int j=1;j<=n;j++)
{
if(!visit[j]&&(location==-1||dist[j]<dist[location]))
{
location=j;
}
}//找到当前从起点到终点距离最小的目标点,开始松弛操作
if(dist[location]>=INF)return;//目标点是INF无穷大证明已经没有可操作的点了,直接回退结束函数。
visit[location]=true;//location点进入集合S
for(int k=1;k<=n;k++)
{
if(!visit[k])dist[k]=min(dist[location]+connects[location][k],dist[k]);//对点的所有连接进行松弛操作。
if(dist[k]==dist[location]+connects[location][k])prefix[k]=location;//如果成功松弛,就改变其前缀。
}
}
}
void findway(int terminal)
{
stack<int> s;
int i=terminal;
s.push(i);
while(prefix[i]!=i)
{
s.push(prefix[i]);
i=prefix[i];
}
while(!s.empty())
{
cout<<s.top()<<" ";
s.pop();
}
cout<<endl;
return;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
visit[i]=false;
dist[i]=INF;
for(int j=1;j<=n;j++)
connects[i][j]=INF;
prefix[i]=i;
}
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
connects[u][v]=connects[v][u]=w;
}
Dijkstra(1);
for(int i=1;i<=n;i++)
cout<<dist[i]<<" ";
cout<<endl;
for(int i=1;i<=n;i++)
findway(i);
system("pause");
return 0;
}
/*
测试样例与结果:
6 9
1 3 5
1 4 30
2 1 2
2 5 8
3 6 7
3 2 15
5 4 4
6 4 10
6 5 18
0 20 5 22 28 12
1
1 3 2
1 3
1 3 6 4
1 3 2 5
1 3 6
*/
队列优化提速:
#include<bits/stdc++.h>
using namespace std;
const int INF=2147483647;
struct nexts
{
int pos;
int w;
};
vector<nexts> connects[100001];
struct nodes
{
int dist;
int location;
bool operator<(const nodes &x)const
{
return x.dist<dist;
}
};
priority_queue<nodes> q;
int n,m,dis[10001],s,visit[100001],father[100001];
void init(int n)
{
for(int i=1;i<=n;i++)
{
dis[i]=INF;
visit[i]=false;
father[i]=i;
connects[i].clear();
}
}
int Dijkstra_queue(int n,int start)
{
dis[start]=0;
q.push((nodes){dis[start],start});
while(!q.empty())
{
int location=q.top().location;
q.pop();
if(visit[location])continue;
visit[location]=1;
if(dis[location]>=INF)return -1;
int num=connects[location].size();
for(int i=0;i<num;i++)
{
int v=connects[location][i].pos,wv=connects[location][i].w;
if(!visit[v])
if(dis[v]>dis[location]+wv)
{
dis[v]=dis[location]+wv;
father[v]=location;
q.push({dis[v],v});
}
}
}
return 1;
}
void print(int terminal)
{
stack<int> s;
int i=terminal;
s.push(terminal);
while(father[i]!=i)
{
s.push(father[i]);
i=father[i];
}
while(!s.empty())
{
cout<<s.top()<<" ";
s.pop();
}
cout<<endl;
return;
}
int main()
{
cin >> n >> m >> s;
init(n);
for (int i = 1, u, v, w; i <= m; i++)
{
scanf("%d%d%d", &u, &v, &w);
connects[u].push_back({v, w});
// connects[v].push_back({u,w});
}
int sun = Dijkstra_queue(n, s);
if (sun == 1)
for (int i = 1; i <= n; i++)
cout << dis[i] << " ";
else
cout << -1;
cout << endl;
for (int i = 1; i <= n; i++)
print(i);
system("pause");
return 0;
}
每次松弛操作实际上是对相邻节点的访问,第
与
因为负权环可以无限制的降低总花费,所以如果发现第
对边集合
若存在边
所以松弛函数的作用,就是判断是否经过某个顶点,或者说经过某条边,可以缩短起点到终点的路径权值。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;//顶点最大数
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
int graph[maxn][maxn];//邻接矩阵存储图
int dis[maxn];//存储所有顶点到源点的距离。
void init(){
memset(dis,inf,sizeof(dis));
memset(graph,inf,sizeof(graph));
}
int bellman_ford(int S){
dis[S]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
//进行松弛操作
dis[j]=min(dis[i]+graph[i][j],dis[j]);
}
}
int flag=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
//进行松弛操作
if(dis[j]>dis[i]+graph[i][j]){
flag=1;
break;
}
}
}
return flag;
}
int main(){
while(cin>>n>>m){
int u,v,w;
init();
for(int i=0;i<m;i++){
cin>>u>>v>>w;
graph[u][v]=graph[v][u]=w;
}
int S,E;//起点与终点。
cin>>S>>E;
int result=bellman_ford(S);
if(result)cout<<"此存在负权回路,计算两点之间距离可能没有意义,输出的仅供参考"<<endl;
cout<<dis[E]<<endl;
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;//顶点最大数
const int M=1e4;//边最大数
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
int cnt;//递增存储边。
typedef struct Edge{
int u;//边起点
int v;//边的终端节点
int w;//边权值。
Edge(){};
Edge(int u,int v,int w):u(u),v(v),w(w){}//构造函数
}Edge;
Edge edge[M];//前向星存储
int dis[maxn];//存储所有顶点到源点的距离。
void init(){
memset(dis,inf,sizeof(dis));
}
int bellman_ford(int S){
dis[S]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<cnt;j++){
//进行松弛操作,不断接近真实最短路径
if(dis[edge[j].v]>dis[edge[j].u]+edge[j].w){
dis[edge[j].v]=dis[edge[j].u]+edge[j].w;
}
}
}
int flag=0;
for(int j=0;j<cnt;j++){
//如果还能进行松弛操作,说明存在负权回路。
if(dis[edge[j].v]>dis[edge[j].u]+edge[j].w){
flag=1;
break;
}
}
return flag;
}
int main(){
while(cin>>n>>m){
int u,v,w;
init();
cnt=0;
for(int i=0;i<m;i++){
cin>>u>>v>>w;
edge[cnt].u=u;edge[cnt].v=v;edge[cnt].w=w; //这里是针对无向图。一定要注意的是,无向图中有负边的话一定存在负权回路,而负权回路是没有意义的。
cnt++;
edge[cnt].u=v;edge[cnt].v=u;edge[cnt].w=w;
cnt++;
}
int S,E;//起点与终点。
cin>>S>>E;
int result=bellman_ford(S);
if(result)cout<<"此存在负权回路,计算两点之间距离可能没有意义,输出的仅供参考"<<endl;
cout<<dis[E]<<endl;
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;//顶点最大数
const int M=1e4;//边最大数
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
typedef struct Edge{
int to;//边的终端节点
int w;//边权值。
Edge(int to,int w):to(to),w(w){}//构造函数
}Edge;
vector<Edge> graph[maxn];//用vector模拟邻接表
int dis[maxn];//存储所有顶点到源点的距离。
void init(){
memset(dis,inf,sizeof(dis));
}
int bellman_ford(int S){
dis[S]=0;
int t;
int temp,len;
for(int i=1;i<=n;i++){
t=graph[i].size();
for(int j=0;j<t;j++){
temp=graph[i][j].to;
len=graph[i][j].w;
if(dis[temp]>dis[i]+len)
dis[temp]=dis[i]+len;
}
}
int flag=0;
//判断是否存在负环。
for(int i=1;i<=n;i++){
t=graph[i].size();
for(int j=0;j<t;j++){
temp=graph[i][j].to;
len=graph[i][j].w;
if(dis[temp]>dis[i]+len){
flag=1;
break;
}
}
}
return flag;
}
int main(){
while(cin>>n>>m){
int u,v,w;
init();
for(int i=0;i<m;i++){
cin>>u>>v>>w;
graph[u].push_back(Edge(v,w));//这里针对的是无向图,有向图加边只加一次。
graph[v].push_back(Edge(u,w));
}
int S,E;//起点与终点。
cin>>S>>E;
int result=bellman_ford(S);
if(result)cout<<"此存在负权回路,计算两点之间距离可能没有意义,输出的仅供参考"<<endl;
cout<<dis[E]<<endl;
}
return 0;
}
算法实现:
#include <bits/stdc++.h>
using namespace std;
int n, m, s;
struct nexts
{
int pos;
int w;
};
vector<nexts> connects[100001];
int dist[100001], cnt[100001];
const int INF = 2147483647;
bool visit[100001];
void init(int n)
{
for (int i = 1; i <= n; i++)
{
visit[i] = false;
dist[i] = INF;
connects[i].clear();
cnt[i] = 0;
}
}
int spfa_slf(int n, int start)
{
deque<int> q;
dist[start] = 0;
q.push_back(start);
cnt[start]++;
visit[start] = true;
while (!q.empty())
{
int location = q.front();
q.pop_front();
visit[location] = false;
int t = connects[location].size();
for (int i = 0; i < t; i++)
{
int v = connects[location][i].pos;
int w = connects[location][i].w;
if (dist[v] > dist[location] + w)
{
dist[v] = dist[location] + w;
if (!visit[v])
{
if (!q.empty() && dist[v] <= dist[q.front()])
q.push_front(v);
else
q.push_back(v);
cnt[v]++;
if (cnt[v] > n)
return -1;
visit[v] = true;
}
}
}
}
return 1;
}
int main()
{
cin >> n >> m >> s;
init(n);
for (int i = 1, u, v, w; i <= m; i++)
{
cin >> u >> v >> w;
connects[u].push_back({v, w});
}
int change = spfa_slf(n, s);
if (change == 1)
for (int i = 1; i <= n; i++)
cout << dist[i] << " ";
cout << endl;
system("pause");
return 0;
}
请我喝杯咖啡吧~