搜索算法

DFS

为对一组数进行 全排列
深度优先搜索

#include<stdio.h>
int a[10], book[10],n;

void dfs(int step)
{
	int i;
	if (step == n + 1)	//如果已经选择完毕
	{	//则打印,所选数据
		for (i = 1; i <= n; i++)
			printf("%d ", a[i]);
		printf("\n");
		return;	//一定要立即return,不然就会无限制地运行下去
	}
	
	for (i = 1; i <= n; i++)	//选择数
	{
		if (book[i] == 0)	//如果这个数是可选的
		{
			a[step] = i;	//第step个位置,选择的数i
			book[i] = 1;	//因为上面a[]已选,则把这个i标记为不可选

			dfs(step + 1);	//选择下一个数
			book[i] = 0;	//将上面选择的数标记为可选,为下一次选数做准备
		}
	}
	return;
}

int main()
{
	scanf("%d", &n);
	dfs(1);
	return 0;
}

深度优先搜索的关键在于解决 “当下该如何做” 。
至于下一步怎么做是和 “当下怎么做” 是一样的

深度优先搜索的基本模型:

void dfs(int step)
{
	判断边界,并进行一些操作
	
	尝试每一种可能 for (i = 1; i <= n; i++)
	{
		继续下一步 dfs(step + 1);
	}
	
	返回
}

每一种尝试就是一种扩展


题目1

[ ][ ][ ]+[ ][ ][ ]=[ ][ ][ ]
找九个互不相同的数,使上式成立

#include<stdio.h>
int a[10], book[10], total = 0;
void dfs(int step)
{
	int i;
	if (step == 10)	//如果选择完毕
	{
		if (a[1] * 100 + a[2] * 10 + a[3] + a[4] * 100 + a[5] * 10 + a[6] == a[7] * 100 + a[8] * 10 + a[9])
		{
			total++;
			printf("%d%d%d+%d%d%d+%d%d%d\n", a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]);
		}
		return;	//返回之前的一步,最近调用的地方
	}

	//此刻进行第step的选择
	for (i = 1; i <= 9; i++)	//按照1/2/3···牌的顺序选择
	{
		if (book[i] == 0)	//判断i这个数是否还可以被选
		{
			a[step] = i;	//选择i
			book[i] = 1;	//表示i已经被选了,不能再选了
			dfs(step + 1);	//进行下一选择
			book[i] = 0;	//把刚刚选的标记删除,进行下一次尝试
		}
	}
	return;
}

int main()
{
	dfs(1);		//对第一个数进行选择
	printf("total=%d", total / 2);	//因为会有相同的,比如A+B=C和B+A=C相同
	return 0;
}

这个代码中最为关键的还是dfs那一部分。
dfs的思想方法
简直牛逼的不行

走迷宫

主要思想
深度优先,先走一边,然后一直尝试,直到走不通了,就层层回溯,回到最初的位置,然后换方向走

就是先判断这个点是不是想要走到的点
如果不是,则进行走到下一个点
并递归调用dfs,以对下一个点进行分析,操作

//当前点的x,y坐标,以及已经走过的步数step
void dfs(int x, int y, int step)	
{
	//判断是否找到了
	if (x == p && y == q)	//如果找到了
	{
		if (step < min)	//看是不是比最小的还要小
			min = step;
		return;	//这里的返回很重要
	}
	//如果没有找到,则找出下一步可以走的地方
	int next[4][2] = { {0,1},	//向右走
						{1,0},	//向下走
						{0,-1},	//向左走
						{-1,0} };	//向上走
	for (k = 0; i < 4; k++)
	{
		tx = x + next[k][0];	//下一步的坐标
		ty = y + next[k][1];

		if (tx<1 || tx>n || ty<1 || ty>m)	//如果超界直接返回
			continue;

		//判断是否为障碍物,或者已经走过了
		if (a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;	//标记这里已经走过了
			dfs(tx, ty, step + 1);	//进行下一步的走
			book[tx][ty] = 0;	//尝试结束,也就是退到了这个点的上一个位置
		}
	}
	return;
}

完整代码:

#include<stdio.h>

int n, m, p, q, min = 999999999;
int a[51][51], book[51][51];

//当前点的x,y坐标,以及已经走过的步数step
void dfs(int x, int y, int step)
{
	//判断是否找到了
	int tx, ty, k;
	if (x == p && y == q)	//如果找到了
	{
		if (step < min)	//看是不是比最小的还要小
			min = step;
		return;	//这里的返回很重要
	}
	//如果没有找到,则找出下一步可以走的地方
	int next[4][2] = { {0,1},	//向右走
						{1,0},	//向下走
						{0,-1},	//向左走
						{-1,0} };	//向上走
	for (k = 0; k < 4; k++)
	{
		tx = x + next[k][0];	//下一步的坐标
		ty = y + next[k][1];

		if (tx<1 || tx>n || ty<1 || ty>m)	//如果超界直接返回
			continue;

		//判断是否为障碍物,或者已经走过了
		if (a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;	//标记这里已经走过了
			dfs(tx, ty, step + 1);	//进行下一步的走
			book[tx][ty] = 0;	//尝试结束,也就是退到了这个点的上一个位置
		}
	}
	return;
}

