【算法基础】深搜(上)

简介: 【算法基础】深搜(上)


引语:

本篇文章从迭代,递归,再到深搜,由浅入深结合例题介绍。如果是零基础的,建议从头看完,这样到后面更好理解,如果递归学的较好的话也可以跳过前面的递归部分。

在各种算法竞赛或者面试中,总会有一些题目要求我们给出某个问题的各种方案以及方案的个数,对于数量级比较小的题目,我们可以采用枚举之类的暴力解法,但是对于数量级到达n的三次方及以上的题目甚至是阶乘级的题目就要小心了,因为暴力求解很可能超时。

所以,我们可以用试探性解法,当某一条支路提前确定走不通的时候,我们就不必继续向下进行了。也可以理解为“不撞南墙不回头”,或者“先把一条路走到黑”。

虽然看起来比暴力枚举更快一点,但是《算法竞赛入门经典》这本书将这种方法一并归到“暴力求解法”这一章。

回顾

首先,我想先从“逐步生成结果”开始讲起。

刚开始学C语言的时候,我们会学“斐波那契数列”,这就是典型的“逐步生成”思想,也可以理解为迭代

我相信斐波那契大家肯定很熟悉了,这里就不在介绍了。

解决简单情况下的问题:上楼梯

题意:有一个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶,2阶,3阶。

请实现一个方法,计算小孩有多少种上楼梯的方式。

为了防止溢出,请将结果Mod1000000007。 给定一个正整数,请返回一个数,代表上楼的方式数。保证n小于等于100000.

由题意,我们可以做出如下图所示的推理:


递推:

#include<stdio.h>
#define mod 1000000007
int main(){
  int x1 = 1, x2 = 2, x3 = 4, n;
  long long ans = 0;
  scanf("%d",&n);
  if(n < 0) ans = 0;
  else if(n == 0 || n == 1) ans = x1;
  else if(n == 2) ans = x2;
  else if(n == 3) ans = x3;
  for(int i = 4; i <= n; i++){
    int t = x1;
    x1 = x2;  x2 = x3;
    x3 = ((x1+x2)%mod+t)%mod;
    ans = x3;
  } 
  printf("%d\n",ans);
  return 0;
}

递归:

long f1(int n){
  if(n < 0) return 0;
  if(n == 0 || n == 1) return 1;
  if(n == 2) return 2;
  return f1(n-1)%mod + f1(n-2)%mod + f1(n-3)%mod;
}

如果大家数学基础比较好,也可以利用递推公式,直接得出封闭解。由于我数学推理不是很好,在这里就不展示了。

推广到稍微复杂的问题:机器人走方格

有一个X*Y的网格,一个机器人只能走格点且只能向右或向下走,要从左上角走到右下角。

请设计一个算法,计算机器人有多少种走法。

给定两个正整数int x,int y,请返回机器人的走法数目。

保证x+y小于等于12。
由题意我们可以推出:

  • 当x或者y为1时,走法都是1种。
  • 当x或者y为2,另一个为1时,也就是(1,2)或者(2,1),走法也都是1种。
  • 当x和y都为2时,走法是两种 ……

顺着思路往下想,我们可以画出这样一幅示意图

从这幅图我们可以发现:

在x+y>=3时,当前状态的解都为(x-1,y)状态的解和(x,y-1)的解的和
也就是f(x,y)=f(x-1,y)+f(x,y-1)
因为在任何一种状态,我们都有两种选择,下或者右,如果现在向下走,那么下一种状态的解即为(x,y-1)的解,如果向右,那么下一种状态为(x-1,y)的解。

所以,我们同样可以用递推或者递归来写代码~

递推:

建立一个二维数组存放每一个状态对应的解,逐步迭代得到所有解

#include<stdio.h>
int solve1(int x, int y){
  int a[x+1][y+1];
  for(int i = 1; i <= x; i++) a[i][1] = 1;//当x或y为1时,解法都是一种
  for(int i = i; i <= y; i++) a[1][i] = 1;
  for(int i = 2; i <= x; i++){
    for(int j = 2; j <= y; j++){
      a[i][j] = a[i-1][j]+a[i][j-1];  //利用递推关系迭代出所有的解
    }
  }
  return a[x][y];
} 
int main(){
  int x,y;
  scanf("%d%d",&x,&y);
  printf("%d\n",solve1(x,y));
  return 0;
}

递归:

int solve2(int x, int y){
  if(x == 0 || y == 0 || x == 1 || y == 1) return 1;
  return solve2(x-1,y)+solve2(x,y-1);
}

由以上两道例题我们发现,递推和递归都是对于需要迭代问题的解法

区别在于:递推是正着思考正着写,递归是正着思考倒着写

递归对于这类问题的思考的锻炼是非常有帮助的。

还有一道更难一点的题:硬币表示(点击阅读我的另一篇文章)

相关文章
|
传感器 编解码 区块链
Google Earth Engine(GEE)——Landsat8/modis/sentinel2 NDVI时序影像差异对比分析图表
Google Earth Engine(GEE)——Landsat8/modis/sentinel2 NDVI时序影像差异对比分析图表
430 0
|
Linux API 调度
关于在Linux内核中使用不同延迟/休眠机制 【ChatGPT】
关于在Linux内核中使用不同延迟/休眠机制 【ChatGPT】
|
存储 SQL NoSQL
深入理解数据库管理系统(DBMS)及其在现代应用中的重要性
一、引言 随着信息技术的飞速发展,数据已成为现代社会中不可或缺的资源
1479 3
Echarts参数属性学习xAxis与yAxis演示案例
Echarts参数属性学习xAxis与yAxis演示案例
381 0
Echarts参数属性学习xAxis与yAxis演示案例
|
负载均衡 网络协议 算法
【springcloud】Ribbon详解
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。简单点说,其主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接,权重等)去连接这些机器。
528 0
|
NoSQL Linux MongoDB
linux中mongoDB安装
linux中mongoDB安装
620 0
|
JavaScript API 开发者
Vue3自定义hooks
Vue3自定义hooks
215 0
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用问题之如何指定从特定的binlog位置或最新的binlog位置开始读取数据
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
vscode 如何修改c/c++格式化风格,大括号不换行
vscode 如何修改c/c++格式化风格,大括号不换行
|
Linux 网络安全 Apache
使用树莓派搭建个人网站,并发布到外网可访问:实用步骤解析
使用树莓派搭建个人网站,并发布到外网可访问:实用步骤解析
498 0