1、图的存储
①邻接矩阵
②邻接表(二维vector数组、链式前向星)
1 int n,m; 2 3 int head[100005]; 4 5 int cnt; 6 7 struct edge 8 9 { 10 11 int to,next,w; 12 13 }e[4000005]; 14 15 void ins(int u,int v,int w) 16 17 { 18 19 cnt++; 20 21 e[cnt].to=v; 22 23 e[cnt].next=head[u]; 24 25 e[cnt].w=w; 26 27 head[u]=cnt; 28 29 } 30 for(int i=head[x];i;i=e[i].next){ 31 v = e[i].to; 32 u = x; 33 w = e[i].w; 34 }
2、连通分量
①并查集
判定两个点是否在一个点集内
1 int find(int x) //查找根节点 2 { 3 int r=x; 4 while ( pre[r ] != r ) //返回根节点 r 5 r=pre[r ]; 6 7 int i=x , j ; 8 while( i != r ) //路径压缩 9 { 10 j = pre[ i ]; // 在改变上级之前用临时变量 j 记录下他的值 11 pre[ i ]= r ; //把上级改为根节点 12 i=j; 13 } 14 return r ; 15 } 16 17 18 void join(int x,int y) //判断x y是否连通, 19 //如果已经连通,就不用管了 //如果不连通,就把它们所在的连通分支合并起, 20 { 21 int fx=find(x),fy=find(y); 22 if(fx!=fy) 23 pre[fx ]=fy; 24 }
(刘汝佳一行版)
1 int findset (int x) { reuturn pa[x] != x ? pa[x] = findset(pa[x]) : x;}
*②求强连通分量(Tarjan)
1 //时间复杂度:O(n) 2 //注意判断两个强连通分量之间的重边是否会影响答案 3 //注意:在算强连通分量出度入度时,一定要先判断某边的两点是否在一个强连通分量内!!! 4 #include <stdio.h> 5 #include <iostream> 6 #include <string.h> 7 #include <vector> 8 #include <stack> 9 using namespace std; 10 #define MIN(a,b) ((a)<(b)?(a):(b)) 11 #define N 10005 // 题目中可能的最大点数 12 stack<int>sta; // 存储已遍历的结点 13 vector<int>gra[N]; // 邻接表表示图 14 int dfn[N]; // 深度优先搜索访问次序 15 int low[N]; // 能追溯到的最早的次序 16 int InStack[N]; // 检查是否在栈中(2为在栈中,1为已访问,且不在栈中,0为不在) 17 vector<int> Component[N]; // 获得强连通分量结果 18 int InComponent[N]; // 记录每个点在第几号强连通分量里 19 int index,ComponentNumber; // 索引号,强连通分量个数 20 int n, m; // 点数,边数 21 22 void init(void) 23 { 24 memset(dfn, 0, sizeof(dfn)); 25 memset(low, 0, sizeof(low)); 26 memset(InStack, 0, sizeof(InStack)); 27 index = ComponentNumber = 0; 28 for (int i = 1; i <= n; ++ i) 29 { 30 gra[i].clear(); 31 Component[i].clear(); 32 } 33 34 while(!sta.empty()) 35 sta.pop(); 36 } 37 38 void tarjan(int u) 39 { 40 InStack[u] = 2; 41 low[u] = dfn[u] = ++index; 42 sta.push(u); 43 44 for (int i = 0; i < gra[u].size(); ++ i) 45 { 46 int t = gra[u][i]; 47 if (dfn[t] == 0) 48 { 49 tarjan(t); 50 low[u] = MIN(low[u], low[t]); 51 } 52 else if (InStack[t] == 2) 53 { 54 low[u] = MIN(low[u], dfn[t]); 55 } 56 } 57 58 if (low[u] == dfn[u]) 59 { 60 ++ ComponentNumber; 61 while (!sta.empty()) 62 { 63 int j = sta.top(); 64 sta.pop(); 65 InStack[j] = 1; 66 Component[ComponentNumber].push_back(j); 67 InComponent[j]=ComponentNumber; 68 if (j == u) 69 break; 70 } 71 } 72 } 73 74 void input(void) 75 { 76 for(int i=1;i<=m;i++) 77 { 78 int a,b; 79 scanf("%d%d",&a,&b); 80 gra[a].push_back(b); 81 } 82 } 83 84 void solve(void) 85 { 86 for(int i=1;i<=n;i++) 87 if(!dfn[i]) 88 tarjan(i); 89 cout<<"account:"<<ComponentNumber<<endl; 90 for(int i = 1;i <= ComponentNumber;i++){ 91 cout<<i<<" : "; 92 for(int j = 0;j < Component[i].size();j++){ 93 cout<<Component[i][j]<<" "; 94 } 95 cout<<endl; 96 } 97 } 98 99 int main() 100 { 101 freopen("data.in","r",stdin); 102 103 scanf("%d%d",&n,&m); 104 105 init(); 106 input(); 107 solve(); 108 109 }
*③割边割点(Tarjan)
1 //永远先注意图的连通性。 2 //割点:模板题:poj1144(由于本题有多组测试数据,所以有一些标记不是boolean) 3 //割点割边的主要思想就是:一棵dfs树,我们根据dfs序人为地规定父节点,某个节点一定不可能和兄弟(子树)有边,只可能和后代和祖先有边,假如当前dfs到了节点x,那么,假如该节点及以该节点为根的子树中,有一个节点与x的父亲节点的祖先(时间戳比dfn[f[x]]更小)有连边,则即使删除x的父节点,x及其子树依然和x的父节点的祖先连通。若x的兄弟节点都具有这样的性质,则x的父节点就不是割点,否则只要有一个兄弟节点不具有该性质,则x的父节点是割点。利用这个特点,对图进行dfs,dfn[i]记录时间戳,而low[i]记录与以i为根的子树中的某节点有边的层数最小节点(即时间戳最小)。具体实现是:当前dfs到节点x。遍历x的所有边,首先不能遍历父亲节点。若指向的点曾遍历过,则一定是x的祖先,由于祖先的low还未求出,所以直接用祖先的时间戳dfn来更新low[x];若所指向的点未遍历过,则先遍历该点,再用该点的low来更新low[x]。最后,加入low[x]>=dfn[f[x]],则x的父节点是割点。同时可以注意到,由于这个判断,则我们人为定义的根节点(本程序中是1)会被认为是割点,但它并不一定是割点。可以先把根设为访问过(也就是假设根是一个割点,然后进行验证),然后找到根的一个儿子,从该儿子做bfs或者dfs,若整张图都被遍历,则根不是割点,否则是割点。 4 //割边:割边和割点基本相同,不过割边不仅要记录父亲,也要记录父亲连到自己的那条边的反边(在判断时不要判断是否是父亲,只判断是否是反边),因为有可能两点之间有不止一条边。在最后只要low[x]>dfn[f[x]],从父亲连下来的那条边就是割边。并且也不需要最后对根的验证。显然只有某个点连向父亲的边可能是桥,非树边不可能是桥。 5 //在主程序中记得也要和dfs中一样,也要判断是否访问过 6 //在求点、边双连通分量时,做完所有dfs,记得最后再退一次栈,因为根和其他节点可能也构成一个双连通分量 7 8 //若要求割点数时,千万不能在dfs时求,因为一个割点可能被多个儿子都当成割点,会重复计算,所以要在主程序中算。 9 #include<iostream> 10 #include<algorithm> 11 using namespace std; 12 13 const int N = 110; 14 const int WHITE = 0,GREY = 1,BLACK = 2; //标记值 15 int map[N][N]; 16 int col[N],low[N],dep[N];//color 17 int n,m; 18 bool tag[N];//标记点i是否割点 19 20 //求0-1图的割点 21 void dfs(int now,int father,int depth){ 22 col[now] = GREY; 23 dep[now] = depth; 24 int child = 0; 25 for(int i=1;i<=n;i++){ 26 if(map[now][i]==0)continue; 27 28 if(i != father && col[i] == GREY) 29 low[now] = min(low[now], dep[i]);//low需要被初始化成大数 30 if(col[i] == WHITE){ 31 dfs(i, now, depth+1); 32 child = child + 1; 33 low[now] = min(low[now], low[i]); 34 35 if((now==1&&child>1)||(now!=1&&low[i]>=dep[now])) //判割点 36 tag[now] = 1;//注意:需要记录该割点增加几个联通分量的操作需要在这里cnt++ 37 } 38 } 39 col[now] = BLACK; 40 } 41 42 void init(){ 43 memset(map,0,sizeof(map)); 44 memset(col,0,sizeof(col)); 45 memset(tag,0,sizeof(tag)); 46 for(int i=0;i<=n;i++)low[i]=INT_MAX; //low应该被初始化成大值 47 } 48 49 int main(){ 50 int a,b; 51 cin>>n>>m; 52 init(); 53 for(int i=0;i<m;i++){ 54 cin>>a>>b; 55 map[a][b] = map[b][a] = 1;//无向图 56 } 57 58 dfs(1,1,0);//dfs(root,root,0)的形式调用就行了 59 int ans=0; 60 61 for(int i=1;i<=n;i++) 62 if(tag[i])cout<<i<<' '; 63 64 cout<<endl; 65 system("pause"); 66 return 0; 67 } 68 /* 69 4 4 70 1 2 71 2 4 72 1 3 73 2 3 74 75 *//* 76 5 5 77 0 3 78 3 4 79 3 1 80 4 2 81 0 2 82 3 //3有3个孩子 83 5 5 84 0 2 85 2 4 86 0 3 87 3 4 //3只有2个新生连通分量 88 3 1 89 2 90 */
3、最小生成树(kruskal)
排序边,贪心枚举,不在同一集合合并
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int maxn = 100100; 7 8 struct edge{ 9 int v1,v2,power; 10 }; 11 int a,b,c,n,m,pre[maxn],sign = 0,big = 0,e = 0; 12 long long int tot = 0; 13 edge g[maxn]; 14 15 bool cmp(edge a,edge b){ 16 17 return a.power<b.power; 18 } 19 int find(int x) //查找根节点 20 { 21 int r=x; 22 while ( pre[r ] != r ) //返回根节点 r 23 r=pre[r ]; 24 25 int i=x , j ; 26 while( i != r ) //路径压缩 27 { 28 j = pre[ i ]; // 在改变上级之前用临时变量 j 记录下他的值 29 pre[ i ]= r ; //把上级改为根节点 30 i=j; 31 } 32 return r ; 33 } 34 35 36 void join(int x,int y) //判断x y是否连通, 37 //如果已经连通,就不用管了 //如果不连通,就把它们所在的连通分支合并起, 38 { 39 int fx=find(x),fy=find(y); 40 if(fx!=fy) 41 pre[fx ]=fy; 42 } 43 int main(){ 44 cin>>n>>m; 45 for(int r1 = 1;r1 <= n;r1++)pre[r1] = r1; 46 for(int r1 = 0;r1 < m;r1++){ 47 cin>>a>>b>>c; 48 g[r1].power = c; 49 g[r1].v1 = a; 50 g[r1].v2 = b; 51 52 } 53 sort(g,g+m,cmp); 54 for(int i = 1;i <= n;i++){ 55 if(find(g[sign].v1) == find(g[sign].v2)){ 56 tot += g[sign].power; 57 } 58 cout<<tot<<endl; 59 return 0; 60 }
4、最短路
①dijkstra
初始源点距离0,其他点INF,以距已选点的集合最近的点进行松弛操作,直到选中所有点
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cfloat> 7 #include <algorithm> 8 #include <queue> 9 #include <set> 10 #include <stack> 11 #include <map> 12 #include <vector> 13 #include <list> 14 using namespace std; 15 #define INF ~0U>>1 16 #define Pi acos(-1) 17 18 int graph[100][100],n,begin,end,V; 19 int d[100],used[100]; 20 21 int Dijkstra(int s,int e){ 22 d[s]=0; 23 24 for(int v=-1;1;v=-1){ 25 for(int i=1;i<=V;i++) 26 if(!used[i] && (v==-1 || d[i]<d[v])) v=i; 27 28 if(v==-1) break; 29 30 used[v]=1; 31 32 for(int i=1;i<=V;i++) d[i]=min(d[i],d[v]+graph[v][i]); 33 } 34 35 return d[e]; 36 } 37 38 int main(){ 39 memset(d,0X7F,sizeof(d)); 40 41 cin >> V >> begin >> end; 42 for(int i=1;i<=V;i++) 43 for(int j=1;j<=V;j++) 44 cin >> graph[i][j]; 45 46 cout << Dijkstra(begin,end); 47 48 return 0; 49 }
借助堆进行优化
1 /*********************** 2 2015-8-8 16:25:33 3 Algorithm -> 图论 -> 最短路 4 堆优化版dijkstra求最短路 5 可以将时间复杂度降为nlogn 6 无法处理负权图 7 ************************/ 8 9 #include<iostream> 10 #include<cstdio> 11 #include<string> 12 #include<cstring> 13 #include<vector> 14 #include<queue> 15 #include<algorithm> 16 #define mx 1000 17 18 using namespace std; 19 struct orz 20 { 21 int d,p; 22 friend bool operator <(orz a,orz b) {return a.d>b.d;}//堆和set里面都只有小于号,所以要用小根堆的话要将<重定向为> 23 }; 24 struct Edge{ 25 int to; 26 int w; 27 }; 28 priority_queue < orz > ss; 29 int flag = 0,v[mx],d[mx],n,m,l; 30 vector<Edge> edge[mx]; 31 void input(){ 32 cin>>n>>m>>l; 33 int u,v,wei; 34 Edge test; 35 for(int i = 1;i <= l;i++){ 36 cin>>u>>v>>wei; 37 test.to = v; 38 test.w = wei; 39 edge[u].push_back(test); 40 test.to = u; 41 edge[v].push_back(test); 42 } 43 for(int i = 0;i < mx;i++) d[i] = 1000000000; 44 } 45 void dij(int s) 46 { 47 48 d[s]=0; 49 orz tmp; 50 tmp.d=0,tmp.p=s; 51 ss.push(tmp); 52 flag++; 53 int x,dd; 54 Edge j; 55 while (!ss.empty())//不能只做n次,要一直做到堆空 56 { 57 tmp=ss.top(); 58 ss.pop(); 59 x=tmp.p,dd=tmp.d; 60 if (v[x]==flag) continue;//这里一定要判断!!! 61 v[x]=flag; 62 for (int i = 0;i < edge[x].size();i++){ 63 64 j = edge[x][i]; 65 if (d[j.to]>dd+j.w) 66 { 67 d[j.to]=dd+j.w; 68 tmp.d=dd+j.w,tmp.p=j.to; 69 ss.push(tmp); 70 } 71 } 72 73 } 74 } 75 int main(){ 76 input(); 77 dij(m); 78 int ans = 0; 79 for(int i = 1;i <= n;i++){ 80 if(i == m) continue; 81 if(d[i] >= 1000000000){ 82 cout<<"Sth wrong."<<endl; 83 return 0; 84 } 85 ans+=d[i]; 86 } 87 cout<<ans<<" M(s) are needed."<<endl; 88 return 0; 89 }
②floyd
多源最短路,动态规划,枚举所有点,寻找可能通过此点更新距离的两个点,三个点不能重复
1 void floyd(){ 2 for(int k = 1;k <= n;k++){ 3 for(int i = 1;i <= n;i++){ 4 if(k == i) continue; 5 for(int j = 1;j <= n;j++){ 6 if(k!=i && k!=j && i!=j) g[i][j] = min(g[i][j],g[i][k] + g[k][j]); 7 } 8 } 9 } 10 }
③SPFA
利用队列实现,源点入队,松弛更新,更新则入队,队首出队,直到队列为空
1 #define inf 0x3fffffff 2 #define M 105 //最大点数 3 struct son{ 4 int v, w; 5 }; 6 vector<son> g[M]; 7 bool inq[M]; //入队列标记 8 int dist[M], n; //n:实际点数 9 10 void init () 11 { 12 for (int i = 1; i <= n; i++) 13 g[i].clear(); 14 } 15 16 void spfa (int u) 17 { 18 int i, v, w; 19 for (i = 1; i <= n; i++) 20 { 21 dist[i] = inf; 22 inq[i] = false; 23 } 24 queue<int> q; 25 q.push (u); 26 inq[u] = true; 27 dist[u] = 0; 28 while (!q.empty()) 29 { 30 u = q.front(); 31 q.pop(); 32 inq[u] = false; 33 for (i = 0; i < g[u].size(); i++) 34 { 35 v = g[u][i].v; 36 w = g[u][i].w; 37 if (dist[u] + w < dist[v]) 38 { 39 dist[v] = dist[u] + w; 40 if (!inq[v]) 41 { 42 q.push (v); 43 inq[v] = true; 44 } 45 } 46 } 47 } 48 }
5、环
——判断是否为DAG
①、floyd传递闭包
如果i、j连通,j、i也连通,那么形成环,自己与自己连通
1 #include<iostream> 2 #include<vector> 3 #define mx 1050 4 using namespace std; 5 int n,m,l,r,circle[mx][mx]; 6 int main(){ 7 cin>>n>>m; 8 for(int i = 1;i <= m;i++){ 9 cin>>l>>r; 10 circle[l][r] = 1; 11 } 12 for(int k = 1;k <= n;k++){ 13 for(int i = 1;i <= n;i++){ 14 for(int j = 1;j <= n;j++){ 15 if(k != i){ 16 if(circle[i][k] && circle[k][j]) circle[i][j] = 1; 17 } 18 } 19 } 20 } 21 for(int i = 1;i <= n;i++){ 22 if(circle[i][i]) cout<<"T"<<endl; 23 else cout<<"F"<<endl; 24 } 25 return 0; 26 }
②、dfs遍历
设访问标记,自己能回到自己
1 //判断正负环 2 //判定负环应用与很多二分答案后以是否存在负环来判定答案是否合法(题目多与环的权值或差分约束系统有关) 3 //首先一定要初始化为0;去当且仅当我们可以更新这个点的时候我们去遍历该点。若该点已被别遍历过(v==true)则找到负环。 4 //注意退出前一定要让当前节点的v变成false 5 //从每个点都要做一次dfs,但并不用每次都清空d数组,只在第一次遍历前清空d数组即可。 6 bool dfs(int now) 7 { 8 int j; 9 for (j=last[now];j;j=next[j]) if (d[now]+w[j]<=d[a[j]]) 10 if (v[a[j]]) 11 { 12 v[now]=false;//退出前一定让v[now]=false,别忘了,也别写成v[a[j]] 13 return true; 14 } 15 else 16 { 17 v[a[j]]=true; 18 d[a[j]]=d[now]+w[j]; 19 if (dfs(a[j])) 20 { 21 v[now]=false;//退出前一定让v[now]=false,别忘了,也别写成v[a[j]] 22 return true; 23 } 24 } 25 v[now]=false;//退出前一定让v[now]=false,别忘了,也别写成v[a[j]] 26 return false; 27 } 28 bool check() 29 { 30 int i; 31 memset(d,0,sizeof(d));//只需在第一次dfs前清空数组即可 32 for (i=1;i<=n;i++) if (dfs(i)) return true; 33 return false; 34 } 35
——DAG上的拓扑排序
①、dfs
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cstring> 5 #include<vector> 6 #include<algorithm> 7 #define mx 1000 8 9 using namespace std; 10 vector<int> G[mx];//邻接表存储图 11 int c[mx],topo[mx],t,n,m;//c表示是否访问过(1为访问过,0为未访问,-1为正访问),topo表示拓扑序,t为倒序的排位指针,n为节点数,m为边数 12 bool dfs(int u){ 13 c[u] = -1;// 访问标志 14 for(int i = 0,v;i < G[u].size();i++){ 15 v = G[u][i];//遍历出边 16 if(c[v] < 0) return false;//存在有向环,失败退出 17 else if(!c[v] && !dfs(v)) return false;//如果已经访问,或者自身往下形不成拓扑序,失败退出; 18 19 } 20 c[u] =1; 21 topo[--t] = u; 22 return true; 23 } 24 bool topsort(){ 25 t = n; 26 memset(c,0,sizeof(c)); 27 for(int u = 0;u < n;u++) 28 if(!c[u])if(!dfs(u)) return false; 29 return true; 30 } 31 void input(){ 32 cin>>n>>m; 33 int u,v; 34 35 for(int i = 1;i <= m;i++){ 36 cin>>u>>v; 37 G[u].push_back(v); 38 } 39 40 } 41 void output(){ 42 if(topsort()){ 43 for(int i = 0;i < n;i++) cout<<topo[i]<<" ";//输出拓扑序列 44 cout<<endl; 45 }else{ 46 cout<<"No way!"<<endl; 47 } 48 } 49 int main(){ 50 input(); 51 output(); 52 return 0; 53 }
②、遍历
借助出度入度,入度为0的点入队,删掉此点的出边并使出边指向的边的入度减一
1 //Wikioi 2833 奇怪的梦境 2 #include<iostream> 3 #include<cstdio> 4 5 using namespace std; 6 const int maxn = 100000; 7 struct edge{ 8 int next; 9 int to; 10 }; 11 edge test[maxn]; 12 int head[maxn],ind[maxn],j[maxn],n,m,cur = 0,ans = 0,face = 0; 13 14 void add(int u,int v){ 15 test[cur].to = v; 16 test[cur].next = head[u]; 17 head[u] = cur++; 18 } 19 20 int px(int deep){ 21 22 if(deep == n - 1){ 23 ans = n; 24 face = 1; 25 return 0; 26 } 27 int flag = 1; 28 for(int p = 1;p <= n;p++){ 29 if(j[p] && ind[p] == 0){ 30 flag = 0; 31 for(int i=head[p];i>-1;i=test[i].next) ind[test[i].to]--; 32 j[p] = 0; 33 px(deep + 1); 34 j[p] = 1; 35 break; 36 } 37 } 38 if(flag && ans < deep){ 39 ans = deep; 40 return 0; 41 } 42 } 43 44 int main(){ 45 cin>>n>>m; 46 for(int i = 0;i < n;i++){ 47 test[i].next = -1; 48 head[i] = -1; 49 ind[i] = 0; 50 j[i] = 1; 51 } 52 int tu,tv; 53 for(int i = 0;i < m;i++){ 54 cin>>tu>>tv; 55 add(tu,tv); 56 ind[tv]++; 57 } 58 px(0); 59 if(face) cout<<"o(∩_∩)o"; 60 else cout<<"T_T"<<endl<<n - ans; 61 return 0; 62 }
③有向无环图上最长链
1 For (i=1;i<=n;i++) 2 { 3 x=q[i]; 4 if (f[x]==0) f[x]=1; 5 for(j=last[x];j!=0;j=next[j]) f[v[j]]=max(f[v[j]],f[x]+1); 6 ans=max(ans,f[x]); 7 }
——求最小环(floyd的变形)
1 int floyd_circle(){ 2 int ans = maxint; 3 for(int k = 0;k < n;k++){ 4 for(int i = 0;i <= k-1;i++) 5 for(int j = i + 1;j <= k-1;j++) 6 ans = min(ans,d[i][j] + block[i][k] + block[k][j]); 7 for(int i = 0;i < n;i++) 8 for(int j = 0;j < n;j++) 9 d[i][j] = min(d[i][j],d[i][k] + d[k][j]); 10 11 } 12 return ans; 13 14 }
——*欧拉通路、回路
http://blog.csdn.net/sunmenggmail/article/details/8030128
1 #include "SqStack.h" //堆栈的常见操作 2 #include "Queue.h"//队列的常见操作 3 4 typedef int Graph[200][200]; 5 int v,e; 6 7 void DFS(Graph &G,SqStack &S,int x,int t) 8 { 9 int k=0,i,m,a; 10 Push(S,x); 11 for(i=t;i<v;i++) 12 if(G[i][x]>0) 13 { 14 k=1; 15 G[i][x]=0; //删除此边 16 G[x][i]=0; 17 DFS(G,S,i,0); 18 break; 19 }//if,for 20 if(k==0) 21 { 22 Pop(S); 23 GetTop(S,m); 24 G[x][m]=1;//恢复刚刚删除的边 25 G[m][x]=1; 26 a=x+1;//从下一条边开始搜寻 27 if(StackLength(S)!=e) 28 { 29 Pop(S); 30 DFS(G,S,m,a); 31 }//if 32 else 33 Push(S,x); 34 }//if 35 }//DFS 36 37 int BFSTest(Graph G) 38 { 39 int a[200],x,i,k=0; 40 LinkQueue Q; 41 InitQueue(Q); 42 EnQueue(Q,0); 43 for(i=0;i<v;i++) 44 a[i]=0; 45 a[0]=1; 46 while(!QueueEmpty(Q)) 47 { 48 DeQueue(Q,x); 49 for(i=0;i<v;i++) 50 if(G[x][i]>0) 51 if(a[i]!=1) 52 { 53 a[i]=1; 54 EnQueue(Q,i); 55 }//if 56 }//while 57 for(i=0;i<v;i++) 58 if(a[i]==0) 59 { 60 k=1; 61 break; 62 } 63 if(k==1) 64 return 0; 65 else 66 return 1; 67 } 68 69 void Euler(Graph &G,int x) 70 { 71 int m; 72 SqStack S; 73 InitStack(S); 74 DFS(G,S,x,0); 75 printf("该图的一个欧拉回路为:"); 76 while(!StackEmpty(S)) 77 { 78 GetTop(S,m); 79 printf("->v%d",m); 80 Pop(S); 81 }//while 82 } 83 84 void InputM1(Graph &G) 85 { 86 87 int h,z; 88 printf("Please input 顶点数和边数 "); 89 scanf("%d",&v); 90 scanf("%d",&e); 91 for(int i=0;i<v;i++) 92 for(int j=0;j<v;j++) 93 G[i][j]=0; 94 95 printf("please int the 邻接矩阵的值(起点(数字) 终点(数字)): "); 96 for(int i=0;i<e;i++) 97 { 98 scanf("%d",&h); 99 scanf("%d",&z); 100 G[h-1][z-1]=1; 101 G[z-1][h-1]=1; 102 }//for 103 }//InputM1 104 105 int main() 106 { 107 int i,j,sum,k=0; 108 Graph G; 109 InputM1(G); 110 if(BFSTest(G)==0) 111 { 112 printf("该图不是连通图! "); 113 exit(0); 114 }//if 115 for(i=0;i<v;i++) 116 { 117 sum=0; 118 for(j=0;j<v;j++) 119 sum+=G[i][j]; 120 if(sum%2==1) 121 { k=1; 122 break; 123 }//if 124 }//for 125 if(k==1) printf("该图不存在欧拉回路! "); 126 else 127 Euler(G,0); //从那个点出发 128 return 1; 129 } 130 顶点数5,边数为6 131 相关联的点1 2 132 1 3 133 2 5 134 4 2 135 3 2 136 4 5
1 /* 2 欧拉(回)路是指从某点出发,每条边恰好经过依次的路径。 3 4 无向图欧拉回路存在的条件:所有点的度为都为偶数。 5 无向图欧拉路径存在的条件:只有两个点的度为奇数(起点终点) 6 有向图欧拉回路存在的条件:所有点入度=出度。 7 有向图欧拉路经存在的条件:一个点出度=入度+1,一个点出度=入度-1,其余点入度=出度。 8 */ 9 10 //usaco 3.3 fence 11 //题目保证存在欧拉路,要求输出字典序最小的欧拉路径 12 注意,这个算法的实现虽然是随便搜,但是实质并不是随便搜。所以绝对不可以把这个递归改成非递归。并且记录答案一定要放在dfs末尾,最终一定要倒序输出。 13 var n,m,i,j,k,sum,x,y,head:longint; 14 a:array[1..500,1..500]of longint;//两点之间可能不止一条边 15 du:array[1..500]of longint;//度 16 ans:array[1..5000]of longint;//注意ans数组的大小是边数而不是点数 17 procedure dfs(now:longint); 18 var ii:longint; 19 begin 20 for ii:=1 to 500 do if a[ii,now]<>0 then begin 21 dec(a[ii,now]); 22 dec(a[now,ii]); 23 dfs(ii); 24 end; 25 inc(sum); 26 ans[sum]:=now; 27 end; 28 begin 29 assign(input,'fence.in'); 30 assign(output,'fence.out'); 31 reset(input); 32 rewrite(output); 33 read(m); 34 for i:=1 to m do begin read(x,y); inc(du[x]); inc(du[y]);inc(a[x,y]); inc(a[y,x]); end; 35 x:=1;//若是欧拉回路,即所有点度数都是偶数,那么循环就不会找到奇数度点,所以要从1开始找 36 for i:=1 to 500 do if du[i]and 1=1 then begin x:=i; break;end;//若有奇数度点,则从编号小的开始找 37 dfs(x); 38 for i:=sum downto 1 do writeln(ans[i]);//一定要逆序输出!! 39 close(input); 40 close(output); 41 end.
6、树结构
①最近公共祖先(LCA)
1 void dfs(int x)//强烈建议把这个预处理换成bfs,防止爆栈 2 { 3 int y,j,k; 4 y=f[x][0]; 5 deep[x]=deep[y]+1; 6 for (k=0;f[y][k]!=0;k++) { f[x][k+1]=f[y][k]; y=f[y][k]; } 7 for (j=last[x];j!=0;j=next[j]) if (v[j]!=f[x][0]) 8 { 9 f[v[j]][0]=x; dfs(v[j]); 10 } 11 } 12 13 int findlca(int x,int y) 14 { 15 int z,k,dd; 16 if (deep[x]<deep[y]) {z=x; x=y; y=z;} 17 k=0; 18 for (dd=deep[x]-deep[y];dd!=0;dd=dd>>1) 19 { if (dd&1) x=f[x][k]; k++; }//这里是k++,别写成k=k<<1 20 if (x==y) return x; 21 k=0; 22 while (k>=0)//一定要写=0 23 if (f[x][k]!=f[y][k]) {x=f[x][k];y=f[y][k]; k++;} 24 else k--; 25 return f[x][0]; 26 }
②遍历
前序遍历:lson -> root -> rson
中序遍历:root -> lson -> rson
后序遍历:lson -> rson -> root
7、二分图匹配(匈牙利算法)
1 // 顶点、边的编号均从 0 开始 2 // 邻接表储存 3 4 struct Edge 5 { 6 int from; 7 int to; 8 int weight; 9 10 Edge(int f, int t, int w):from(f), to(t), weight(w) {} 11 }; 12 13 vector<int> G[__maxNodes]; /* G[i] 存储顶点 i 出发的边的编号 */ 14 vector<Edge> edges; 15 typedef vector<int>::iterator iterator_t; 16 int num_nodes; 17 int num_left; 18 int num_right; 19 int num_edges; 20 int matching[__maxNodes]; /* 存储求解结果 */ 21 int check[__maxNodes]; 22 23 bool dfs(int u) 24 { 25 for (iterator_t i = G[u].begin(); i != G[u].end(); ++i) { // 对 u 的每个邻接点 26 int v = edges[*i].to; 27 if (!check[v]) { // 要求不在交替路中 28 check[v] = true; // 放入交替路 29 if (matching[v] == -1 || dfs(matching[v])) { 30 // 如果是未盖点,说明交替路为增广路,则交换路径,并返回成功 31 matching[v] = u; 32 matching[u] = v; 33 return true; 34 } 35 } 36 } 37 return false; // 不存在增广路,返回失败 38 } 39 40 int hungarian() 41 { 42 int ans = 0; 43 memset(matching, -1, sizeof(matching)); 44 for (int u=0; u < num_left; ++u) { 45 if (matching[u] == -1) { 46 memset(check, 0, sizeof(check)); 47 if (dfs(u)) 48 ++ans; 49 } 50 } 51 return ans; 52 }