int main()
{
	int i, j, startx, starty;
	scanf("%d %d", &n, &m);
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
		}
	}
	scanf("%d%d%d%d", &startx, &starty, &p, &q);

	book[startx][starty] = 1;
	dfs(startx, starty, 0);
	printf("%d", min);
	return 0;
}

BFS

广度优先搜索
宽度优先搜索

走迷宫

通过一层一层的扩展,来找到想要找到的出路
扩展时每发现一个点就把这个点加入到队列中,直到走到出路为止

核心思想:
利用队列,实现层层扩展

核心代码:

while (head < tail)	//当队列不为空的时候循环
	{
		//枚举四个方向
		for (k = 0; k < 4; k++)
		{
			tx = que[head].x + next[k][0];	//下一个点是从head扩展出来的
			ty = que[head].y + next[k][1];

			//判断是否越界
			if (tx<1 || tx>n || ty<1 || ty>m)
				continue;
			//判断是否是障碍物或者已经走过了
			if (a[tx][ty] == 0 && book[tx][ty] == 0)
			{
				//把这个点标记为已走
				//BFS中每个点只入队一次,因此,book一旦标记,就不再还原
				book[tx][ty] = 1;

				//将走到的点插入到队列中
				que[tail].x = tx;
				que[tail].y = ty;
				que[tail].f = head;	//因为这个点是从head扩展出来的,因此父亲就是head
				que[tail].s = que[head].s + 1;

				tail++;
			}
			if (tx == p && ty == q)//如果到达终点了,直接跳出
			{
				flag = 1;
				break;
			}
		}
		if (flag == 1)
			break;
		
		head++;		//当一个点的扩展结束了,head++才能对后面的点再扩展
	}
#include<stdio.h>

struct node
{
	int x;	//横坐标
	int y;	//纵坐标
	int f;	//父亲在队列中的编号,这个是为输出路径而准备的
	int s;	//步数
};
int main()
{
	int i, j, k, n, m;

	node que[2501];	//因为地图大小不超过50*50,因此队列扩展不会超过2500
	int head, tail;	//队首,队尾

	int a[51][51] = { 0 };	//地图
	int book[51][51] = { 0 };	//book的作用是记录哪些点已经在队列中,防止一个点被重复扩展,并全部初始化为0

	int next[4][2] = { {0,1},	//向右走
						{1,0},	//向下走
						{0,-1},	//向左走
						{-1,0} };	//向上走

	int startx, starty;	//开始的位置的横纵坐标
	int p, q;
	int tx, ty;	//下一个点的横纵坐标
	int flag;

	scanf("%d%d", &n, &m);	//输入地图
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
		}
	}

	//输入开始的横纵坐标
	scanf("%d%d%d%d", &startx, &starty, &p, &q);

	//最开始的时候需要进行队列初始化,即将队列设置为空
	head = 1;
	tail = 1;

	//在队列中设置开始时位置坐标以及路径长度
	que[tail].x = startx;
	que[tail].y = starty;
	que[tail].s = 0;
	que[tail].f = 0;
	tail++;
	book[startx][starty] = 1;

	flag = 0;	//用来记录是否到达目标点,0表示未到,1表示到达
	while (head < tail)	//当队列不为空的时候循环
	{
		//枚举四个方向
		for (k = 0; k < 4; k++)
		{
			tx = que[head].x + next[k][0];	//下一个点是从head扩展出来的
			ty = que[head].y + next[k][1];

			//判断是否越界
			if (tx<1 || tx>n || ty<1 || ty>m)
				continue;
			//判断是否是障碍物或者已经走过了
			if (a[tx][ty] == 0 && book[tx][ty] == 0)
			{
				//把这个点标记为已走
				//BFS中每个点只入队一次,因此,book一旦标记,就不再还原
				book[tx][ty] = 1;

				//将走到的点插入到队列中
				que[tail].x = tx;
				que[tail].y = ty;
				que[tail].f = head;	//因为这个点是从head扩展出来的,因此父亲就是head
				que[tail].s = que[head].s + 1;

				tail++;
			}
			if (tx == p && ty == q)//如果到达终点了,直接跳出
			{
				flag = 1;
				break;
			}
		}
		if (flag == 1)
			break;
		
		head++;		//当一个点的扩展结束了,head++才能对后面的点再扩展
	}

	//打印队列中末尾最后一个点的步数(这时候就是到达终点的点)
	printf("%d", que[tail - 1].s);	//tail指向的队列中最后一个元素的下一个位置。因此需要减1
	return 0;
}

