关于DFS 与 BFS (1)

    DFS与BFS本质上是对图的遍历,只是在遍历方式上有不同之处,下面就这两种方式谈一些我自己浅显的见解(来自完全没学过图论的蒻鶸)。

DFS

    DFS(Deepth First Search)深度优先搜索,根据字面意思我们就可以知道,DFS对树的遍历方式是优先搜索一个路径,也就是一条路走到黑,直到走完所有可能的路径或者搜索到了结束状态。DFS通常需要配合DP来进行剪枝,以保证不会超时。DFS一般通过递归过程实现,而结束状态用来作为递归边界,当然非递归也没毛病,不过就是复杂一点,而且利用的也是系统栈,效率上来说差别不大。

BFS

    BFS(Breadth First Search)广度优先搜索,搜索方式是从头结点开始,优先搜索同一个深度的所有结点,然后再进行下一个深度的遍历,由于这种独特搜索方式,使得搜索到的路径一定是理论上的最短路径。对于BFS来说,能够应用的前提,一般是要给出明确的初始状态(头节点)以及结束状态,还有节点之间明确的边。BFS一般用队列实现比较简单,当然不用队列也能行,有一些题用队列反而有点不好理解。

下面直接扔几个例题,对例题进行讲解

HDU 1016  Prime Ring Problem

传送门 http://acm.hdu.edu.cn/showproblem.php?pid=1016

    本题可以说是应用DFS进行解题的经典例题之一了。首先判断初始状态以及结束状态不明确,而且又是对所有可行路径的搜索,应用DFS解题是非常显然的了。

#include <iostream>
#include <cstdio> 
#include <string.h>
using namespace std;
int ring[21]; // 存放可行的数字 
bool in_ring[21]; 
bool Is_prime[110];  
int n;
void prime(void) // 素数筛法, 筛出素数以便判断 
{
	memset(Is_prime,1,sizeof(Is_prime));
	Is_prime[0] = false;
	Is_prime[1] = false;
	for(int i = 2;i < 100;i++)
	{
		if(Is_prime[i])
		{
			for(int j = i + i;j <= 100;j += i)
				Is_prime[j] = false;
		}
	}
}

void print_ans(void) // 打印答案 
{
	for(int i = 1;i <= n;i++)
		printf(" %d",ring[i]);//懒得调整格式了…… 用 
	printf("\n");
}
void DFS(int x)
{
	if(x == n+1) // 递归边界, 如果成功到达递归边界意味着这是一个可行的解 
	{
		print_ans();
		return;
	}
	
	for(int i = 2;i <= n;i++) // 对目前要填的空,枚举所有的数 
	{
		if(in_ring[i] == false ) // 如果这个数在环上还未出现过 
		{
			if(Is_prime[i + ring[x - 1]]) //判断这个数与上一个数的和是否为素数 
				if(x != n || (x == n && Is_prime[ring[1] + i]) ) // 如果是环上最后一个数,判断这个数和1的和是否为素数 
				{
					in_ring[i] = true; // 设置这个数已经填入过环中 
					ring[x] = i;
					DFS(x + 1); // 这里是DFS的核心代码,如果要填的这个数可行,那么继续向树的更深处枚举 
					in_ring[i] = false; // 当到达递归边界函数开始返回时,把环上的所有的数重置为未填入过 
				}
					
		}	
	}
	return; //如果当前搜不到一条可行的路径,直接返回 
}
int main(int argc, char** argv) {
	
	prime();
	
	while(~scanf("%d",&n))
	{
		memset(in_ring,0,sizeof(in_ring));
		ring[1] = 1; // 根据题目要求,环的开头必须是1 
		DFS(2); // 从第二个数开始进行深度搜索 ,顺时针方向进行搜索 
	}
	return 0;
}

HDU 1241 Oil Deposits

传送门 http://acm.hdu.edu.cn/showproblem.php?pid=1241

此题虽然没有给出明确的初始和结束状态,但是思想上却是通过BFS进行标记,而且此题给出了明确的边和结点。而且这题要是用DFS进行遍历永远也遍历不完,因为DFS的有的结点是要重复走的,BFS每个结点只需要入队一次就能判断。

#include <cstdio>
#include <queue>
#include <string.h>
using namespace std;
struct Node
{
	int x, y;
};
char Matrix[110][110];
bool inq[110][110];//利用hash进行标记此结点是否入队 
int move_x[8] = {1,-1,0,0,-1,-1,1,1}; // 对每个结点进行八个方向的枚举 
int move_y[8] = {0,0,1,-1,1,-1,1,-1};

int nLie, nHang;

bool Judge(int x, int y)
{
	if(x >= nHang || y >= nLie || x < 0 || y < 0) // 给出图的边界 
		return false;

	if(Matrix[x][y] == '*' || inq[x][y] == true  ) // 判断是否曾经入队,是否需要入队 
		return false;

	return true;
}

void BFS(int x, int y) // BFS的应用其实是由模板和套路可循的…… 
{
	queue<Node> qu;  
	Node top;
	top.x = x;
	top.y = y;
	qu.push(top);//头节点入队 
	inq[x][y] = true;

	while(!qu.empty())//判断是否队空 
	{
		Node temp = qu.front();//取出队列的首元素 ,下一步可以判断队首元素是否满足要求,不过这里不需要 
		qu.pop();//弹出队首元素 
		for(int i = 0;i < 8;i++) // 对队首元素的相邻八个方向分别进行判断是否入列 
		{
			int newX = temp.x + move_x[i];
			int newY = temp.y + move_y[i];
			if(Judge(newX,newY))
			{
				Node node;
				node.x = newX;
				node.y = newY;
				qu.push(node);//将相邻的油田入队 
				inq[newX][newY] = true; //设置此块油田已经被标记过与其他油田相邻 
			}
		}
	}
}

