当前位置: 首页 > news >正文

专题 解空间的一种遍历方式:深度优先(Depth First)

概念解释

定义

        首先需要知道解空间是什么。概括地,解空间是一个逻辑结构,为实际问题中的解可能存在的逻辑区域。

        深度优先,则是一种非常常见、应用非常广泛的一个遍历方式。在各种各样的解空间中,包括线性空间, 树结构,图结构中都可以用到深度优先的一种方式。

        自然,在线性序列中寻找解的深度优先,称为深度优先搜索(Depth First Serach);在树与图上称为深度优先遍历(其实本质是一样的)。

特征 

        深度优先,这限定了这个思想是有优先级的:在面临决策时,往往朝着一个方向深入,直到这个方向上再无可能存在可行解,才会返回。用古话讲:

“ 不撞南墙不回头。”

算法分析

        通常,这种深度优先的思想可以借助于一种物理模型来实现:栈(FILO)。更常用的,则是借助递归这一逻辑结构来实现的。本文不做区分,介绍DFS也就介绍了递归。(本栏目介绍DP而不介绍递推。)

        递归,通俗理解,为自调用函数。顾名思义,自己调用自己的函数。我们需要知道这么几个特性:

  • 栈帧。这是计算机储存某一层次递归时的信息的位置结构;
  • 返回,又称回溯。这是程序执行到“无路可走”的时候将会返回上一层的栈帧,并提取上一层的信息。
  • 搜索树。这是一个解空间,但是,实际情况是:这个树结构并不客观存在,但是在DFS运行过程中体现的一种逻辑结构
  • 状态。这个在研究DP时会着重提到,但是在DFS的概念里,我们认为:搜索树上的一个节点代表着一个状态。事实上,关于DP的许多概念在DFS里也是有体现的,这里不展开了。
  • 剪枝。这是搜索算法特有的一种手段。由于大部分情况下,搜索算法本质上是一种暴力枚举,这导致时间复杂度通常不是多项式级别的。相对于搜索树,我们“剪去不可能达到的枝叶”,等效于排除不可能的状态,这是搜索算法生命力旺盛的原因之一:剪枝会使时间复杂度趋近于多项式复杂度。

        下面介绍几个例题,加深对DFS的理解。

luogu.B3621 枚举元组
luogu.B3622 枚举子集(递归实现指数型枚举)

luogu.P10448 组合型枚举

B3623 枚举排列(递归实现排列型枚举)

        这四道题是计数问题的基石。我们直接对后三道题给出分析:

一、参数设计对比

  1. 子集枚举‌(B3622):

    • 仅需x记录当前处理位置
    • a[]数组存储选择状态(0/1)
    • mark数组(实际未使用)
  2. 组合枚举‌(B10448):

    • x记录已选元素数量
    • last保证组合升序(关键去重参数)
    • mark数组防止元素重复使用
  3. 排列枚举‌(B3623):

    • x记录当前填充位置
    • mark数组确保元素不重复
    • 注意:代码中a[x]=i被重复赋值(第二处是冗余操作)

二、递归结构差异

子集枚举(n=3示例):
dfs(1)
├─ a[1]=0 → dfs(2)
│   ├─ a[2]=0 → dfs(3)
│   │   ├─ a[3]=0 → 输出NNN
│   │    └─ a[3]=1 → 输出NNY
│    └─ a[2]=1 → dfs(3)
│       ├─ a[3]=0 → 输出NYN
│        └─ a[3]=1 → 输出NYY
└─ a[1]=1 → dfs(2)├─ a[2]=0 → dfs(3)│   ├─ a[3]=0 → 输出YNN│   └─ a[3]=1 → 输出YNY└─ a[2]=1 → dfs(3)├─ a[3]=0 → 输出YYN└─ a[3]=1 → 输出YYY

特点‌:二叉树结构,每个节点分两支(选/不选)

组合枚举(n=3,m=2示例):
dfs(1,1)
├─ i=1: mark[1]=1 → dfs(2,1)
│   ├─ i=2: mark[2]=1 → 输出1 2
│    └─ i=3: mark[3]=1 → 输出1 3
├─ i=2: mark[2]=1 → dfs(2,2)
│    └─ i=3: mark[3]=1 → 输出2 3
└─ i=3: 不执行(start=3但n=3)

特点‌:通过last限制选择范围,避免[1,2]和[2,1]重复      

排列枚举(n=2,k=2示例): 
dfs(1)
├─ i=1: mark[1]=1 → dfs(2)
│    └─ i=2: mark[2]=1 → 输出1 2
└─ i=2: mark[2]=1 → dfs(2)└─ i=1: mark[1]=1 → 输出2 1

