首页 > 代码库 > 树的问题小结(最小生成树、次小生成树、最小树形图、LCA、最小支配集、最小点覆盖、最大独立集)

树的问题小结(最小生成树、次小生成树、最小树形图、LCA、最小支配集、最小点覆盖、最大独立集)

树的定义:连通无回路的无向图是一棵树。

有关树的问题:

1、最小生成树。

2、次小生成树。

3、有向图的最小树形图。

4、LCA(树上两点的最近公共祖先)。

5、树的最小支配集、最小点覆盖、最大独立集。

 

一、最小生成树

解决的问题是:求无向图中边权值之和最小的生成树。

算法有KruskalPrim

Kruskal使用前向星和并查集实现,可以存储重边(平行边),时间复杂度是O(m log m  +  m)m是边的数量。

Prim使用邻接矩阵建图,不可以存储重边(平行边),如果出现重边,存储的是权值最小的那一条,时间复杂度为O(n*n), n是顶点的数量。使用邻接表建图可能会提高效率。

一般情况下,题目都是比较裸的。难度为易。

 模版:建好图以后,直接调用,输出。

prim

 1 const int maxn=101,INF=0x3f3f3f3f; 2 int dist[maxn],Map[maxn][maxn],pre[maxn]; 3 //向外延伸的最短边长,记录图信息,记录连接信息 4 bool p[maxn];//1表示点已经在树的,0表示点在树外 5 bool f[maxn][maxn]; 6 int Prim(int n) 7 { 8     int i,j,k,Min,ans=0; 9     for(i=2;i<=n;i++)10     {11         p[i]=0;12         dist[i]=Map[1][i];13         pre[i]=1;14     }15     dist[1]=0;16     p[1]=1;17     for(i=1;i<n;i++)18     {19         Min=INF;20         k=0;21         for(j=1;j<=n;j++)22         {23             if(!p[j]&&dist[j]<Min)24             {25                 Min=dist[j];26                 k=j;27             }28         }29         if(k==0) return -1;//G不连通30         ans+=Min;31         p[k]=1;32         for(j=1;j<=n;j++)33         {34             if(!p[j]&&Map[k][j]!=INF&&dist[j]>Map[k][j])35             {36                 dist[j]=Map[k][j];37                 pre[j]=k;38             }39         }40     }41     return ans;42 }
View Code

Kruskal

 

 1 const int N=1010,M=100010; 2 int f[N],r[N]; 3 struct node 4 { 5     int x, y, len; 6 }e[M]; 7 int cmp(const node &a,const node &b) 8 { 9     return a.len<b.len;10 }11 void Make_Set(int n)12 {13     for(int i=0;i<=n;i++)14     {15         f[i]=i;16         r[i]=1;17     }18 }19 void Link(int a, int b)20 {21     if (r[a] > r[b]) {f[b] = a; r[a]+=r[b];}22     else {f[a] = b; r[b]+=r[a];}23 }24 int Find_Set(int a)25 {26     if (a == f[a]) return a;27     else return  f[a] = Find_Set(f[a]);28 }29 int Kruskal(int n,int m)30 {31     int i, j, k, s=0,cn=0;32     Make_Set(n);33     sort(e,e+m,cmp);34     for(i=0;i<m;i++)35     {36         j=Find_Set(e[i].x);37         k=Find_Set(e[i].y);38         if(j!=k) {39             Link(j,k);40             s+=e[i].len;41             cn++;42         }43         if(cn==n-1) break;44     }45     if(cn<n-1) return -1;46     return s;47 }
View Code

 

 

 

 

二、次小生成树

1、最朴素的办法就是暴力枚举。求一遍最小生成树以后,依次删除最小生成树上的边,求此时的最小生成树,值最小的就是我们所求的。总共要运行 n次最小生成树算法,时间复杂度为n倍最小生成树算法的复杂度。有些题目能AC。暴力枚举在没有好方法时,也是一种非常好的方法。

2、我们知道,添加任意一条不在最小生成树上的边以后一定会形成一个环。找到环上除了(u,v)以外的权值最大的边,把它删掉,那么此时生成树就可能是次小生成树。基于这种思想:首先求出原图最小生成树,权值之和为MST。在执行最小最小生成树算法的时候,记录任意两点(u,v)路径之间的最大权值,maxe[u][v]注意,不是记录以uv为起点和终点的边的最大值,是两点之间的路径,可能有多个点在它们之间。然后枚举每一条边,如果该边不是原本最小生成树上的边,就用MST - maxe[u][v] + Map[u][v],统计结果最小的。

需要知道的是次小生成树的权值和可以与最小生成树的权值和相等。

模版:

prim(直接贴poj1679的代码了)

 1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6  7 const int maxn=1100,INF=0x3f3f3f3f; 8 int dist[maxn],Map[maxn][maxn],pre[maxn],maxe[maxn][maxn]; 9 //向外延伸的最短边长,记录图信息,记录连接信息,最长边10 bool vis[maxn];//1表示点已经在树的,0表示点在树外11 bool f[maxn][maxn];//存在边为1,用过为012 int n,m;//顶点数目13 int Prim()14 {15     int i,j,k,Min,ans=0;16     memset(vis,0,sizeof(vis));17     memset(f,0,sizeof(f));18     memset(maxe,0,sizeof(maxe));19     for(i=2;i<=n;i++)20     {21         dist[i]=Map[1][i];22         pre[i]=1;23     }24     dist[1]=0;25     vis[1]=1;26     pre[1]=0;27     for(i=1;i<n;i++)28     {29         Min=INF;30         k=0;31         for(j=1;j<=n;j++)32         {33             if(!vis[j]&&dist[j]<Min)34             {35                 Min=dist[j];36                 k=j;37             }38         }39         if(Min==INF) return -1;//G不连通40         vis[k]=1;41         ans+=Min;42         f[pre[k]][k] = f[k][pre[k]]=1;43         for(j=1;j<=n;j++)44         {45             if(vis[j]) maxe[k][j]=maxe[j][k]=max(maxe[j][pre[k]],dist[k]);46             if(!vis[j]&&dist[j]>Map[k][j])47             {48                 dist[j]=Map[k][j];49                 pre[j]=k;50             }51         }52     }53     return ans;54 }55 void init()56 {57     for(int i=0;i<=n;i++)58         for(int j=0;j<=n;j++)59         {60             if(i==j)Map[i][j]=0;61             else Map[i][j]=INF;62         }63 }64 int main()65 {66     //freopen("test.txt","r",stdin);67     int i,j,k,a,b,c,ca;68     scanf("%d",&ca);69     while(ca--)70     {71         scanf("%d%d",&n,&m);72         init();73         for(i=0;i<m;i++)74         {75             scanf("%d%d%d",&a,&b,&c);76             Map[a][b]=Map[b][a]=c;77         }78         int ans=Prim();79         if(ans==-1) {printf("Not Unique!\n");continue;}80         int res=INF;81         for(i=1;i<=n;i++)82         {83             for(j=i+1;j<=n;j++)84             {85                 if(!f[i][j]&&Map[i][j]!=INF)86                 {87                     res=(res,ans+Map[i][j]-maxe[i][j]);88                 }89             }90         }91         if(res==ans) printf("Not Unique!\n");92         else printf("%d\n",ans);93     }94     return 0;95 }
View Code

kruskal

 1 //O(mlogm+n^2) 2 const int N=1010,M=100010,INF=0x3f3f3f3f; 3 int f[N],r[N]; 4 int Find(int x) 5 { 6     if(x==f[x]) return x; 7     return f[x]=Find(f[x]); 8 } 9 void Link(int x,int y)10 {11     int a=Find(x), b=Find(y);12     if(a!=b)13     {14         f[b]=a;15         r[a]+=r[b];16     }17 }18 struct node19 {20     int u,v,w;21     bool select;22 }edge[M];23 bool cmp(const node &a, const node &b)24 {25     if(a.w!=b.w) return a.w<b.w;26     if(a.u!=b.u) return a.u<b.u;27     return a.v<b.v;28 }29 struct node130 {31     int to,next;32 }e[N];33 int cnt,tot,head[N],en[N],maxe[N][N];34 //e的边数、头结点、尾结点、两点在mst中的最大边35 void Kruskal(int n,int m)36 {37     int k=0, i,j,t;38     for(cnt=0;cnt<n;cnt++)39     {40         e[cnt].to=cnt+1;41         e[cnt].next=head[cnt+1];42         en[cnt+1]=cnt;43         head[cnt+1]=cnt;44     }45     sort(edge,edge+m,cmp);46     for(i=0;i<m;i++)47     {48         if(k==n-1) break;49         if(edge[i].w<0) continue;50         int a=Find(edge[i].u), b=Find(edge[i].v);51         if(a!=b)52         {53             for(j=head[a];j!=-1;j=e[j].next){54                 for(t=head[b];t!=-1;t=e[t].next){55                     int u=e[j].to, v=e[t].to;56                     maxe[u][v]=maxe[v][u]=edge[i].w;57                 }58             }59             e[en[b]].next=head[a];60             en[b]=en[a];61             Link(a,b);62             k++;63             edge[i].select=1;64         }65     }66 }67 void addedge(int u,int v,int w)68 {69     edge[tot].u=u;edge[tot].v=v;edge[tot].w=w;tot++;70 }71 void init()72 {73     tot=0;74     memset(head,-1,sizeof(head));75     memset(en,-1,sizeof(en));76     for(int i=0;i<N;i++){77         edge[i].select=0;78         f[i]=i;79         r[i]=1;80     }81 }82 int main()83 {84     //freopen("test.txt","r",stdin);85     int mst,secmst;86     Kruskal(n,m);87     mst=0;88     for(i=0;i<m;i++){89         if(edge[i].select) mst+=edge[i].w;90     }91     secmst=INF;92     for(i=0;i<m;i++){93         if(!edge[i].select)94             secmst=min(secmst,mst+edge[i].w-maxe[edge[i].u][edge[i].u]]);95     }96 97     return 0;98 }
