《算法笔记》8.1小节——搜索专题->深度优先DFS 广度优先BFS

深度优先搜索DFS

DFS一般使用递归实现
深度优先算法解决背包问题

#define _CRT_SECURE_NO_WARNINGS 1
#include<cstdio>
const int maxn = 30;
int n, V, maxValue = 0;
int w[maxn], c[maxn];
void DFS(int index, int sumW, int sumC)
{
	if (index == n)//死胡同
	{
		if (sumW <= V && sumC >= maxValue)
		{
			maxValue = sumC;
		}
		return;
	}
	//岔道口
	DFS(index + 1, sumW, sumC);//不选第index件物品
	DFS(index + 1, sumW + w[index], sumC + c[index]);//选第index件物品
}
int main()
{
	scanf("%d %d",&n,&V);//物品件数,背包总容量
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &w[i]);
	}
	for (int i = 0; i < n; i++)
	{
		scanf("%d",&c[i]);
	}
	DFS(0,0,0);
	printf("%d\n",maxValue);	
	return 0;
}

背包问题减枝

void DFS(int index, int sumW, int sumC)
{
	if (index == n)//死胡同
	{		
		return;
	}
	//岔道口
	DFS(index + 1, sumW, sumC);//不选第index件物品
	if (sumW + w[index] <= V)//选index的时候有条件
	{
		if (sumC + c[index] > maxValue)
			maxValue = sumC + c[index];
		DFS(index + 1, sumW + w[index], sumC + c[index]);//选第index件物品
	}
}

背包问题减枝2.0

void DFS(int index, int sumW, int sumC)
{
	if (index == n)//死胡同
	{		
		if (sumC >= maxValue)
		{
			maxValue = sumC;
		}
		return;
	}
	//岔道口
	DFS(index + 1, sumW, sumC);//不选第index件物品
	if (sumW + w[index] <= V)//选index的时候有条件
	{		
		DFS(index + 1, sumW + w[index], sumC + c[index]);//选第index件物品
	}
}

刚刚没找到codeup网址:在这里记录一下
http://codeup.hustoj.com/contest.php
问题 A: 【递归入门】全排列

#include <cstdio>
int n;
bool flag[20]={false};
int ans[20];
void combine(int count)
{
    if(count==n)
    {
        for(int i=0;i<n;i++)
        {
            printf("%d ",ans[i]);
        }
        printf("\n");
        return ;
    }
    for(int i=0;i<n;i++)
    {
        if(flag[i]==false)
        {
            ans[count]=i+1;
            flag[i]=true;
            combine(count+1);
            flag[i]=false;
        }
    }
}

int main()
{
    while(~scanf("%d",&n))
    {
        int count=0;
        combine(count);
    }
    return 0;
}

问题 B: 【递归入门】组合的输出

#include <cstdio>
using namespace std;
int n, r;
int ans[26];
bool flag[26] = { false };
void combine(int x, int count)
{
    if (count == r)
    {
        for (int i = 0; i < r; i++)
        {
            printf("%d ", ans[i]);
        }
        printf("\n");
        return;
    }
    for (int i = x; i <= n; i++)
    {
        if (flag[i - 1] == false)
        {
            ans[count] = i;
            flag[i - 1] = true;
            combine(i, count + 1);
            flag[i - 1] = false;
        }
    }
}
int main()
{
    while (~scanf("%d %d", &n, &r))
    {
        combine(1, 0);
    }
    return 0;
}

问题 C: 【递归入门】组合+判断素数

#include <cstdio>
#include <cmath>
using namespace std;
int n,k,count;
int number[25],sum;
bool isPrime(int n)
{
    if(n<=1)
        return false;
    int sqr=(int)sqrt(1.0*n);
    for(int i=2;i<=sqr;i++)
    {
        if(n%i==0)
            return false;
    }
    return true;
}
void combine_judge(int pos,int level)
{
    if(level==k)
    {
        if(isPrime(sum)==true)
        {
            count++;
        }
        return;
    }
    for(int i=pos;i<n;i++)
    {
        sum+=number[i];
        combine_judge(i+1,level+1);
        sum-=number[i];
    }
}
int main()
{
    while(~scanf("%d %d",&n,&k))
    {
        for(int i=0;i<n;i++)
        {
            scanf("%lld",&number[i]);
        }
        count=0;
        sum=0;
        combine_judge(0,0);
        printf("%d",count);
    }
    return 0;
}

