搜索详解

搜索

一.dfs和bfs简介

深度优先遍历(dfs)

本质:

遍历每一个点。

遍历流程:

从起点开始,在其一条分支上一条路走到黑,走不通了就往回走,只要当前有分支就继续往下走,直到将所有的点遍历一遍。

剪枝:

如果已经确定这条路没有我们想要的答案,那么就不用继续在这条路上走下去了,于是我们就开始走其他的分支或者往回走,这样节省时间的方法称之为剪枝。

回溯:

当我们一条路走到头,往回走时,就叫做回溯。

恢复现场:

当我们回溯的时候,原来这个图是什么样的,我们还要变回什么样。这样做的目的: 当我们遍历完这条分支,去遍历下一条分支的时候,我们需要保证当前图其他条件的一致性,也就是遍历每一条分支的时候,当前图的状态都是一样的。保证遍历每一条分支的时候都是公平的。

广度优先遍历(bfs)

遍历流程: 逐层逐层的遍历,先遍历第一层,再遍历第二层…,也就是遍历当前节点所能到达的所有子节点。直到遍历所有的点。不存在剪枝,回溯和恢复现场的操作。

对比dfs和bfs

时间复杂度:

dfs: 因为我们需要枚举每一个点,以及每一条边,所示它的时间复杂度为O(n + e) 即点的个数+边的个数

bfs:跟dfs时间复杂度一样,都为O(n + e) 不同的是对每个点的访问顺序是不一样的

用到的数据结构

dfs: stack

bfs: queue

空间复杂度

dfs: O(h) h为树的深度

bfs: O(2^h)

特性

dfs: 不具有最短性

bfs: 具有最短性

二. 树与图的深度优先遍历(dfs)

树与图的深度优先遍历:
树其实也是图的一种
图: 分为有向图和无向图
图的储存:

第一种:邻接矩阵,就是一个二维数组,缺点:当点和边特别多的时候,存不下,一般用的比较少,而且非常浪费空间
第二种:邻接表:由n个单链表组成,也可以用vector动态数组来实现,但vector有很大的缺点,当点和边非常大时,用vector动态数组的方法很容易超时,所以我们常用n个但链表的方式来存储图

邻接表如何存图呢:
假设有这样一个图:
在这里插入图片描述
那么我们可以给每个节点开一个单链表,如下图所示:
在这里插入图片描述
这样我们就把图用邻接表的方法存了下来
树与图深度优先遍历的大致流程:一条路走到黑,直到撞到南墙,走不通了,然后往回走,只要有分支就继续往下走

1.树与图的遍历模板:

邻接表以h数组为表头,使用 e 和 ne 数组分别存储边的终点和下一个节点