特点‌:每次循环从1开始,用mark保证不重复。

三、关键逻辑区别

维度子集枚举组合枚举排列枚举
选择策略每个元素独立决策从剩余元素中选择全排列
去重方式无(自然不重复)last参数跳过已处理元素mark数组标记已使用元素
递归参数仅需位置x需要x和last仅需位置x
输出特征所有子集(2^n种)C(n,m)种组合n!或P(n,k)种排列

参考程序

//B3621 枚举元组
#include<iostream>
using namespace std;
int a[10],n,k;
bool mark[10];
void dfs(int x)
{if(x>n) {for(int i=1;i<=n;i++) cout<<a[i]<<' ';cout<<endl;return;}for(int i=1;i<=k;i++)a[x]=i,dfs(x+1);}
int main()
{cin>>n>>k;dfs(1);return 0;
}

 

//B3622 枚举子集(递归实现指数型枚举)
#include<iostream>
#include<vector>
using namespace std;
int n;
int a[15];
void dfs(int x)
{if(x>n) {for(int i=1;i<=n;i++) if(a[i]) cout<<'Y';else cout<<'N';cout<<endl;return;}a[x]=0;dfs(x+1);a[x]=1;dfs(x+1);
}
int main()
{cin>>n;dfs(1);return 0;
}
//P10448 组合型枚举
#include<iostream>
using namespace std;
int n,m,a[30];
bool mark[30];
void dfs(int x,int last)
{if(x>m){for(int i=1;i<=m;i++) cout<<a[i]<<' ';cout<<endl;return;}for(int i=last;i<=n;i++)if(!mark[i]){mark[i]=1;a[x]=i;dfs(x+1,i);mark[i]=0;}
}
int main()
{cin>>n>>m;dfs(1,1);return 0;
}

 

//B3623 枚举排列(递归实现排列型枚举)
#include<iostream>
using namespace std;
int n,k;
int a[15];
bool mark[15];
void dfs(int x)
{if(x>k){for(int i=1;i<=k;i++)cout<<a[i]<<' ';cout<<endl;return;}for(int i=1;i<=n;i++)if(!mark[i]){mark[i]=1;a[x]=i;dfs(x+1);a[x]=i;mark[i]=0;}
}
int main()
{cin>>n>>k;dfs(1);return 0;
}

细节实现

        弄清子集、排列、组合这些数学的意义。子集是 2^{n} 级别的,排列是 A^{n}_{m} 的,组合是C^{m}_{n} 的。组合不考虑顺序,也就是不同顺序的不算做不同的答案;而排列正好相反。另外,在设计DFS函数时,可以先从边界条件入手,抓住递归的特性(解决小问题,逐步扩大规模),然后从状态转移的角度入手。

总结归纳

        期待再次更新。

http://www.dtcms.com/a/289263.html

相关文章:

  • 【unitrix】 6.9 减一操作(sub_one.rs)
  • Go语言的函数
  • qcow2磁盘虚拟机的使用
  • Spring Cloud Gateway 电商系统实战指南:架构设计与深度优化
  • Work SSD基础知识
  • 数列-冒泡排序,鸡尾酒排序
  • LINUX(三)文件I/O、对文件打开、读、写、偏移量
  • 什么是 ELK/Grafana
  • Cosmos:构建下一代互联网的“区块链互联网
  • roboflow使用教程
  • GaussDB 数据库架构师修炼(七) 安全规划
  • C51单片机学习笔记——定时器与中断
  • Image Processing 【Normlize和DeNormlize】
  • 【Linux】3. Shell语言
  • Oracle触发器:数据世界的“隐形守护者“
  • EXPLAIN 用法详解(表格)
  • 数据结构-线性表顺序表示
  • 【Linux内核模块】导出符号详解:模块间的“资源共享”机制
  • 子查询转连接查询
  • 30天打牢数模基础-模糊综合评价讲解
  • Vue基础(21)_Vue生命周期
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 用户注册实现
  • 《拆解WebRTC:NAT穿透的探测逻辑与中继方案》
  • 力扣49:字母异形词分组
  • 处理Electron Builder 创建新进程错误 spawn ENOMEM
  • 下载win10的方法
  • 构建一个简单的Java框架来测量并发执行任务的时间
  • Linux安装jdk和maven教程
  • 论文解读:基于时域相干累积的UWB Radar 生命体征检测
  • PyTorch里的张量及张量的操作