本文来自《挑战程序设计竞赛》2.5 它们其实都是图
1.图的搜索
1.题目原文:
二分图判定。给定一个具有n个顶点的图,要给图上每个顶点染色,并且要使相邻的顶点颜色不同。问是否能最多用两种颜色进行染色。题目保证没有重边和自环。
1<=n<=1000
2.分析:
3.代码及样例
/*
3
0 1
0 2
1 2
-1 -1
NO
4
0 1
0 3
1 2
2 3
-1 -1
YES
*/
#include <iostream>
#include<vector>
#include<cstring>
using namespace std;
#define maxv 1000
vector<int> G[maxv];//图
int V;//顶点数
int color[maxv];//顶点i的颜色
//将顶点染成1或-1
bool dfs(int v,int c)
{
color[v]=c;//把顶点v染成颜色c
for(int i=0;i<G[v].size();i++){
int u=G[v][i];
//相邻顶点同色,返回false
if(color[u]==c) return false;
//相邻顶点还未染色,染成-c
if(!color[u]&&!dfs(u,-c)) return false;
}
//如果所有的顶点都染过色了,则返回true
return true;
}
void solve()
{
for(int i=0;i<V;i++){
if(color[i]==0){
//顶点i未被染色,染成1
if(!dfs(i,1)){
cout<<"NO\n"<<endl;
return;
}
}
}
cout<<"YES\n"<<endl;
}
int main()
{
cin>>V;
int u,v;
cin>>u>>v;
while(u>=0&&v>=0){
G[u].push_back(v);
G[v].push_back(u);
cin>>u>>v;
}
memset(color,0,sizeof(color));
solve();
return 0;
}
2.最短路径问题
最短路是给定两个顶点,在以这两个顶点为起点和终点的路径中,边的权值之和最小的路径。智力游戏中的最小步数,也可以看作最短路径问题。
1.单源最短路径1(Bellman-Ford算法)
1.算法原理
记从起点s出发到顶点i的最短距离为d[i],则有d[i]=min{d[j]+(从j到i的边的权值)|e=(j,i)属于E}
如果给定的图是DAG,就可以按拓扑序给顶点编号,按照上述递推关系式计算出d。但是图中如果有圈,就不可以依赖这样的顺序计算。在这种情况下,设初值d[s]=0,d[i]=INF(足够大的常数),在不断利用这个递推关系式就可以解决。只要图中不存在负圈,操作就是有限的。
2.代码及样例:
/*
5 10
0 1 6
0 3 7
1 2 5
1 3 8
1 4 -4
2 1 -2
3 2 -3
3 4 9
4 0 2
4 2 7
0 2 4 7 -2
*/
#include <iostream>
#include<vector>
#include<cstring>
using namespace std;
#define maxn 1000
#define INF 0x7fffffff
int V,E;
struct edge
{
int from;
int to;
int cost;
};
edge es[maxn];//边
int d[maxn];//最短距离
//求解从顶点s出发到所有顶点的最短距离
void shortest_path(int s)
{
for(int i=0;i<V;i++){
d[i]=INF;
}
d[s]=0;
while(true){
bool update=false;
for(int i=0;i<E;i++){
edge e=es[i];
int u=e.from,v=e.to;
if(d[u]!=INF&&d[v]>d[u]+e.cost){
d[v]=d[u]+e.cost;
update=true;
}
}
if(!update) break;
}
}
int main()
{
cin>>V>>E;
for(int i=0;i<E;i++){
int u,v,c;
cin>>u>>v>>c;
es[i]=(edge){u,v,c};//这个感觉好6,以前没见过……
}
shortest_path(0);
for(int i=0;i<V;i++){
cout<<d[i]<<" ";
}
cout<<endl;
return 0;
}
3.时间复杂度分析
4.负圈检查
//检查是否存在负圈
bool find_negative_loop()
{
memset(d,0,sizeof(d));
for(int i=0;i<V;i++){
for(int j=0;j<E;j++){
edge e=es[i];
if(d[e.to]>d[e.from]+e.cost){
d[e.to]=d[e.from]+e.cost;
//如果第n次仍然更新了,则存在负圈
if(i==V-1) return false;
}
}
}
return true;
}
3.最小生成树
2.Kruscal算法
1.算法原理
Kruscal算法按照边的权值的顺序从小到大查看一遍,如果不产生圈,就把这条边加入到生成树中。
现在介绍如何判断是否产生边。假设现在要把连接顶点u和v的边e加入到生成树中,如果之前u和v不在同一个连通分量中,那么加入e不会产生圈,否则会。可以用并查集高效地判断是否属于同一个连通分量。
Kruscal算法在边的排序上最花时间,算法的时间复杂度为O(ElogV)。
2.代码模板
/*
9 14
0 1 4
0 7 8
1 2 8
1 7 11
2 3 7
2 5 4
2 8 2
3 4 9
3 5 14
4 5 10
5 6 2
6 7 1
6 8 6
7 8 7
37
*/
#include<iostream>
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define maxn 1005
int par[maxn];//父亲
int Rank[maxn];//树的高度
//初始化
void init(int n)
{
for(int i=0;i<n;i++){
par[i]=-1;
Rank[i]=0;
}
}
//查询父亲,路径压缩
int Find(int x)
{
int s;
for(s=x;par[s]!=-1;s=par[s]);
while(s!=x){
int temp=par[x];
par[x]=s;
x=temp;
}
return s;
}
//合并x,y属于的集合
void unite(int x,int y)
{
x=Find(x);y=Find(y);
if(x==y) return;
if(Rank[x]<Rank[y]){
par[x]=y;
}
else{
par[y]=x;
if(Rank[x]==Rank[y]) Rank[x]++;
}
}
bool same(int x,int y)
{
return Find(x)==Find(y);
}
struct edge
{
int from;
int to;
int cost;
} ;
bool cmp(const edge& e1,const edge& e2)
{
return e1.cost<e2.cost;
}
int V,E;
edge es[maxn];
int kruscal()
{
sort(es,es+E,cmp);//按照edge.cost从小到大的顺序进行排序
init(V);
int res=0;
for(int i=0;i<E;i++){
edge e=es[i];
if(!same(e.from,e.to)){
unite(e.from,e.to);
res+=e.cost;
}
}
return res;
}
int main()
{
cin>>V>>E;
for(int i=0;i<E;i++){
int u,v,c;
cin>>u>>v>>c;
es[i]=(edge){u,v,c};
}
printf("%d\n",kruscal());
return 0;
}

本文深入讲解图论中的三种核心算法:二分图判定、最短路径(Bellman-Ford算法)与最小生成树(Kruskal算法)。通过具体实例演示算法实现过程,帮助读者掌握这些算法的工作原理及应用场景。
1239

被折叠的 条评论
为什么被折叠?