View Code

算法的不同,当然也继承了该算法的特点。Prim不能存重边。这是需要注意的。

题目:Hdu2489  poj1679

 

三、有向图的最小树形图

解决的问题:一水源给菜地供水,水只能从高处向低处流,修水渠需要一定的花费,问怎样才能使得花费最低。

边是有方向的,与最小生成树不同,这可以理解为有向图的最小生成树。

算法是朱刘算法,难得的以国人名字命名的算法。

邻接矩阵建图,时间复杂度为O( n*n*n )。 hdu4009用这种方法超时了。

前向星建图,时间复杂度为O( n*m )

朱刘算法的两种实现可以看我的博客 http://www.cnblogs.com/Potato-lover/p/3942321.html

题目:hdu4009 、 hdu2121

 

四、LCA(树上两点的最近公共祖先)

解决的问题:求树上的任意两个点之间的最短距离。

用最短路的方法是行不通的,复杂度太高。所以才有解决LCA的专门的算法。

我只会离线的Tarjan(图拉)算法。 时间复杂度为O(m+q),m是边数,q是问题的个数。

我的博客里已经有相关的介绍了。http://www.cnblogs.com/Potato-lover/category/613545.html

题目:poj1330 (hdu2586) 

 

五、树的最小支配集、最小点覆盖、最大独立集

注意:是树上的,图中一定不能有圈。

都是基于深度优先搜索。

最小支配集:对于图G=(V,E),从V取尽量少的点组成一个集合,使得V中剩余的点都与取出来的点有边相连。

最小点覆盖:对于图G=(V,E),从V取尽量少的点组成一个集合,使得E中所有的边都与取出来的点相连。

最大独立集:对于图G=(V,E),从V取尽量多的点组成一个集合,使得这些点之间没有边相连。

时间复杂度都是O(n)。

这方面的题目似乎不曾出现,但是作为图论的学习者,有必须会。

 实现的算法很相似。

顶点下标从1开始的。

  1 /*newpos[i]表示深度优先遍历序列的第i个点是哪个点,  2 now表示当前深度优先遍历序列已经有多少个点了。select[]用于  3 深度优先遍历的判重,p[i]表示点i的父节点的编号。对于greedy(),  4 s[i]为1表示第i个点被覆盖。se[i]表示点i属于要求的点集*/  5   6 const int N = 1010;  7 struct node  8 {  9     int to, w, next; 10 }e[N*N]; 11 int head[N],tot,now; 12 int p[N], newpos[N] ; 13 bool select[N]; 14 bool s[N],se[N]; 15 //深度优先遍历,得到深度优先遍历序列 16 void dfs(int x) 17 { 18     newpos[now++] = x; 19     int k; 20     for(k=head[x]; k!=-1; k=e[k].next) 21     { 22         if(!select[e[k].to]) 23         { 24             select[e[k].to]= 1; 25             p[e[k].to]=x; 26             dfs(e[k].to); 27         } 28     } 29 } 30 //最小支配集 31 int greedy1() 32 { 33     memset(s,0,sizeof(s)); 34     memset(se,0,sizeof(se)); 35     int ans=0; 36     int i; 37     for(i=now-1; i>=0; i--) 38     { 39         int t=newpos[i]; 40         if(!s[t]) 41         { 42             if(!se[p[t]]) 43             { 44                 se[p[t]]=1; 45                 ans++; 46             } 47             s[t] = 1; 48             s[p[t]] = 1; 49             s[p[p[t]]] = 1; 50         } 51     } 52     return ans; 53 } 54 //最小点覆盖 55 int greedy2() 56 { 57     memset(s,0,sizeof(s)); 58     memset(se,0,sizeof(se)); 59     int ans=0; 60     int i; 61     for(i=now-1; i>=1; i--) 62     { 63         int t=newpos[i]; 64         if(!s[t]&&!s[p[t]]) 65         { 66             se[p[t]]=1; 67             ans++; 68             s[t] = 1; 69             s[p[t]] = 1; 70         } 71     } 72     return ans; 73 } 74 //最大独立集 75 int greedy3() 76 { 77     memset(s,0,sizeof(s)); 78     memset(se,0,sizeof(se)); 79     int ans=0; 80     int i; 81     for(i=now-1; i>=0; i--) 82     { 83         int t=newpos[i]; 84         if(!s[t]) 85         { 86             se[t]=1; 87             ans++; 88             s[t] = 1; 89             s[p[t]] = 1; 90         } 91     } 92     return ans; 93 } 94 void addedge(int i,int j) 95 { 96     e[tot].to=j;e[tot].next=head[i];head[i]=tot++; 97 } 98 void init() 99 {100     tot=now=0;101     memset(head,-1,sizeof(head));102     memset(select, 0, sizeof(select));103 }104 int main()105 {106     /* 读入图*/107     select[1]=1;108     p[1]=1;109     dfs(1);110     printf("%d\n",greedy());111     return 0;112 }
View Code

 

树的问题小结(最小生成树、次小生成树、最小树形图、LCA、最小支配集、最小点覆盖、最大独立集)