#include<iostream>
#include<cstring>
using namespace std;
const int N  = 1e6 + 10;
int h[N], e[N], ne[N], idx, n;//这里跟单链表一样,只不过这里是N个头节点,H[N]
bool vis[N];  //判断是否遍历过
void add(int a, int b)  //邻接表存树与图
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int cur)
{vis[cur] = true;for(int i = h[cur]; i != -1; i = ne[i]){  //遍历树int u = e[i];if(!vis[u]){dfs(u);}}
}
int main()
{int a, b;cin >> n;//初始化memset(h, -1, sizeof h);memset(vis, false, sizeof vis);for(int i = 0; i < n; i++){cin >> a >> b;//建树,双向图add(a, b);add(b, a);}dfs(1);return 0;
}

这样我们就遍历了每个点。

2.树的dfs序

​ 一般来讲,我们在对树进行深度优先遍历时,对于每个节点,在刚进入递归以后以及即将回溯前各记录一次该点的编号,最后产生的长度为2N的节点序列被称为树的dfs序

void dfs(int x)
{a[++m] = x;v[x] = 1;for(int i = h[x]; i != -1; i = ne[i]){int y = e[i];if(v[y])continue;dfs(y);}a[++m] = x;
}

3.树的深度

​ 我们已知根节点的深度为0.若节点x的深度为d[x],则它的子节点的深度为d[y] = d[x] + 1

void dfs(int x)
{v[x] = 1;for(int i = h[x]; i != -1; i = ne[i]){int y = e[i];if(v[y])continue;d[y] = d[x] + 1;dfs(y);}
}

4.连通图的划分

​ 假设从x点开始一次遍历,就会访问x能够到达的所有的点和边,因此,通过多次深度优先遍历,可以划分出一张无向图中的各个连通图。同理,对一个森林进行深度优先遍历,可以划分森林中每棵树

​ cnt表示无向图包含的连通块的个数, v数组标记了每一个点属于哪个连通块

void dfs(int x)
{v[x] = cnt;for(int i = h[x]; i != -1; i = ne[i]){int y = e[i];if(v[y])continue;dfs(y);}
}
for(int i = 1; i <= n; i++){if(!v[i]){cnt++;dfs(i);}
}

三.树与图的广度优先遍历

​ 树与图的广度优先遍历需要使用一个队列来实现。起初,队列中仅包含一个起点。在广度优先遍历过程中,我们不断从队头取出一个节点x,对于x面对的多条分支,将所有x能够达到的下一个节点插入队尾,重复执行上述过程直到队列为空

1.广度优先遍历模板

void dfs()
{memset(d, 0, sizeof d);queue<int> q;q.push(1);d[1] = 1;  //d数组表示节点的深度while(q.size()){    //只要队列不为空int x = q.front();  //取出队头q.pop();for(int i = h[x]; i != -1; i = ne[i]){  //遍历x能够到达的所有下一个节点int y = e[i];if(d[y])continue;  d[y] = d[x] + 1;    //深度+1q.push(y);}}
}

​ 在上面的代码中,d数组表示从起点 1 走到点 x 需要经过的最少点数. 广度优先遍历是一种按照层次顺序进行访问的方法, 它具有如下俩个重要的性质:
1.在访问完所有的第 i 层节点后,才会开始访问第 i + 1 层节点

2.广度优先遍历队列中的元素关于层次满足俩段性和单调性, 即队列中至多包含俩个层次的节点, 其中一部分属于第 i 层, 一部分属于 i + 1 层,并且所有的第 i 层节点都排在第 i + 1 层节点之前

2.拓扑排序

​ 给定你一个无向图,若一个由图中所有点构成的序列A满足:对于图中的每条边 (x, y),x 在A中都出现在y之前,则称A是该有向图的一个拓扑排序

入度: 在有向图中,以节点 x 为终点的有向边的条数被称为 x 的入度

出度: 在有向图中,以节点 x 为起点的有向边的条数被称为 x 的出度

​ 拓扑排序非常简单,我们只需要不断选择图中入度为0的节点 x , 然后把 x 连向的点的入度减1,我们可以结合广度优先遍历的框架来实现:

​ 1.建立空的拓扑排序A。

​ 2.预处理出所有点的入度deg[i],起初把所有入度为0的点入队

​ 3.取出队头节点x,把x加入拓扑序列A的末尾

​ 4.对于x出发的每条边(x, y)把 deg[y] 减 1 。若被减为0, 则把y入队

​ 5.重复3~4过程,直到队列为空,我们便求出了拓扑序列A

void add(int x, int y)  //建边
{e[cnt] = y, ne[cnt] = h[x], h[x] = cnt++;
}
void topsort()
{queue<int> q;for(int i = 1; i <= n; i++)if(deg[i] == 0)q.push(i);  //将入度为0的点加入到队列中while(q.size()){int x = q.front();q.pop();a[++t] = x;  //将x加入到拓扑序中for(int i = h[x]; i != -1; i = ne[i]){int y = e[i];deg[y]--;  //入度--if(deg[y] == 0)q.push(y);//如果入度为0,添加到队列中去}}
}
int main()
{cin >> n >> m;for(int i = 1; i <= m; i++){int x, y;cin >> x >> y;add(x, y);}topsort();for(int i = 1; i <= n; i++)//输出拓扑序printf("%d ", a[i]);cout << endl;
}

四.迭代加深

​ 深度优先搜索每次选定一个分支,不断深入,直至到达递归边界才回溯。这种策略带有一定的缺陷。如果搜索树每个节点的分枝数非常多, 而答案在某个较浅的节点上。如果深搜在一开始选错了分支,就很可能在不包含答案的深层子树上浪费太多的时间

​ 此时,我们可以从小到大限制搜索的深度如果在当前深度限制下找不到答案,就把深度限制增加,重新进行一次搜索,这就是迭代加深的思想。

例题:加成序列(poj2248)

​ 满足如下条件的序列X(序列中元素被标号为1、2、3…m)被称为“加成序列”:

​ 1、X[1]=1

​ 2、X[m]=n

​ 3、X[1]<X[2]<…<X[m-1]<X[m]

​ 4、对于每个 k(2 ≤ k ≤ m)都存在两个整数 i 和 j (1 ≤ i,j ≤ k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。

​ 你的任务是:给定一个整数n,找出符合上述条件的长度m最小的“加成序列”。

​ 如果有多个满足要求的答案,只需要找出任意一个可行解。

搜索框架:依次搜索序列中的每个位置k, 枚举 i 和 j 作为分支,把 X[i] 和 X[j] 的和填到 X[k] 上,然后递归填写下一个位置。

​ 加入以下剪枝:

​ 1.优化搜索顺序:为了让序列中的数尽快逼近n,在枚举 i 和 j 时从大到小枚举

​ 2.排除等效冗余

​ 对于不同的 i 和 j ,X[i] 和 X[j] 可能是相等的,我们可以在枚举是用一个 bool 类型的数组对 X[i] 和 X[j] 进行判重,避免重复搜索同一个和

​ 3.我们可以采用迭代加深的方法进行搜索, 从1开始限制搜索深度,若搜索失败就增加深度限制重新搜索,直到找到一组解时即可输出答案

#include<iostream>
#include<cstring>
using namespace std;const int N = 110;
int a[N];
bool vis[N];
int n, k;
bool dfs(int u, int k)
{//如果达到搜索限制,判断a[u - 1]是否等于nif(u == k)return a[u - 1] == n;//遍历前边的元素for(int i = u - 1; i >= 0; i--){for(int j = i; j >= 0; j--){int s = a[i] + a[j];//如果和已经出现过,或者不满足要求,剪枝掉if(vis[s] || s > n || s <= a[u - 1])continue;a[u] = s;if(dfs(u + 1, k))return true;}}return false;
}
int main()
{a[0] = 1;while(cin >> n && n){int k = 1;while(!dfs(1, k)){ //不断增加搜索限制k直到得到正确的答案memset(vis, false, sizeof vis);k++;}for(int i = 0; i < k; i++)cout << a[i] << ' ';cout << endl;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445226.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python外(5)-for-enumerate()-zip()

for循环小技巧技巧1&#xff1a;enumerate()技巧2&#xff1a;打包两个可遍历数据&#xff0c;一起循环-zip()技巧1&#xff1a;enumerate() 在使用pytorch训练网络的过程中&#xff0c;官方教程给出了 for i, data in enumerate(trainloader, 0): 这涉及到enumerate函数的使用…

特征工程总结

目录1 特征工程是什么&#xff1f; 2 数据预处理   2.1 无量纲化     2.1.1 标准化     2.1.2 区间缩放法     2.1.3 标准化与归一化的区别   2.2 对定量特征二值化   2.3 对定性特征哑编码   2.4 缺失值计算   2.5 数据变换 3 特征选择   3.1 Filter …

Jmeter测试并发https请求成功了

Jmeter2.4 如何测试多个并发https请求&#xff0c;终于成功了借此机会分享给大家 首先要安装jmeter2.4版本的&#xff0c;而且不建议大家使用badboy&#xff0c;因为这存在兼容性问题。对于安装&#xff0c;我就不讲了&#xff0c;我就说说如何测试https&#xff0c;想必大家都…

关系数据库——sql基础1定义

关系数据库标准语言SQL 基本概念 SQL语言是一个功能极强的关系数据库语言。同时也是一种介于关系代数与关系演算之间的结构化查询语言&#xff08;Structured Query Language&#xff09;&#xff0c;其功能包括数据定义、数据查询、数据操纵和数据控制。 SQL的特点&#xff…

libcurl编程

一、curl简介 curl是一个利用URL语法在命令行方式下工作的文件传输工具。它支持的协议有&#xff1a;FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 LDAP。curl同样支持HTTPS认证&#xff0c;HTTP POST方法, HTTP PUT方法, FTP上传, kerberos认证, HTTP上传, 代理服…

大数据学习(09)--Hadoop2.0介绍

文章目录目录1.Hadoop的发展与优化1.1 Hadoop1.0 的不足与局限1.2 Hadoop2.0 的改进与提升2.HDFS2.0 的新特性2.1 HDFS HA2.2 HDFS Federation3. 新一代的资源管理器YARN3.1 MapReduce1.0 缺陷3.2 YARN的设计思路3.3 YARN 体系结构3.4 YARN工作流程3.5 YARN框架与MapReduce1.0框…

Java多线程常用方法

start()与run() start() 启动线程并执行相应的run()方法 run() 子线程要执行的代码放入run()方法 getName()和setName() getName() 获取此线程的名字 setName() 设置此线程的名字 isAlive() 是判断当前线程是否处于活动状态。活动状态就是已经启动尚未终止。 curren…

MachineLearning(2)-图像分类常用数据集

图像分类常用数据集1 CIFAR-102.MNIST3.STL_104.Imagenet5.L-Sun6.caltech-101在训练神经网络进行图像识别分类时&#xff0c;常会用到一些通用的数据集合。利用这些数据集合可以对比不同模型的性能差异。下文整理常用的图片数据集合&#xff08;持续更新中)。基本信息对比表格…

Linux网络编程实例详解

本文介绍了在Linux环境下的socket编程常用函数用法及socket编程的一般规则和客户/服务器模型的编程应注意的事项和常遇问题的解决方法&#xff0c;并举了具体代 码实例。要理解本文所谈的技术问题需要读者具有一定C语言的编程经验和TCP/IP方面的基本知识。要实习本文的示例&…

python的命令解析getopt.getopt()函数分析

【转自http://hi.baidu.com/javalang/blog/category/Python】 可以参考http://docs.python.org/lib/module-getopt.html # -*- coding: cp936 -*-import getoptimport sysdef usage():print Help Information:-h: Show help information-xValue:...if __name____main__:#set d…

博弈论基础

博弈论总结 什么是博弈论&#xff1a; 多人进行博弈&#xff0c;假设每个人都采取最优策略&#xff0c;一定有一个人胜出&#xff0c;在知道初态及规则的情况下&#xff0c;求解出 何人胜出的一类问题的理论及方法。 博弈论的一些性质 P点&#xff1a;必败点&#xff0c;N…

矩阵论-范数理论及其应用

范数理论及其应用2.1向量范数及其性质2.2矩阵范数本系列博文主要总结学习矩阵论的心得笔记&#xff0c;参考数目《矩阵论》–张凯院&#xff1b;整个文章的整理体系参照行书过程。范数–非负实数&#xff0c;用于衡量线性空间元素&#xff08;如&#xff1a;向量&#xff0c;矩…

大数据学习(09)--spark学习

文章目录目录1.spark介绍1.1 spark介绍1.2 scale介绍1.3 spark和Hadoop比较2.spark生态系统3.spark运行框架3.1 基本概念3.2 架构的设计3.3 spark运行基本流程3.4 spark运行原理3.5 RDD运行原理3.5.1 设计背景3.5.2 RDD概念和特性3.5.3 RDD之间的依赖关系3.5.4 stage的划分3.5.…

探索 Pexpect

概述 通过本系列第一部分 《探索 Pexpect&#xff0c;第 1 部分&#xff1a;剖析 Pexpect 》&#xff08;请参阅参考资料&#xff09;的介绍&#xff0c;相信大家已经对 Pexpect 的用法已经有了比较全面的了解&#xff0c;知道 Pexpect 是个纯 Python 语言实现的模块&#xff…

Python的Pexpect详解 [图片]

Pexpect 是一个用来启动子程序并对其进行自动控制的纯 Python 模块。 Pexpect 可以用来和像 ssh、ftp、passwd、telnet 等命令行程序进行自动交互。继第一部分《探索 Pexpect&#xff0c;第 1 部分&#xff1a;剖析 Pexpect 》介绍了 Pexpect 的基础和如何使用后&#xff0c;本…

关系数据库——sql增删改

数据的插入 插入元祖 --1. 表名后没有指定属性列&#xff1a;表示要插入的是一条完整的元组&#xff0c;且属性列属性与表定义中的顺序一致 insert into student values (201215128, 陈东, 18, 男, IS);--2. 在表明后指定要插入数据的表名及属性列&#xff0c;属性列的顺序可…

机器学习中的聚类方法总结

聚类定义 定义 聚类就是对大量未知标注 的数据集&#xff0c;按数据 的内在相似性将数据集划分为多个类别&#xff0c;使 类别内的数据相似度较大而类别间的数据相 似度较小。是无监督的分类方式。 聚类思想 给定一个有N个对象的数据集&#xff0c;构造数据的k 个簇&#x…

学点数学(1)-随机变量函数变换

随机变量函数变换本文介绍一维随机变量函数变换&#xff0c;参考文献&#xff1a;https://wenku.baidu.com/view/619f74ac3186bceb19e8bbd0.html变换TTT作用于随机变量XXX&#xff0c;产生随机变量YYY. T:X−>Y或者写为yT(x)T:X->Y 或者写为 yT(x)T:X−>Y或者写为yT(x…

关系数据库——关系数据语言

关系 域&#xff1a;一组具有相同数据类型的值的集合&#xff08;即取值范围&#xff09; 笛卡尔积&#xff1a;域上的一种集合运算。结果为一个集合&#xff0c;集合的每一个元素是一个元组&#xff0c;元组的每一个分量来自不同的域。 基数&#xff1a;一个域允许的不同取值…

Python模块(2)-Numpy 简易使用教程

Numpy模块 简易使用教程1.数组创建2.数组基本属性-维度、尺寸、数据类型3.数组访问-索引、切片、迭代4.数组的算术运算-加减乘除、转置求逆、极大极小5.通用函数-sin,cos,exp,sqrtnp.dot与np.matmul的区别6.数组的合并和分割6.1 np.vstack(),np.hstack()6.2 np.stack()7.list与…