原创

深度优先搜索(DFS)

温馨提示:
本文最后更新于 2019年11月08日,已超过 1,621 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

深度优先搜索(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);
}

输出:

仙士可博客\

去除终止条件时候的输出:

仙士可博客

以广度优先搜索对比:

仙士可博客

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2hrq8tku2rqco

正文到此结束
本文目录