炸弹人

BFS大致的模板

while (head < tail)
{
	for (k = 0; k < 4; k++)
	{
		tx = que[head].x + next[k][0];
		ty = que[head].y + next[k][1];

		if (tx<1 || tx>n || ty<1 || ty>m)
			continue;

		if (a[tx][ty] == '.' && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			que[tail].x = tx;
			que[tail].y = ty;

			//在这里进行你先要的操作

			tail++;
		}
	}
	head++;
}

DFS
这个与为了找到终点是不一样的
这个是为遍历图中的每一个点
因此,需要的将所有的点都做一个标记
而且不能清除这个标记,不然没有办法知道,是否遍历完所有的点

void dfs(int x, int y)
{
	//操作
	for (k = 0; k < 4; k++)
	{
		tx = x + next[k][0];
		ty = y + next[k][1];

		if (tx<0 || tx>n - 1 || ty<0 || ty>m - 1)
			continue;
		if (a[tx][ty] == '.' && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty);
			//book[tx][ty] = 0;	因为是遍历图中的每一个点,与找到终点不一样,所以这个步骤删除
		}
	}
	return;
}

上述代码就是着色法的实现:
着色法:以某个点为源点对其临近的点进行着色


求一个图中有多少个独立子图
对图中每一个点进行一次DFS,并对其进行不同的着色

Floodfill漫水填充法
Floodfill算法在计算机图形学中有着非常广泛的应用。比如图像切割,物体识别
例如:Windows下画图软件的油漆桶就是基于这个算法


水管工游戏

在这里插入图片描述
输出通路,需要一个栈
每次进行尝试的时候入栈
如果尝试不成功,则将这个不成功的出栈

最后栈中保留的,就是正确的路线
当成功的时候,就输出栈中路线就可以了

这个我觉得收获最大是
形成路线
至于水管的选择还是dfs
不过,在选择水管的时候,可以选择进行多个dfs,给我带来了启发

#include<stdio.h>
int a[51][51];
int book[51][51];
int n, m, flag = 0;

struct note
{
	int x;
	int y;
}s[100];	//形成一个栈
int top = 0;

void dfs(int x, int y, int front)//x,y代表位置,front代表进水口方向
{
	int i;
	//判断是否到达终点
	//判断终点一定要在判断越界前面
	if (x == n && y == m + 1)
	{
		flag = 1;
		for (i = 1; i <= top; i++)
			printf("(%d,%d)", s[i].x, s[i].y);
		return;
	}
	if (x < 1 || x>n || y<1 || y>m)
	{
		return;
	}
	if (book[x][y] == 1)	//判断这个管道是不是已经使用过
		return;
	book[x][y] = 1;//如果没有使用过,标记的当前管道

	//将当前尝试入栈
	top++;
	s[top].x = x;
	s[top].y = y;


	//枚举当前管道的每一种摆放方式。根据进水口的方向来决定管道方向
	
	//当前管道是直道
	if (a[x][y] >= 5 && a[x][y] <= 6)
	{	//front代表进水口方向
		if (front == 1)
		{
			dfs(x, y + 1, 1);
		}
		if (front == 2)
		{
			dfs(x + 1, y, 2);
		}
		if (front == 3)
		{
			dfs(x, y - 1, 3);
		}
		if (front == 4)
		{
			dfs(x - 1, y, 4);
		}
	}

	//当前是弯道的时候
	//每个弯道有两种状态,因此对应于需要两个dfs
	if (a[x][y] >= 1 && a[x][y] <= 4)
	{
		if (front == 1)		//如果进水口是左边
		{
			dfs(x + 1, y, 2);	//那么我下一个位置进水口只有上面和下面
			dfs(x - 1, y, 4);
		}
		if (front == 2)
		{
			dfs(x, y + 1, 1);
			dfs(x, y - 1, 3);
		}
		if (front == 3)
		{
			dfs(x - 1, y, 4);
			dfs(x + 1, y, 2);
		}
		if (front == 4)
		{
			dfs(x, y + 1, 1);
			dfs(x, y - 1, 3);
		}
	}
	book[x][y] = 0;	//取消标记	这是因为,这个尝试失败了,要换一种了

	top--;	//将当前尝试出栈
	return;
}

int main()
{
	int i, j, num = 0;
	scanf("%d%d", &n, &m);
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
		}
	}
	dfs(1, 1, 1);
	if (flag == 0)
	{
		printf("impossible\n");
	}
	else
		printf("找到铺设方案\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值