问题 D: 【递归入门】n皇后 问题(原始的8皇后问题)

#include <cstdio>
int n,result[15],count;
void n_queen(int index)
{
    if(index==n)
    {
        for(int i=0;i<n;i++)
        {
            printf("%d ",result[i]+1);
        }
        printf("\n");
        count++;
        return;
    }
    bool flag[15]={false};
    for(int i=0;i<index;i++)
    {
        flag[result[i]]=true;
        int lower_right=i+result[i]-index;
        int top_right=-i+result[i]+index;
        if(lower_right>=0)
            flag[lower_right]=true;
        if(top_right<=n-1)
            flag[top_right]=true;
    }
    for(int i=0;i<n;i++)
    {
        if(flag[i]==false)
        {
            result[index]=i;
            n_queen(index+1);
        }
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        count=0;
        n_queen(0);
        if(count==0)
            printf("no solute!\n");
    }
    return 0;
}

问题 E: 【递归入门】出栈序列统计

#include <cstdio>
int n,count;
void dispose(int num,int pop,int push)
{
    if(pop==0&&push==0)
    {
        count++;
        return;
    }
    if(push>0)
        dispose(num+1,pop,push-1);
    if(pop>0&&num>0)
        dispose(num-1,pop-1,push);
}
int main()
{
    while(~scanf("%d",&n))
    {
        count=0;
        dispose(0,n,n);
        printf("%d\n",count);
    }
    return 0;
}

