(图的遍历专题整理)
这一篇博客继续以一些OJ上的题目为载体,对搜索专题进行整理整理一下。
会陆续的更新。。
。
一、DFS在图的遍历中的使用
1、HDU 1241 Oil Deposits
题目分析:
这道题是一道简单的DFS的题目(当然也能够用其它方法来做).题目主要意思是求有多少块油田。算法就是:
假设map[i][j]是一块油田就不断的对他进行DFS。
/* * HDU_1241.cpp * * Created on: 2014年6月5日 * Author: Administrator */ #include <iostream> #include <cstdio> using namespace std; const int maxn = 105; char map[maxn][maxn]; //n:行数..m:列数 int n,m; int dir[8][2] = {//定义方向矩阵 {-1,1},{-1,0}, {-1,-1},{0,1}, {0,-1},{1,1}, {1,0},{1,-1} }; /** * 推断边界 */ bool checkBound(int i,int j){//(i,j):第i行,第j列 if(i < 0 || i >= n || j < 0 || j >= m){ return false; } return true; } /** * 深搜 */ void dfs(int i,int j){//訪问(i,j) map[i][j] = '*';//将(i,j)设置为已訪问 int ii; for(ii = 0 ; ii < 8 ; ++ii){//遍历这个点的全部相邻的点 int x = dir[ii][0]; int y = dir[ii][1]; int xx = i + x; int yy = j + y; if(checkBound(xx,yy) && map[xx][yy] == '@'){//假设这个相邻点还没有被訪问 dfs(xx,yy); } } } int main(){ while(scanf("%d%d",&n,&m),n||m){ int count = 0; int i; int j; for(i = 0 ; i < n ; ++i){//*****特别要注意这种字符矩阵的数据的读入的方式... scanf("%s",&map[i]); } for(i = 0 ; i < n ; ++i){ for(j = 0 ; j < m ; ++j){ if(map[i][j] == '@'){ dfs(i,j); count++; } } } printf("%d\n",count); } return 0; }
2、POJ 2386 Lake Counting
题目分析:
这道题和上面的那道是一样的。
。
/* * POJ_2386.cpp * * Created on: 2014年6月5日 * Author: Administrator */ #include <iostream> #include <cstdio> using namespace std; const int maxn = 105; char map[maxn][maxn]; int n, m; //方向千万别写错了,否则会WA的 int dir[8][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 0, -1 }, { 1, 1 }, { 1, 0 }, { 1, -1 } }; bool checkBound(int i, int j) { if (i < 0 || i >= n || j < 0 || j >= m) { return false; } return true; } void dfs(int i, int j) { map[i][j] = '.'; int ii; for (ii = 0; ii < 8; ++ii) { int xx = i + dir[ii][0]; int yy = j + dir[ii][1]; if(checkBound(xx,yy) && map[xx][yy] == 'W'){ dfs(xx,yy); } } } int main(){ while(scanf("%d%d",&n,&m)!=EOF){ int count = 0; int i; for(i = 0 ; i < n ; ++i){ scanf("%s",map[i]); } int j; for(i = 0 ; i < n ; ++i){ for(j = 0 ; j < m ; ++j){ if(map[i][j] == 'W'){ dfs(i,j); count++; } } } printf("%d\n",count); } return 0; }
二、拓扑排序在图的遍历中的使用
1、WIKIOI 2833 奇怪的梦境
题目分析:
1)题目能够抽象为:“推断一幅图能否生成一个拓扑序列,假设不能输出不满足拓扑序列的元素的个数”
使用链式前向星建图。然后进行拓扑排序,然后输出拓扑序列。(第一道使用链式前向星来做的题,好兴奋)
2)每行两个数ai,bi,表示bibutton要在ai之后按下.事实上这个就能够建立一条从a指向b的边.......
/* * WIKIOI_2833.cpp * * Created on: 2014年6月6日 * Author: Administrator */ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 10005; const int maxm = 25009; //链式前向星 int head[maxn];//head[i]: 以节点i为顶点的第一条边的位置(边号) struct Edge{ int to;//终点 int weight;//权值.(这道题中没实用到) int next;//edge[i].next: 与第i条边同起点的下一条边的位置(边号) }edge[maxm]; int indegree[maxn];//indegree[i]: 顶点i的入度 int n,m; /** * 拓扑排序:不断的删除入度为0的顶点及其出边。直到没有顶点 */ void topo(){ int queue[maxn];//用来记录最后的拓扑序列 int iq = 0;//用来记录拓扑序列中元素的个数.. int i; for(i = 1 ; i <= n ; ++i){//寻找入度为0的顶点 if(indegree[i] == 0){ queue[iq++] = i; } } for(i = 0 ; i < iq ; ++i){ int k; for(k = head[queue[i]] ; k != -1 ; k = edge[k].next){ indegree[edge[k].to]--; if(indegree[edge[k].to] == 0){ queue[iq++] = edge[k].to; } } } if(iq < n){//假设iq的终于值小于n.说明拓扑序列不存在... printf("T_T\n"); printf("%d\n",n-iq); }else{ printf("o(∩_∩)o\n"); } } int main(){ while(scanf("%d%d",&n,&m)!=EOF){ int i; int cnt = 0; memset(head,-1,sizeof(head)); memset(indegree,0,sizeof(indegree)); for(i = 1 ; i <= m ; ++i){ int a,b; scanf("%d%d",&a,&b); indegree[b]++;//该顶点的入度+1 edge[cnt].to = b; edge[cnt].next = head[a]; head[a] = cnt++; } topo(); } }
2、UVA 10305 Ordering tasks
题目分析:
输出一个合法的拓扑序列就可以(不必和例子保持一致)...
/* * UVA_10305.cpp * * Created on: 2014年6月6日 * Author: Administrator */ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 105; const int maxm = 10005; int head[maxn]; struct Edge{ int to; int weight; int next; }edge[maxm]; int indegree[maxn]; int n,m; void topo(){ int queue[maxn]; int iq = 0; int i; for(i = 1 ; i <= n ; ++i){ if(indegree[i] == 0){ queue[iq++] = i; } } for(i = 0 ; i < iq ; ++i){ int k; for(k = head[queue[i]] ; k != -1; k = edge[k].next){ indegree[edge[k].to]--; if(indegree[edge[k].to] == 0){ queue[iq++] = edge[k].to; } } } for(i = 0 ; i < iq-1 ; ++i){ printf("%d ",queue[i]); } printf("%d\n",queue[iq-1]); } int main(){ while(scanf("%d%d",&n,&m),n||m){ int i; int cnt = 0; memset(head,-1,sizeof(head)); memset(indegree,0,sizeof(indegree)); for(i = 1 ; i <= m ; ++i){ int a,b; scanf("%d%d",&a,&b); indegree[b]++; edge[cnt].to = b; edge[cnt].next = head[a]; head[a] = cnt++; } topo(); } return 0; }
三、图的可行遍性
图的可行遍性通俗点讲,就是图在某种特定的条件下能否被遍历。
1、欧拉图(求欧拉回路)
问题描写叙述:从起点出发。每一条边经过一次以后返回到起点。输出对应的序列(可能是边序列,也可能是点序列)
欧拉图问题的算法实现的基本代码:
/** * 求欧拉回路的基本代码 */ int ans[maxm];//欧拉回路 int ansi; int visited[maxm];//用来标记某一条边是否被訪问过 int edgeNum; void addEdge(int u,int v){ edge[edgeNum].to = v; edge[edgeNum].next = head[u]; head[u] = edgeNum++; } /** * 用来求欧拉回路 */ void dfs(int now){ int k; for(k = head[now]; k != -1 ; k = edge[k].next){//遍历以某个点为起点的全部边 if(visited[k] == false){//假设这条边没有被訪问过 visited[k] = true;//就将这条边标记为已经訪问 dfs(edge[k].to);//继续搜索这条边的终点 ans[ansi++] = edge[k].to;//这样求的是欧拉回路中的点序号.要注意对终点元素手动加上.. // ans[ansi++] = k;//假设这种话,求的是欧拉回路中的边序号 } } // ans[ansi++] = now; // printf("%d\n",now); }
以上欧拉图算法的代码是基于链式前向星的。下面是链式前向星的基本代码:
/** * 链式前向星的基本结构 */ int head[maxn]; struct Edge{ int to; int weight; int next; }; Edge edge[maxm];
算法的时间复杂度:O(m)
例题:
1)POJ 2230 Watch Cow
题目与分析:
这一道题抽象一下,能够描写叙述为:“从起点出发。经过每条边2次,再返回起点。输出所经过的点序列”。
事实上就是欧拉图的简单变形。本题与主要的欧拉图的问题的差别就在于将便变成双向边就可以。
。
/* * POJ_2230.cpp * * Created on: 2014年6月7日 * Author: Administrator */ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 10005; const int maxm = 100005; /** * 链式前向星的基本结构 */ int head[maxn]; struct Edge{ int to; int weight; int next; }; Edge edge[maxm]; /** * 求欧拉回路的基本代码 */ int ans[maxm];//欧拉回路 int ansi; int visited[maxm];//用来标记某一条边是否被訪问过 int edgeNum; //链式前向星加入一条边的操作 void addEdge(int u,int v){ edge[edgeNum].to = v; edge[edgeNum].next = head[u]; head[u] = edgeNum++; } /** * 用来求欧拉回路 */ void dfs(int now){ int k; for(k = head[now]; k != -1 ; k = edge[k].next){//遍历以某个点为起点的全部边 if(visited[k] == false){//假设这条边没有被訪问过 visited[k] = true;//就将这条边标记为已经訪问 dfs(edge[k].to);//继续搜索这条边的终点 ans[ansi++] = edge[k].to;//这样求的是欧拉回路中的点序号.要注意对终点元素手动加上.. // ans[ansi++] = k;//假设这种话,求的是欧拉回路中的边序号 } } // ans[ansi++] = now; // printf("%d\n",now); } int main(){ int n,m; while(scanf("%d%d",&n,&m)!=EOF){ int i; memset(head,-1,sizeof(head)); edgeNum = 0; memset(visited,false,sizeof(visited)); ansi = 0; for(i = 1 ; i <= m ; ++i){ int a,b; scanf("%d%d",&a,&b); addEdge(a,b);//用来解决每条边通过两次的问题 addEdge(b,a); } dfs(1); ans[ansi++] = 1;//这里手动加上起点 for(i = 0 ; i < ansi; ++i){ printf("%d\n",ans[i]); } } return 0; }