int main()
{
	while(scanf("%d %d",&nHang,&nLie))
	{
		memset(inq,0,sizeof(inq));

		int ans = 0;
		if(nHang  == 0 && nLie == 0 )
			break;

		for(int i = 0;i < nHang;i++)
		{
			for(int j = 0;j < nLie;j++)
				scanf(" %c",&Matrix[i][j]); // %c前面加个空格就能自动屏蔽空白键的输入 
		}

		for(int i = 0;i < nHang;i++)
		{
			for(int j = 0;j < nLie;j++) // 对所有的结点进行枚举 
			{
				if(Matrix[i][j] == '@' && inq[i][j] == false) // 如果发现一块油田,那么通过BFS标记所有相邻的油田 
				{
					BFS(i,j);
					ans++;	//油田数目 +1(滑稽) 
				}
					
			}

		}
		printf("%d\n",ans);

	}
}

HDU 1728 逃离迷宫

传送门 http://acm.hdu.edu.cn/showproblem.php?pid=1728

    此题贼吉尔有意思,在CSDN上看到了大神的解法,非常有意思,为我们提供了一种新的思路。这个题之所以和其他的模板BFS不同就是需要判断转弯次数,一开始我想的是将目前的朝向单独作为一个变量加入结构体,如果朝向改变那么转弯数+1,但是如果要判断方向改变至少需要三个点,那么BFS似乎很难达到要求,因为每次都回溯三个点……emmm不好操作啊,看起来只有DFS加DP进行剪枝才行。不过在网上看了大神解法简直精巧,非常漂亮,下面上一段C艹实现。

    

#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;

struct Node
{
	int x, y, step;
};
int moveX[4] = {0, 0, 1, -1};
int moveY[4] = {1, -1, 0, 0};
char maze[110][110];
bool inq[110][110];
int m, n;
int k;
int x1, y3, x2, y2;

bool Judge(int x, int y)
{
	if(x <= 0 || x > m || y <= 0 || y > n)
		return false;
	if(maze[x][y] == '*')  //此处不能直接判断是否已经入队,否则会使搜索提前终止 
		return false;
	return true;
}

bool check (int x, int y, int step)
{
	if(x == x2 && y == y2 && step <= k)
		return true;
	return false;
}
bool BFS(int k, int x1, int y3, int x2, int y2)
{
	queue<Node> qu;
	Node node;
	node.x = x1;
	node.y = y3;
	node.step = -1;
	inq[x1][y3] = true;
	qu.push(node);
	while(!qu.empty())
	{
		Node top = qu.front();
		qu.pop();
		
		for(int i = 0;i < 4;i++) // 对于每一个结点,以他为中心对四个方向搜索 
		{						//但是实际上只有两个方向需要进行搜索,这两个方向就是转弯的方向 
			int newX = top.x + moveX[i];  
			int newY = top.y + moveY[i];
			int newS = top.step + 1; 
			while(Judge(newX, newY))
			{
				if(inq[newX][newY] == false)
				{
					node.x = newX;
					node.y = newY;
					node.step = newS;
					inq[newX][newY] = true;
					qu.push(node);
				}
				
				if(!check(newX, newY, newS)) //如果新的结点并不是终点,对这一个方向继续延伸,直到到达图的边界 
				{
					newX = newX + moveX[i];
					newY = newY + moveY[i];
				}
				else
				{
					return true; 
				}
			}
		}
	}
	
	return false;
}

int main() 
{
	int t = 0;
	scanf("%d",&t);
	
	while(t--)
	{
		memset(inq, 0, sizeof(inq));
		
		
		scanf("%d %d",&m, &n);
		
		for(int i = 1;i <= m;i++)
		{
			for(int j = 1;j <= n;j++)
				scanf(" %c",&maze[i][j]);
		}
		scanf("%d %d %d %d %d",&k, &y3, &x1, &y2, &x2);//此题巨坑,注意x和y分别代表的是啥。 
		
		if(BFS(k, x1, y3, x2, y2))
			printf("yes\n");
		else
			printf("no\n");
	}
	return 0;
}

POJ 3984 迷宫问题

传送门 http://poj.org/problem?id=3984

此题不上完整代码了,直接上核心。此题难点在于回溯输出路径……当时试了好几种方法,又是map又是pair的,不过最满意的还是这一种,应该是链表吧,记录前一个结点。

struct Node
{
    int x,y;
    int pre;
}node[100];

void print_ans(Node a)
{
    if(a.x == 0 && a.y == 0)
    {
    	printf("(%d, %d)\n",a.x , a.y);
    	return;
	}
	else
		print_ans(node[a.pre]);
	
	printf("(%d, %d)\n",a.x ,a.y);
		
}

void BFS(void)
{
	int pre = 0;
	int next = pre + 1;
    queue<Node> qu;
    node[pre].x = 0;
    node[pre].y = 0;
    node[pre].pre = 0;
    qu.push(node[pre]);
    inq[0][0] = true;

    while(!qu.empty())
    {
    	
        Node a = qu.front();
        if(a.x == 4 && a.y == 4)
        {
            print_ans(a);
            return;
        }
        qu.pop();
        for(int i = 0;i < 4;i++)
        {
            int newX = a.x + mX[i];
            int newY = a.y + mY[i];
            if(Judge(newX,newY))
            {
                node[next].x = newX;
                node[next].y = newY;
                node[next].pre = pre;
                inq[newX][newY] = true;
                qu.push(node[next]);
                next++;
            }
        }
        pre++;

    }
}
回看感觉自己好蒻鶸啊……DFS的剪枝也不熟练……下一步还是忒多练啊。

猜你喜欢

转载自blog.csdn.net/aldo101/article/details/79586206