最近在整理图论的算法。并且做了一些基础的题来练习,现在做一个总结,然后准备进入下一类算法的复习。
算法这个东西,就是不要害怕去编,哪怕自己只是有一点点理解,有好多点的模糊, 找一道基础的题, 对应着有一种思路解答的,去学习代码,学习里面的思路,并且自己动手跟着敲一敲,慢慢的就会理解了。 不用想着一开始就能够很彻底的理解算法的细节上思想。 理解的时候,从它所要达到的目的去思考, 知道它是要做一个什么事情,这是第一步。后面慢慢的多去练,就会更加深刻的理解里面的思想了,包括细节部分, 也会一点一点也清晰了。
感觉最近也总是提不起劲来做事情,果然是之前的大项目让自己疲惫了么,让自己消耗了太多脑细胞了咩。
噢哟哟, 想来这种情况在未来也可能是常态的, 我也得平时留心注意找一个能够让自己放松休息的方法, 劳逸结合, 这句话越来越觉得不错了。
古语真是古语,越品越有味道。
现在连最喜欢追的动漫,看起来都打不起精神来了。 学习底层的干劲也不是很强。 不过也是正常吧, 大脑在过度的使用之后,开始疲于思考, 或者说懒于思考了。
总之,这个月, 好好休息吧。
=================================================================================================================================
扯了一堆题外话,只在抒发一下自己的心情,很多事情要说出来才好。
首先总结两个图论算法,都是最小生成树算法。
prim算法:看一张图,简单明了。
prim算法的核心思想,就是以集合为中心,向外扩散。
分为两个集合, set1{}正在处理的最小生成树点 ,set2{}待处理的最小生成树点
每次从set2{}中寻找一个与该集合相邻接的,权值最小的点,加入set1{}, 直到set2{}中的点找完了。
下面是一个模板算法:算法是参考牙签大哥的。
模板使用说明:MAXN为定义的顶点最大可达数,n为图的大小,图以邻接表的形式存放在map中,调用prim函数后,最小需要花费的值为ret。
#define MAXN 101
int n, ret;
int map[MAXN][MAXN];
void prim()
{
int closet[MAXN];//set1{}集合的标记,标记为1的,表示加入到set1{}集合中
int dist[MAXN];//set2{}集合中的点到set1{}集合的最小距离,随着点的加入,要逐步更新
int i, j;
for ( i=0; i<n; i++)
{
closet[i] = 0;//将点都初始化为在set2{}集合中
dist[i] = map[0][i];//将以0号点v1为初始点,最小距离,设置为到v1的距离
}
closet[0] = 1;
int u, min;
for ( i=1; i<n; i++)//寻找剩下的n-2个点的最小距离的边
{
min = 100001;
for ( j=1; j<n; j++)//寻找一个set2{}集合的,到set1{}集合距离最小的点。
{
if ( ! closet[j] && dist[j] < min && dist[j] > 0)
{
u = j;
min = dist[j];
}
}
closet[u] = 1;//找到的点设置为set1{}集合
ret += min;//权值对应的增加
for ( j=1; j<n; j++)//依次更新各个点到set1{}集合的最小距离
{
if (! closet[j] && map[u][j] < dist[j] )
dist[j] = map[u][j];
}
}
}
Kruskal算法:依旧是一张图,简单明了。
Kruskal算法的核心,是以边的权值为出发点,从最小的权值边开始寻找,依次寻找边所依赖的两个点在不同的联通分量的点,直到最后构成n-1条边组成的最小生成树。
再上一个算法模板,
模板使用方法:vn表示图中结点个数,dis用邻接表的形式表示一幅图,dis[i][j]为0时表示i点和j点不连通,最后所需耗费的最小值存于sum中。
#include <stdio.h>
#include <queue>
using namespace std;
#define Maxv 100+5
typedef struct
{
int v1,v2,len;
}Node;//节点的结构体,边的两个点,和边的权值
bool operator > (Node a,Node b)//设置优先队列的排列条件
{
if( a.len > b.len )
return true;
return false;
}
int dis[Maxv][Maxv];//图的邻接表
int fa[Maxv];//对应点的父亲
int Getfa(int i)//寻找点i的父亲节点, 并且更新点i的父亲节点
{
if( fa[i] == i ) return i;
fa[i] = Getfa(fa[i]);
return fa[i];
}
int main()
{
int sum;
priority_queue< Node,vector<Node>,greater<Node> > Q; //返回最小数
int vn,i,j;
while( scanf( "%d" , &vn ) != EOF )
{
//输入图部分
for( i = 1 ; i <= vn ; i++ )
for( j = 1 ; j <= vn ; j++ )
scanf( "%d" , &dis[i][j] );
for( i = 1 ; i <= vn ; i++ ) fa[i] = i;
while( !Q.empty() ) Q.pop();//清空队列的点
//把每条边压入堆中
for( i = 1 ; i < vn ; i++ )
for( j = i+1 ; j <= vn ; j++ )
if( dis[i][j] )
{
Node e;
e.v1 = i , e.v2 = j , e.len = dis[i][j];
Q.push(e);
}
sum = 0;
while( Q.size() != 0 )//从最小边权值开始出发寻找,满足条件的边
{
Node e;
e = Q.top();//去除最小值
Q.pop();
if( Getfa(e.v1) != Getfa(e.v2) )//如果边的两个点的父亲不同,即属于不同的联通分量,就连接这两个点
{
sum += e.len;
fa[Getfa(e.v2)] = Getfa(e.v1);//v2的父亲的父亲 等于 v1的父亲,将v1,v2 连接起来了
}
}
printf( "%d\n" , sum ); //所需的最小值
}
return 0;
}
这里比较巧妙的是使用了Getfa这个函数,能够高效的判断两个点的联通分量,并且更新点所在的联通分量。
还使用了STL里面的一个非常方便的priority_queue。这里有它的详细使用方法,很好用的。
http://www.cnblogs.com/flyoung2008/articles/2136485.html
==================================================================================================================================
接下来,就是我练习的题目的总结,解答仅供参考。
stage1:
畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1232)
这道题目我在开始的时候考虑少了一种情况。
我在开始是这样考虑的。
设置两个集合set1(满足联通的点), set2(待分析的点)。
对于每对输入的点,如果两个点,有一个以上的点在set1中,则不用增加边将它们联通;
如果两个点都不在set1中,则需要增加一个边将它们联通。
这个逻辑表面上看起来没有错,其实有一个错误:后面扫描边的时候,有两个点都在set1中,但是这两个点是在处理上述第二种情况的时候我自己加进去的,那么就会多出一条边。就是因为这个原因,我总是无法AC。后来多块CSDN的朋友,才得以解决。感谢CSDN的朋友!
下面上代码:
#include <stdio.h>
int city[1005];//用来记录点所在的集合,或者说联通分量
int main()
{
int n,m;
int count=0;
while(1)
{
scanf("%d",&n);
if( n == 0)
{
break;
}
scanf("%d",&m);
int i,a,b,temp,j;
int unionIndex = 0;
int unionNum = 0;
for ( i=1; i<=n; i++ ) city[i] = -1;//2、城市编号从1开始
for ( i=0; i<m; i++ )
{
scanf("%d%d",&a,&b);
if ( a == b ) continue;
if ( (city[a] == -1) && ( city[b] == -1 ) )
{
city[a] = unionIndex;
city[b] = unionIndex++;
unionNum++;
}
else if ( (city[a] != -1) && ( city[b] == -1 ) )
{
city[b] = city[a];
}
else if ( (city[a] == -1) && ( city[b] != -1 ) )
{
city[a] = city[b];
}
//缺少对后期连接了两个集合的判断, 可以减少独立集合数
//1、缺少对 输入的两个数本身就是一个集合的判断,这时不需要减少独立集合数
else if ( (city[a] != city[b]) && (city[a] != -1) && ( city[b] != -1 ) )
{
//对所有等于a集合标志的元素,归并成b集合标志的元素
temp = city[a];
for ( j=1; j<=n; j++ )//this range !!
if ( city[j] == temp ) city[j] = city[b];
unionNum--;
}
}
for ( i=1; i<=n; i++ )
{
if ( city[i] == -1 ) unionNum++;
}
//
//到目录为止一共有unionNum个联通集,则至少需要再添unionNum-1条路
printf( "%d\n", unionNum-1 );
}
return 0;
}
这个方法比较笨,但是也比较有效,也比较直接。我在自己处理的时候,也会先考虑从自己解决问题的角度去设计解决方法,而不是直接就套用算法之类的。
但随着到后期,相信很多算法,会内化成为我自然而然使用的设计方法。
stage2:
还是畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1233)这里我依旧是从自己的考虑出发,凭借脑海中模糊的prim算法思想,构造出的代码,一次就ac成功了。仅供参考。
#include <stdio.h>
int map[102][102];//city start from 1
int rea_len=0;
int city[102];
int main()
{
int n,j,i;
while(scanf("%d",&n) && n)
{
int x,y,dis;
//init the map
for(i=1 ; i < 102 ; i++)
for(j=1 ; j<102 ; j++)
{
map[i][j] = 9999;
}
for(i=1 ; i<102 ;i++)
{
city[i]= 0;
}
rea_len=n;
city[1] = 1;
//input the city distance data
for(i=0 ; i< n*(n-1)/2 ; i++)
{
scanf("%d%d%d",&x,&y,&dis);
map[x][y] = dis;
map[y][x] = dis;
}
int k;
int sum=0;
while( --rea_len )
{
int min=9999;
for(i=1 ; i<=n; i++)
{
if( city[i] == 1)
for(j=1 ; j<=n ;j++)
{
if(i!=j && city[j] == 0 && (map[i][j] < min || map[j][i] < min) )
{
min = map[i][j];
k = j;
}
}
}
city[k] = 1;
sum += min;
}
printf("%d\n",sum);
}
return 0;
}
stage3:
畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1863)后面的解题,主要依靠Getfa函数,并且随着解题的深入,对于Getfa函数的理解也越来越深刻了。靠着Getfa函数,后面解题势如破竹。这个东西真是好用。
#include <stdio.h>
#include <queue>
using namespace std;
#define MAX 102
typedef struct
{
int x,y,cost;
}NODE;
bool operator > (NODE a, NODE b)
{
if( a.cost > b.cost ) return true;
return false;
}
//记录i点的父亲的值
int fa[MAX];
//寻找并且更新i点的最终父亲
int getfa(int i)
{
if( fa[i] == i) return i;
fa[i] = getfa( fa[i] );
return fa[i];
}
int main()
{
int n,m,i,j,sum;
priority_queue< NODE , vector<NODE>, greater<NODE> > Q;
while( scanf("%d",&n) && n)
{
while( !Q.empty() ) Q.pop();
sum=0;
scanf("%d",&m);
int x,y,cost;
NODE e;
while( n-- )
{
scanf("%d%d%d",&x,&y,&cost);
e.x = x;
e.y= y;
e.cost = cost;
Q.push(e);
}
for(i=1 ; i<=m ;i++) fa[i] = i;
while( Q.size() )
{
e = Q.top();
Q.pop();
if( getfa(e.x) != getfa(e.y) )
{
sum += e.cost;
fa[getfa(e.x)] = getfa(e.y);
}
}
for(i=1 ; i<m ;i++)
{
if( getfa(i) != getfa(i+1) )
{
printf("?\n");
break;
}
}
if( i == m)
{
printf("%d\n",sum);
}
}
return 0;
}
stage4:
欧拉回路(http://acm.hdu.edu.cn/showproblem.php?pid=1878)
这道基础题关键就是要分析出欧拉回路的特点:
判断一个图是否存在欧拉回路的充要条件是:1)每个点的度数为偶数,2)图是连通的
#include <stdio.h>
int fa[1003];
int count[1003];
int getfa(int i)
{
if(fa[i] == i) return i;
fa[i] = getfa( fa[i] );
return fa[i];
}
bool isOK(int n)//判断联通,以及节点度数是否为偶数
{
int i;
for(i=1 ;i<n ; i++)
{
if( getfa(i) != getfa(i+1) || count[i] %2 != 0)
return false;
}
if( count[i]%2 != 0) return false;
return true;
}
int main()
{
int n,m,i;
while( scanf("%d",&n) && n)
{
scanf("%d",&m);
int x,y;
for(i=1 ; i<=n ; i++)
{
fa[i] = i;
count[i] = 0;
}
while(m--)
{
scanf("%d%d",&x,&y);
count[x]++;
count[y]++;
// 将x父亲的父亲指定为 y 的父亲。从根源处将两点联通。
// x--x1--x2 y--y1--y2 , x2--y2
fa[ getfa(x)] = getfa(y);
}
if( isOK(n) )
printf("1\n");
else
printf("0\n");
}
return 0;
}
stage5:
继续畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1879)
//1.operator 操作数的使用方法
//2.priority_queue 的定义方法
#include <stdio.h>
#include <queue>
using namespace std;
typedef struct
{
int x,y,cost;
}NODE;
int fa[103];
int getfa(int i)
{
if( fa[i] == i) return i;
fa[i] = getfa( fa[i]);
return fa[i];
}
bool operator > (NODE a, NODE b)
{
if(a.cost > b.cost ) return true;
return false;
}
int main()
{
int n,i,sum;
priority_queue< NODE, vector<NODE>, greater<NODE> > Q;
while( scanf("%d", &n) && n)
{
while( !Q.empty() ) Q.pop();
for(i=1 ;i<=n; i++)
{
fa[i] = i;
}
sum=0;
int x,y,cost,isbuild;
NODE e;
for(i=1 ;i<= n*(n-1)/2 ; i++)
{
scanf("%d%d%d%d",&x ,&y, &cost, &isbuild);
if( isbuild == 1)
{
fa[ getfa(x)] = getfa(y);
continue;
}
e.x = x;
e.y = y;
e.cost = cost;
Q.push(e);
}
//由于没有一个比较有效的实时判断图是否已经连通的函数
//因此,将队列中所有的点都判断一遍,以做到在连通的情况下
//计算出最小成本
while( Q.size() )
{
e = Q.top();
Q.pop();
if( getfa(e.x) != getfa(e.y) )
{
fa[ getfa(e.x)] = getfa(e.y);
sum += e.cost;
}
}
printf("%d\n",sum);
}
return 0;
}
感谢牙签,感谢wxspll。如果有什么问题,欢迎讨论。