竞赛知识点4【搜索】

复习

栈和队列的概念

  • 栈是限定仅在表头进行插入和删除操作的线性表(先进后出)。
  • 队列是只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。

  • 树是一种数据结构,它是由 n ( n ≥ 1 ) n (n\geq1) n(n1) 个有限节点组成一个具有层次关系的集合。把它叫做
    “树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的
    特点:
    • 每个节点有零个或多个子节点
    • 没有父节点的节点称为根节点
    • 每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子

    在这里插入图片描述

• 通过不停的试探去寻找解的一种算法。与其说是一种算法,不如说是一种方法。

• 基础的方法有暴力的搜索法,深搜,广搜三种。

• 更高级的有IDDFS,DBFS,A*,IDA*等等。

1.1、深度优先搜索(dfs)

1.1.1、概念

“一条道走到黑”、 “走不了了再倒回去。”

算法过程:

VOID DFS(状态 A)

  1. 判断当前的状态是否合法。合法则继续执行,否则则回到上次调用。
  2. 先下走一层,也就是调用DFS(状态 A + Δ)

1.1.2、例题

1、输出n个数的全排列

1,2,3组成的全排列为:

  • 123
  • 132
  • 213
  • 231
  • 312
  • 321
  • 可以写 n 重 for 循环
  • 可以用 stl 里面的 next_permutation

递归的来想这个问题,以1,2,3,4为例:
• 如果第一个数固定为1的话
• 后面就跟着2,3,4的全排列—— 所以就相当于是原问题的子问题的求解
• 考虑用递归解决

Void dfs(已经固定了前k-1个数剩下的数的全排列,是哪k-1个数通过标记该数字用没用过
来显示,当前这一步的任务就是把第k个数放上去)
{
	如果 已经求出来了 k>n 输出, 返回
 	否则 i从1到n循环
 	如果i没有用过,那么就将i固定在当前位置上,并调用 dfs(k+1)
 	在调用完dfs(k+1)后需要将固定在当前位置上的i拿走
}

void dfs(int dep)
{
	if(dep>n)
	{
		write();
		return;
	}	
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			vis[i]=1;
			a[dep]=i;
			dfs(dep+1);
			vis[i]=0;
		}
	}
}
2、输出n个数中选m个的组合
3、N皇后(8皇后的升级版)

N ∗ N N * N NN 的棋盘上放置 N ( n ≤ 13 ) N(n\leq13) N(n13) 个皇后而彼此不受攻击(即在棋盘的任一行,任一列和任一对角线上不能放置 2 2 2 个皇后),编程求解所有的摆放方法。

在这里插入图片描述

  • 由于皇后的摆放位置不能通过某种公式来确定,因此对于每个皇后的摆放位置都要进行试探和纠正,这就是“回溯”的思想。
    • 在 N N N 个皇后未放置完成前,摆放第 i i i 个皇后和第 i + 1 i+1 i+1 个皇后的试探方法是相同的,因此完全可以采用递归的方法来处理。
    • 由于皇后本身的特殊性质,即一行一列只能有一个皇后,所以我们所要做的就是,从第 0 0 0 行开始摆放,一直摆到第 n – 1 n – 1 n–1 行为止。

关于判断当前皇后可不可以放:

  • 我们是一行一行的放置皇后,所以不需要判断行冲突;
  • 判断列冲突时,可以通过设置一个布尔数组,如果已经有皇后放在那里,就把布尔值设为 1 1 1,如果可以放置并且没有冲突(即布尔值为 0 0 0),就放置当前这个皇后,且设置为 1 1 1
  • 判断对角线冲突时,有一个特殊的技巧:
    1. 由于每一条主对角线 ( x − y ) (x-y) (xy) 的值是一定的,每一条副对角线 ( x + y ) (x+y) (x+y)的值是一定的。所以就可以用 ( x + y ) (x+y) (x+y) 的值表示副对角线, ( x − y ) (x - y) (xy)的值表示主对角线。(于是就和处理列的情况一样了!)
    2. 假设我们把第 c u r cur cur 个皇后放在了 p o s [ c u r ] pos[cur] pos[cur] p o s [ c u r ] pos[cur] pos[cur] 储存了这个值),那么只需判断所检查的从前往后数第 k k k 个皇后有没有冲突就行了。
void dfs(int dep)
{
	if(dep>n)
	{
		write();
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(!vis[i] && !l[i+dep] && !r[i-dep+n])
		{
			vis[i]=1,l[i+dep]=1,r[i-dep+n]=1;
			a[dep]=i;
			dfs(dep+1);
			vis[i]=0,l[i+dep]=0,r[i-dep+n]=0;
		}
	}
}
4、马踏棋盘

给一个 5 ∗ 5 5*5 55 的国际象棋棋盘,国际象棋的马同样是走“日”字。
问,如果一个马,从第一个格子开始走,那么走遍整个 5 ∗ 5 5*5 55 的棋盘的方案,有多少种?并且输出方案数。

思路:

  1. 把问题抽象出来,就是有一只马要对整个图进行一次遍历,不重不漏。
  2. 其次考虑这个马的走法( 8 8 8 种)。
  3. 接着如何去在程序里面表现出这个棋盘。
  4. 最后就是套用回溯法的主要结构。

1.1.3、DFS大体框架

  • 试探节点A
  • A是否满足在这个图(或树)中
  • 如果在,标记A如果已经被试探过的话,所影响的各种值;
  • 紧接着,去试探所有的A可以达到的节点;
  • 等待所有的都执行完之后,还原标记A

1.1.4、剪枝

优化搜索的思路 — 减小搜索树的大小

方法:

  1. 改变搜索顺序
  2. 剪枝:最优化剪枝 & 可行性剪枝

题目1
N ∗ M N * M NM 的迷宫中给定起点 S S S 和终点 D D D ,问你是否能在 T T T 时刻恰好到达终点 D D D?

S.X.
..X.
..XD

分析:

  • 两个点之间是可以重复走的
  • A A A 点走到 B B B 点的奇偶性是不会改变的(剪枝1)
  • T a T_a Ta + T b T_b Tb 大于 T T T ,直接回溯(剪枝2)

题目2
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。(小木棍的数量小于等于 64)

分析

  1. 二分是否可行?
    no
  2. 如何枚举?
    所有小木棍长度中最大的那个数开始枚举,下一次枚举的数为所有小木棍长度总和的因子。
  3. 如何拼接?
    先拼接长的(搜索树的规模变小)
  4. 剪枝
  • 如果第 i i i 个棍子不能拼成假设的长度,则和第 i i i 个棍子相同长度的棍子也是不可能的,可以直接跳过去的。
  • 替换第 i i i 根棍子的第一根(或最后一根)木棒是没用的。
  • 如果某次拼接选择长度为 S S S 的木棒,导致最终失败,则在同一位置尝试下一根木棒时,要跳过所有长度为 S S S 的木棒。
bool dfs(int num,int len,int rest) //剩余的小木棍的数量 长度 还剩余要拼接的
{
	if(num==0 && rest==0) return true;
	if(rest==0) rest=len;
	for(int i=1; i<=n; i++)
	{
		if(vis[i])continue;
		if(a[i]>rest) continue;
		vis[i]=true;
		if(dfs(num-1,len,rest-a[i]))
			return true;
		vis[i]=false;
		if(a[i]==rest||len==rest)	break;
		while(a[i]==a[i+1])i++;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zaiyang遇见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值