深度优先搜索(DFS)
深度优先搜索(DFS)
深度优先搜索,是从起点v0开始,优先往下v1,v2级搜索下去,同样的举例子:
假设有一个这样的文件夹:
里面有着大大小小的文件以及子文件夹,当你需要搜索一个名字为:仙士可.txt的文件时
你需要怎么遍历呢?
首先,我们把/text下的文件及文件夹称作为v0级文件,以此同理,vo级文件夹下的子文件为v1级...v2
广度优先搜索
在广度优先搜索中,我们是这样遍历的:
先遍历v0的所有文件,存储v1的所有需要遍历的文件夹,再遍历v1的所有文件,同时存储v2的文件夹....
深度优先搜索
深度优先搜索的做法为:
1:保存v0级别的所有文件,1,2,3,4,5,测试文本01.txt,测试文本02.txt,
2:先遍历v0级别的目录1,判断为目录,而不是目标文件
3:保存目录1的v1级子文件 11,12,测试文本11.txt
4:继续保存目录11的子文件 111,测试文本111.txt,
5:继续遍历目录11的第一个子文件夹111,由于111文件夹没有内容,则返回
6:继续遍历目录11的第二个文本测试文本111.txt,由于不匹配 仙士可.txt,则返回
7:目录11遍历完毕,返回
8:继续遍历12文件夹
...
深度优先搜索的做法是,从一个起点开始,一直遍历下去,直到满足条件或者没有数据遍历,则开始第二个点开始遍历,直到最后一个vo级数据遍历完毕
广度优先搜索和深度优先搜索
现在我们已经知道了广度优先搜索以及深度优先搜索的搜索步骤,
那么,它们之间的优缺点有哪些呢?什么时候要用广度优先什么时候要用深度优先呢?
我们根据它们之间的特性进行分析:
内存消耗
当子节点过多的时候,广度优先搜索需要保存更多的子节点数据以便于下次遍历,而深度优先搜索只需要保存当前节点的上下级节点
例如,
当v0级文件夹有10个文件夹,同时每个子文件夹也有10个文件夹(空文件夹)的时候.
广度优先在遍历到第20次的时候(vo级和v1级都遍历完),这时候的队列已经保存了10*10-20(已经遍历过)需要遍历的数据
而深度优先在这个时候,只保存了10(v0级文件夹)+0(v1级第一个已经遍历完毕)+10(v1级第二个遍历的栈)
深度优先由于只需要记录当前遍历的上下级节点,并且每次遍历完可以及时回收,所以内存消耗较少
广度优先由于需要记录上级所有节点以及当前的下级节点,在子节点数过多的时候,需要消耗更多的内存
最优解
之前的那个搜索文件的需求,我们稍微改一改:
一个文件夹里面有n级的子文件夹,有着5,6个名字为"仙士可.txt"的文件,现在我们需要找到层级最高(离v0最近的)的那个"仙士可.txt"文件
这个时候,该怎么找呢?
深度优先搜索的做法是这样的:
找到该文件之后,记录该文件的层级(假设为v5)以及路径
继续查找,找到之后,如果层级大于v5,则忽略,如果小于v5,则覆盖之前的层级以及路径
...
这样子,我们就可以找到层级最高的"仙士可.txt"
而在广度优先搜索中,我们只需要v0下去逐层查找,找到之后立即返回即可
深度优先搜索可以在消耗少量内存的情况下找到一个解,但这个解并不一定是最优解,如果需要找最优解,需要遍历全部数据,消耗更多的时间
广度优先搜索消耗更多的内存,消耗相对较少的时间,找出的是最优解,
算法实现
深度优先准备工作如下:
1:结果集数组,将匹配正确的结果集数组保存
2:递归函数,栈实现深度搜索
3:创建一个数组,用于记录已经遍历的文件夹(通用写法,当你的v2级文件夹,有一个是v0级的快捷方式的时候,需要判断一下是否已经遍历过了,如果有就不再遍历)
由于深度优先搜索的特性,需要通过一个全局的结果集数组保存结果值,在栈里面判断该次搜索任务是否完成
算法需求拆分:
1:递归函数,foreach当前级别的文件数组的时候,继续调用该函数,去foreach下一个级别的文件数组,直到找到结果集数组或者遍历全部完成
2:获取子级数据 在调用一个文件夹的时候,去获取他的子级并且开始下一次循环
3:根据结果集判断搜索任务是否完成
4:判断任务数据 判断当前数据是否已经遍历过,是否跳过
php实现如下:
<?php
$resultData = [];
$ergodic = [];//通过php的hash数组特性,直接$ergodic[hash(文件夹名)]=1; 进行表示该文件夹名已遍历
$rootPath = '/www/easyswoole/tioncico_demo/test';
search($rootPath,$ergodic,$resultData);
/**
* search
* @param $path //需要搜索的目录
* @param $ergodic //已遍历数据存储
* @param $resultData //结果数据存储
* @param int $level
* @author Tioncico
* Time: 11:51
*/
function search($path,&$ergodic,&$resultData,$level=1){
//判断目录是否已经遍历过
if (checkTask($ergodic, $path)) {
return null;
}
//标记该目录已经遍历过
$ergodic[md5($path)] = 1;
//获取目录的数据
$fileData = getDirData($path);
//判断是文件,还是文件夹
foreach ($fileData as $file) {
//判断是否完成搜索,在这里,只要$resultData不为空就当成成功搜索
if (!empty($resultData)){
break;
}
//过滤本级目录以及上级
if ($file == '.' || $file == '..') {
continue;
}
$filePath = "{$path}/$file";
echo "当前遍历文件:{$filePath}\n";
if (is_file($filePath)) {//如果是文件,则处理文件
//判断文件终止情况
if (checkFile($filePath)) {
$resultData[] = $filePath;
echo "文件搜索成功,路径为:{$filePath}\n";
return $filePath;
}
} elseif (is_dir($filePath)) {//如果是目录,则继续遍历
search($filePath,$ergodic,$resultData,$level+1);
} else {
continue;
}
}
return null;
}
/**
* 扫描文件夹(获取子级数据)
* getDirData
* @param $path
* @return array
* @author Tioncico
* Time: 11:55
*/
function getDirData($path)
{
//扫描文件夹
$file = scandir($path);
return $file;
}
//判断遍历条件是否完成
function checkFile($data)
{
if (basename($data) == '仙士可.txt') {
return true;
}
return false;
}
//判断该任务是否已经遍历过
function checkTask($ergodic, $path)
{
return isset($ergodic[md5($path)]);
}
//子级数据入队列
function pushQueue(&$queue, $data)
{
array_push($queue, $data);
}
复制
输出:
\
去除终止条件时候的输出:
以广度优先搜索对比:
- 点赞
- 收藏
- 关注作者
评论(0)