(图的遍历专题整理)

         这一篇博客继续以一些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,表示bi按钮要在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;
}




     




     














你可能感兴趣的:((图的遍历专题整理))