复习
栈和队列的概念
- 栈是限定仅在表头进行插入和删除操作的线性表(先进后出)。
- 队列是只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
树
- 树是一种数据结构,它是由
n
(
n
≥
1
)
n (n\geq1)
n(n≥1) 个有限节点组成一个具有层次关系的集合。把它叫做
“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的
特点:
• 每个节点有零个或多个子节点
• 没有父节点的节点称为根节点
• 每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子
树
• 通过不停的试探去寻找解的一种算法。与其说是一种算法,不如说是一种方法。
• 基础的方法有暴力的搜索法,深搜,广搜三种。
• 更高级的有IDDFS,DBFS,A*,IDA*等等。
1.1、深度优先搜索(dfs)
1.1.1、概念
“一条道走到黑”、 “走不了了再倒回去。”
算法过程:
VOID DFS(状态 A)
- 判断当前的状态是否合法。合法则继续执行,否则则回到上次调用。
- 先下走一层,也就是调用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 N∗N 的棋盘上放置 N ( n ≤ 13 ) N(n\leq13) N(n≤13) 个皇后而彼此不受攻击(即在棋盘的任一行,任一列和任一对角线上不能放置 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;
- 判断对角线冲突时,有一个特殊的技巧:
- 由于每一条主对角线 ( x − y ) (x-y) (x−y) 的值是一定的,每一条副对角线 ( x + y ) (x+y) (x+y)的值是一定的。所以就可以用 ( x + y ) (x+y) (x+y) 的值表示副对角线, ( x − y ) (x - y) (x−y)的值表示主对角线。(于是就和处理列的情况一样了!)
- 假设我们把第 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
5∗5 的国际象棋棋盘,国际象棋的马同样是走“日”字。
问,如果一个马,从第一个格子开始走,那么走遍整个
5
∗
5
5*5
5∗5 的棋盘的方案,有多少种?并且输出方案数。
思路:
- 把问题抽象出来,就是有一只马要对整个图进行一次遍历,不重不漏。
- 其次考虑这个马的走法( 8 8 8 种)。
- 接着如何去在程序里面表现出这个棋盘。
- 最后就是套用回溯法的主要结构。
1.1.3、DFS大体框架
- 试探节点A
- A是否满足在这个图(或树)中
- 如果在,标记A如果已经被试探过的话,所影响的各种值;
- 紧接着,去试探所有的A可以达到的节点;
- 等待所有的都执行完之后,还原标记A
1.1.4、剪枝
优化搜索的思路 — 减小搜索树的大小
方法:
- 改变搜索顺序
- 剪枝:最优化剪枝 & 可行性剪枝
题目1
N
∗
M
N * M
N∗M 的迷宫中给定起点
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)文章来源:https://www.toymoban.com/news/detail-433331.html
分析文章来源地址https://www.toymoban.com/news/detail-433331.html
- 二分是否可行?
no - 如何枚举?
所有小木棍长度中最大的那个数开始枚举,下一次枚举的数为所有小木棍长度总和的因子。 - 如何拼接?
先拼接长的(搜索树的规模变小) - 剪枝
- 如果第 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;
}
到了这里,关于竞赛知识点4【搜索】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!