这一篇博客继续以一些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;
}