问题 F: 【递归入门】走迷宫
这题好难呀,留着以后填坑叭orz(逃~

广度优先搜索BFS

BFS通常用队列来实现
在n*m矩阵中找 1“块 ”的个数

  • 输入
6 7
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0
  • 输出
4

队列真好用

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<queue>
using namespace std;
const int maxn = 100;
int matrix[maxn][maxn];
int inq[maxn][maxn] = { 0 };//表示inq[i][j]有没有被访问过
int n, m;//矩阵有n行m列
int X[4] = { 0,0,1,-1 };//增量数组
int Y[4] = { 1,-1,0,0 };
struct node {
	int x;
	int y;
}Node;
int judge(int x, int y)//判断坐标(x,y)是否需要访问,x表示行,y表示列
{
	if (x < 0 || y < 0 || x >= n || y >= m) return 0;//如果越界,不需要访问
	if (inq[x][y] == 1 || matrix[x][y] == 0) return 0;	//如果为0或者已经被访问过,不需要访问
	return 1;//不然就可以访问
}
void BFS(int x, int y)
{
	Node.x = x;
	Node.y = y;
	queue < node>q;//定义队列
	q.push(Node);
	inq[x][y] = 1;//!!!注意这个不能忘记,每次push后就记录Inq为1
	while (!q.empty())
	{
		node top = q.front();
		q.pop();//将队首元素出队
		for (int i = 0; i < 4; i++)
		{
			int newX = top.x + X[i];
			int newY = top.y + Y[i];
			if (judge(newX, newY))
			{
				Node.x = newX;
				Node.y = newY;
				q.push(Node);
				inq[newX][newY] = 1;//表示已经访问过
			}			
		}
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	int ans = 0;
	for (int x = 0; x < n; x++)
		for (int y = 0; y < m; y++)
		{
			scanf("%d",&matrix[x][y]);
		}
	for(int x=0;x<n;x++)
		for (int y = 0; y < m; y++)
		{
			if (matrix[x][y] == 1 && inq[x][y] == 0)
			{
				ans++;
				BFS(x, y);
			}
		}
	printf("%d\n",ans);
	return 0;
}

走迷宫

  • 输入
5 5
.....
.*.*.
.*S*.
.***.
...T*
2 2 4 3
  • 输出
11
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 100;
char maze[maxn][maxn];
int inq[maxn][maxn] = { 0 };//表示inq[i][j]有没有被访问过
int n, m;//矩阵有n行m列
int X[4] = { 0,0,1,-1 };//增量数组
int Y[4] = { 1,-1,0,0 };
struct node {
	int x;
	int y;
	int step;
}S,T,Node;
int judge(int x, int y)//判断坐标(x,y)是否需要访问,x表示行,y表示列
{
	if (x < 0 || y < 0 || x >= n || y >= m) return 0;//如果越界,不需要访问
	if (inq[x][y] == 1 || maze[x][y] == '*') return 0;	//如果为0或者已经被访问过,不需要访问
	return 1;//不然就可以访问
}
int BFS()
{	
	queue < node> q;//定义队列
	q.push(S);
	inq[S.x][S.y] = 1;//!!!注意这个不能忘记,每次push后就记录Inq为1
	while (!q.empty())
	{
		node top = q.front();
		q.pop();//将队首元素出队
		if (top.x == T.x && top.y == T.y)
			return top.step;
		for (int i = 0; i < 4; i++)
		{
			int newX = top.x + X[i];
			int newY = top.y + Y[i];
			if (judge(newX, newY))
			{
				Node.x = newX;
				Node.y = newY;
				Node.step = top.step + 1;
				q.push(Node);
				inq[newX][newY] = 1;//表示已经访问过
			}			
		}
	}
	return -1;//无法到达终点
}
int main()
{
	scanf("%d %d",&n,&m);
	
	for (int x = 0; x < n; x++)
	{
		getchar();//过滤掉换行符
		for (int y = 0; y < m; y++)
		{
			maze[x][y] = getchar();
		}
		maze[x][m] = '\0';//最后补上换行符
	}
	scanf("%d %d %d %d",&S.x,&S.y,&T.x,&T.y);
	S.step = 0;
	printf("%d", BFS());
	return 0;
}

需要注意:
1、设置inq数组的含义是节点是否入队,而不是节点被访问
2、queue中的元素只是原来元素的副本,对队列中副本的修改并不会影响原来的元素
一开始我是这样想的:

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<cstring>
#include<queue>
//!!这题注意所有做过的点都可以再走一遍,所以不需要判断重复,要根据什么来判断有没有走完呢,到A最少需要8步,走完8步也说明一定可以到达A,只要判断level是否等于8就可以了。
using namespace std;
char matrix[8][8];
char matrix1[8][8];//matrix的替补
int inq[8][8] = { 0 };//表示inq[i][j]有没有进入队列
int X[9] = { 0,0,0,1,-1,1,-1,1,-1 };//增量数组
int Y[9] = { 0,1,-1,0,0,-1,1,1,-1 };//注意也可以选择不走路
struct node {
	int x;
	int y;
	
}S,T,Node;
int level;
void change1()
{

	for (int i = 7; i >= 0; i--)
	{
		for (int j = 0; j < 8; j++)
		{
			matrix1[i][j] = matrix[i][j];
		}
	}
	for (int i = 7; i >= 0; i--)
	{
		for (int j = 0; j < 8; j++)
		{
			matrix1[i][j] = matrix1[i - 1][j];
		}
	}
	matrix1[1][7] = '.';
	for (int j = 0; j < 7; j++)
		matrix1[0][j] = '.';
	matrix1[0][7] = 'A';
}
int judge(int x, int y)//判断坐标(x,y)是否可以入队,x表示行,y表示列
{
	if (x < 0 || y < 0 || x >= 8 || y >= 8) return 0;//如果越界,不再入队
	if ( matrix[x][y] == 'S') return 0;	//如果为石头.不再入队
	change1();
	if (matrix1[x][y] == 'S') return 0;//如果下一步走过去之后为石头
	return 1;//不然就可以访问
}
void change()
{	
	for (int i = 7; i >= 0; i--)
	{
		for (int j = 0; j < 8; j++)
		{
			matrix[i][j] = matrix[i - 1][j];
		}
	}
	matrix[1][7] = '.';
	for (int j = 0; j < 7; j++)
		matrix[0][j] = '.';
	matrix[0][7] = 'A';
}
int BFS()
{	
	queue < node> q;//定义队列
	q.push(S);
	level = 0;
	while (!q.empty())
	{
		node top = q.front();
		q.pop();//将队首元素出队
				
		for (int i = 0; i < 9; i++)
		{
			int newX = top.x + X[i];
			int newY = top.y + Y[i];
			if (judge(newX, newY))//如果可以入队
			{
				level++;
				//printf("%c:",matrix[newX][newY]);
				//printf("(%d,%d)入队了,level=%d\n", newX, newY, level);
				Node.x = newX;
				Node.y = newY;
				
				if (level >= 8 || (Node.x == 0 && Node.y == 7))
					return 1;//表示可以到达
				q.push(Node);
				
			}			
		}
		change();		
	}
	return 0;//无法到达终点
}
int main()
{
	int n;
	scanf("%d",&n);
	int i;
	for (i = 1; i <= n; i++)
	{
		for (int x = 0; x < 8; x++)
		{
			getchar();//过滤掉换行符
			for (int y = 0; y < 8; y++)
			{
				matrix[x][y] = getchar();
			}
			matrix[x][8] = '\0';//最后补上换行符
		}
		
		S.x = 7, S.y = 0;//左下角方格,起点
		T.x = 0, T.y = 7;//右上角方格,终点
		if (BFS())
		{
			printf("Case #%d: Yes\n",i);
		}
		else
		{
			printf("Case #%d: No\n", i);
		}
		getchar();//过滤换行符
	}
	
	
	return 0;
}

但是上面这个代码总是显示错误

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<cstring>
#include<queue>
//!!这题注意所有做过的点都可以再走一遍,所以不需要判断重复,要根据什么来判断有没有走完呢,到A最少需要8步,走完8步也说明一定可以到达A,只要判断level是否等于8就可以了。
using namespace std;
char matrix[8][8];
int inq[8][8] = { 0 };//表示inq[i][j]有没有进入队列
int dir[9][2] = { {0,1},{0,-1},{1,0},{-1,0},{1,1},{-1,-1},{1,-1},{-1,1},{0,0} };//方向向量
struct node {
	int x,y;//坐标
	int step;
}S;
int sx, sy, ex, ey;
node now, nex;//定义两个状态,当前状态和下一个状态
int judge(int x, int y)//判断坐标(x,y)是否可以入队,x表示行,y表示列
{
	if (x < 0 || y < 0 || x >= 8 || y >= 8) return 0;//如果越界,不再入队
	if (matrix[x][y] == 'S') return 0;	//如果为石头.不再入队
	return 1;//不然就可以访问
}
void change()//落石
{
	for (int i = 7; i >= 0; i--)
		for (int j = 7; j >= 0; j--)
			if (matrix[i][j] == 'S')
			{
				matrix[i][j] = '.';
				if (i <= 6)
					matrix[i + 1][j] = 'S';
			}
}
	
void BFS(int c)
{
	int flag = 0;
	queue < node> q;//定义队列
	S.x = sx,S.y = sy, S.step = 0;
	q.push(S);
	int level = 0;
	while (!q.empty())
	{		
		now = q.front();
		if (now.step != level)
		{
			change();
			level = now.step;
		}
		if (matrix[now.x][now.y] == 'S')
		{
			q.pop();
			continue;
		}
		if (now.step == 8)
		{
			flag = 1;
			break;
		}
		q.pop();
		for (int i = 0; i < 9; i++)
		{
			if (judge(now.x + dir[i][0], now.y + dir[i][1]) == 1)
			{
				nex.x = now.x + dir[i][0];
				nex.y = now.y + dir[i][1];
				nex.step = now.step + 1;
				q.push(nex);
			}			
		}

	}
	if (flag == 0)
		printf("Case #%d: No\n", c);
	else
		printf("Case #%d: Yes\n", c);
}
int main()
{
	int n;
	scanf("%d",&n);
	int i;
	for (i = 1; i <= n; i++)
	{
		for (int x = 0; x < 8; x++)
		{
			scanf("%s",matrix[x]);
		}
		for (int x = 0; x < 8; x++)
		{
			for (int y = 0; y < 8; y++)
			{
				if (matrix[x][y] == 'U')
				{
					sx = x, sy = y;//左下角方格,起点
					
				}
				if (matrix[x][y] == 'A')
				{
					ex = x, ey = y;//右上角方格,终点
				}
				
			}
		}		
		BFS(i);			
	}	
	return 0;
}

这个代码是正确的

改正的代码如下:不得不说bfs的思想很有趣,step==8的判断大大缩短了时间,并且在代码里没有直接判断下一步可不可以走,而是先做一个基本的判断(下标不越界就可以了),在走到是S的坐标时,直接跳过continue,这样就不用每次都要算change1(),导致超时

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<cstring>
#include<queue>
//!!这题注意所有做过的点都可以再走一遍,所以不需要判断重复,要根据什么来判断有没有走完呢,到A最少需要8步,走完8步也说明一定可以到达A,只要判断step是否等于8就可以了。
using namespace std;
char matrix[8][8];
char matrix1[8][8];//matrix的替补
int inq[8][8] = { 0 };//表示inq[i][j]有没有进入队列
int X[9] = { 0,0,0,1,-1,1,-1,1,-1 };//增量数组
int Y[9] = { 0,1,-1,0,0,-1,1,1,-1 };//注意也可以选择不走路
struct node {
	int x;
	int y;
	int step;
}S, T, now,nex;
int level;
int judge(int x, int y)//判断坐标(x,y)是否可以入队,x表示行,y表示列
{
	if (x < 0 || y < 0 || x >= 8 || y >= 8) return 0;//如果越界,不再入队
	if (matrix[x][y] == 'S') return 0;	//如果为石头.不再入队
	return 1;//不然就可以访问
}
void change()
{
	for (int i = 7; i >= 0; i--)
	{
		for (int j = 0; j < 8; j++)
		{
			matrix[i][j] = matrix[i - 1][j];
		}
	}
	matrix[1][7] = '.';
	for (int j = 0; j < 7; j++)
		matrix[0][j] = '.';
	matrix[0][7] = 'A';
}
void BFS(int c)
{
	int flag = 0;
	queue < node> q;//定义队列
	S.step = 0;
	q.push(S);
	level = 0;
	while (!q.empty())
	{
		node now = q.front();
		if (now.step != level)
		{
			change();//注意并不是时刻要change,只在一层遍历完后才change
			level = now.step;
		}
		if (matrix[now.x][now.y] == 'S')//在这里判断如果是S,那么不需要直接判断下一个点就好了
		{
			q.pop();
			continue;
		}
		if (now.step == 8)
		{
			flag = 1;
			break;
		}
		q.pop();
		for (int i = 0; i < 9; i++)
		{
			int newX = now.x + X[i];
			int newY = now.y + Y[i];
			if (judge(newX, newY))//如果可以入队
			{
				nex.x = newX;
				nex.y = newY;
				nex.step = now.step+1;
				q.push(nex);
			}
		}
		
	}

	if (flag == 0)
		printf("Case #%d: No\n", c);
	else
		printf("Case #%d: Yes\n", c);
	
}
int main()
{
	int n;
	scanf("%d", &n);
	int i;
	for (i = 1; i <= n; i++)
	{
		for (int x = 0; x < 8; x++)
		{
			scanf("%s", matrix[x]);			
		}
		for (int x = 0; x < 8; x++)
		{
			for (int y = 0; y < 8; y++)
			{
				if (matrix[x][y] == 'U')
				{
					S.x = x, S.y = y;//左下角方格,起点

				}
				if (matrix[x][y] == 'A')
				{
					T.x = x, T.y = y;//右上角方格,终点
				}

			}
		}
		BFS(i);
	}


	return 0;
}

问题 C: 【宽搜入门】8数码难题
大神的分析帖:
https://blog.csdn.net/u012283461/article/details/79078653

因为BFS缓了好几天orz,我决定满满把这些题目和题解看了,目前就先这样叭

上一篇:PTA 题解2


下一篇:CF 675D——Tree Construction——————【二叉搜索树、STL】