【图论】大总结★★★★★

来源:互联网 发布:攻城掠地神兵锻造数据 编辑:程序博客网 时间:2024/06/12 00:07
因博客搬家,图片无法查看,具体见:http://lydws.blog.163.com/blog/static/22621105120147464018957/

一、图结构
图结构包括:
图涉及到的概念:
边的权
点的度
二、图的储存结构
n
①邻接矩阵(O(n*n))

cin>>N;
memset(map,10,sizeof(map));
for(int i=1;i<=N;i++)
{
int x,y,v;
cin>>x>>y>>v;
map[x][y]=map[y][x]=v;
}


②邻接表(O(|E|))
原理如图:
【图论】大总结★★★★★ - 哭,等谁回眸 - 哭,等谁回眸

struct edge
{
int y,v,next;/* y表示这条边的终点编号,v是权值,next表示同起点下条边的编号是多少*/
}e[MAXN];
int linkk[...],N,M,t=0
void insert(int xx,int yy,int vv)
{
e[++t].next=linkk[xx];
linkk[xx]=t,e[t].y=yy,e[t].v=vv;
}
int main()
{
cin>>N>>M;
for(int i=1;i<=M;i++)
{
int x,y,v;
cin>>x>>y>>v;
insert(x,y,v);
}
}


③边表

struct bian/*适用于稀疏图*/
{
int x,y,v;
}e[MAXN];
int sum=0;
void init()
{
cin>>N;
for(int i=1;i<=N;i++)
{
int a,b,c;
cin>>a>>b>>c;
e[++sum].x=a,e[X].y=b,e[X].v=c;
e[++sum].x=b,e[X].y=a,e[X].v=c;
}
}


④前向星
我也不会,但可以被邻接表代替,不说了。

三、图的遍历
从图中某一顶点出发,按某种搜索方法访遍其余顶点,且使每一顶点仅被访问一次。这一过程称为图的遍历。
遍历图的基本搜索方法有两种:深度优先搜索DFS和广度优先搜索BFS。这两种方法都适用于有向图和无向图。
①dfs:

void dfs(int k){f[k]=0;for(int i=1;i<=N;i++)if(map[k][i]&&f[i])dfs(i);}


②bfs:

void bfs(int k){q[1]=k;int head=0,tail=1;while(head++<tail)for(int i=L[q[head]];i!=0;i=e[i].next)if(f[e[i].y])tail++,q[tail]=e[i].y,f[e[i].y]=0;}int main(){

ios::sync_with_stdio(false);init();for(int i=1;i<=N;i++)if(f[i]){bfs(i);sum++;}}


③欧拉路:
比较简单。
详情见:2007国家集训队论文--仇荣琦。

四、图的最短路径
属性:
【图论】大总结★★★★★ - 哭,等谁回眸 - 哭,等谁回眸
 
(1)三角形性质:设源点s到点x、y的最短路径长度为dis[x]、dis[y]。x与y之间的距离是len[x][y],则有下面的“三角形定理”:   dis[x] + len[x][y]  >=  dis[y] 
(2)松驰:若在处理过程中,有两点x、y出现不符合“三角形定理”,则可改进一下—松驰:
       if(dis[x]+len[x][y]<dis[y] )  dis[y] = dis[x]+len[x][y];
①每对顶点(任意两点)之间的最短路径——floyed(弗洛伊德算法)(O(n*n*n))
求每对节点之间最短距离问题:例如,求一个图的直径,即所有最短路径中最长的。
如果多次调用单源最短路径算法,效果并不好。特别是对有负边的图。
代码如下:

void floyed(){for (int k=1;k<=n;k++)for (int i=1;i<=n;i++)for (int j=1;j<=n;j++)d[i][j] = min( d[i][j],d[i][k]+d[k][j] );}


 ②一个顶点到其他顶点的最短路径(单源最短路径)——dijkstra(迪杰斯特拉算法) (O(n*n))
代码如下:

void dijkstra(int st){for(int i=1;i<=n;i++) dis[i]=a[st][i];memset(f,false,sizeof(f));f[st]=true; 

dis[st]=0;for(int i=1;i<n;i++){int min=1000000000,k=0;for(int j=1;j<=n;j++)if(!f[j]&&(dis[j]<min))min=dis[j],k=j;if(k==0) return;f[k]=true;for(int j=1;j<=n;j++)if(!f[j]&&(dis[k]+a[k][j]<dis[j])) dis[j]=dis[k]+a[k][j]; }}

注:dijkstra不能有负权回路。
③求单源点到其他点的最短路径,判断是否有负环——Bellman-ford算法 (O(N*E))
Bellman-ford算法N次迭代就可以判断图中是否有“负环”。
代码如下:

bool Bellman_ford(int st){memset(dis,10,sizeof(dis));dis[st]=0;bool f=0;for(int i=0;i<=N;i++){f=0;for(int j=1;j<=X;j++)if(dis[map[j].x]+map[j].v<dis[map[j].y]){dis[map[j].y]=dis[map[j].x]+map[j].v;f=1;}if(!f)return 0;//没有一次松弛,结束。}return 1;//迭代了N次,有负环。}


Bellman-ford算法迭代的改进——SPFA算法 (O(K*E))
Bellman-ford算法中,每次都要检查所有的边。这个看起来比较浪费,对于边(x,y),如果上一次dis[x]没有改变,则本次的检查显然是多余的。 
我们每次只要从上次刚被“松驰”过的点x,来看看x能不能松驰其它点即可。 
SPFA算法中用BFS中的队列来存放刚被“松驰”过的点。由于顶点个数为|V|,队列如果用数组的话显然要用“循环队列”使用空间。
代码如下:

void SPFA(){memset(d,10,sizeof(d));int head=0,tail=0;f[st]=1;d[st]=0;q[++tail]=st;while (head++<tail){for (int j=l[q[head]];j;j=e[j].next)if (d[e[j].y]>d[q[head]]+e[j].w){ d[e[j].y]=d[q[head]]+e[j].w; //松弛if (!f[e[j].y])q[++tail]=e[j].y,f[e[j].y]=1; //进队}f[q[head]]=0; //出队标记}


算法比较:
Dijkstra
单源     非负         O(|V|2)               对稠密图好
可用“堆”优化      O(|E|*log|V|)      对稀疏图较好

Bellman-Ford
单源     无负环     O(|V|*|E|)
可先用Dijkstra预处理优化
SPFA用更新队列优化,时间复杂度平均好,但不确定。

Floyd
全源     无负环      O(|V|2)

五、图的最小生成树
属性:       
(1)环属性:一棵生成树上,增加一条边e,再删除e所在环上的最大边,如果e不是最大边,则会得到另一棵更好的生成树。
(2)剪切属性:在图中,剪切将顶点划分成两个不相交集合。交叉边为这些顶点在两个不同集合的边。对于任何一个剪切,各条最小的交叉边都属于某个MST,且每个MST中都包含一条最小交叉边。
【图论】大总结★★★★★ - 哭,等谁回眸 - 哭,等谁回眸
 
算法原理:
(1)最小边原则:图中权值最小的边(如果唯一的话)一定在最小生成树上。
(2)唯一性:一棵生成树上,如果各边的权都不相同,则最小生成树是唯一的。反之不然。

①Prim(普里姆)算法
代码如下:

void init(){cin>>n;memset(a,10,sizeof(a));for(int i=1;i<=n;i++){ vis[i]=false;for(int j=1;j<=n;j++)cin>>a[i][j];}}void Prim(int s){memset(dis,10,sizeof(dis));for(int i=1;i<=n;i++)dis[i]=a[s][i];memset(vis,0,sizeof(vis));vis[s]=1;sumn=0;for(int i=2;i<=n;i++){int minn=a[0][0],c=0;for(int j=1;j<=n;j++)if((!vis[j])&&(dis[j]<minn)){minn=dis[j];c=j;}vis[c]=1;sumn+=minn;for(int j=1;j<=n;j++)if((!vis[j])&&(a[c][j]<dis[j]))dis[j]=a[c][j];}}

算法要点:
         每次求最小权交叉边时,如果都重新计算,则显然要枚举(x,y)--- x∈A ,y∈B。O(n^2)时间复杂度。   
         其实每次A中只是增加一个新顶点v,最多有交叉边(v,y),修改量只有与v有边的顶点,为O(n)。
         只需记录下B中的每个元素y与A所有元素中最小权边,则求最小值最多为O(n)---有时可以用“堆”优化。

②Kruskal(克鲁斯卡尔)算法
算法要点:
         Kruskal算法的最难点在于怎样判断加入边(x,y)后是否形成了环。
问题可化为:
         判断边(x,y)的两个顶点x,y在图(实际是森林)mst中是否已经连通。如果已经连通,加入边将形成环;否则,不形成环。
        连通点集之类问题,有高效算法---并查集!
代码如下:

void Kruskal(){for (int i=1;i<=n;i++)father[i]=i;sort(e+1,e+len+1,mycmp);int c=0;for (int i=1;i<=len;i++){int v=getfather(e[i].start);int u=getfather(e[i].end);if (v!=u){merge(v,u);sum+=e[i].v;if (++c==n-1)}}}


最小生成树(MST)-----时间复杂度
时间复杂度分析
Prim算法普通的方法
          O(|V|2)
  Prim算法中用“堆”方法
          O((|E|+|V|)*log|V|)          ---对稀疏图较好
Kruskal算法
        O(|E|*log|E| +|N|*A(|V|))  ---对稀疏图较好

六、拓扑排序
算法:
1.寻找入度为0的节点
2.将找到的节点放入队列中,删除所有这个节点引出的边
3.重复1,直至没有度为0的节点
4.如果有节点不在队列中,则说明原图中有环,否则无环。
代码如下:

void topsort(){head=tail=0;for(int i=1;i<=n;i++)if(id[i]==0) q[++tail]=i;while(head++<tail)for(int i=1;i<=n;i++)if(a[q[head]][i]){id[i]--;if(id[i]==0) q[++tail]=i;}}


以上是图论已学的所有内容。
/*版权所有,侵权必究*/





0 0