数据结构与算法--杂谈(递归、分治、dfs、bfs)

1,191 阅读4分钟

杂谈

递归

​ 递归满足三个条件: (1)问题可以拆分为几个更小的子问题 (2) 子问题除了数据范围和规模不同,求解思路完全一样 (3) 存在递归终止条件

​ 递归的优点是比较直观;

​ 缺点就是空间复杂度高、有堆栈溢出的风险、存在重复计算、大量函数调用耗时较多等问题

警惕堆栈溢出

​ 关于为什么会造成堆栈溢出,详细可看一下关于栈帧、内存模型相关的内容~

​ 这里简略说一下:

​ 函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。

​ 系统栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险

​ 解决方法:

(1) 设置递归栈深的阈值,当超过时直接报错而不再往下递归
(2) 在堆中手动模拟调用栈
(3) 改递归为递推

对于递归的调试

​ 对于递归层次很深的代码,不方便debug进行调试。我们可以通过打日志的方式来查看递归值。或者结合条件断点进行调试~

分治

​ 分治算法的核心就是将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

分治算法是一种处理问题的思想,递归是一种编程技巧

在海量数据上的应用

​ 要解决数据量大到内存装不下的问题,我们就可以利用分治思想。

​ 我们可以将海量的数据划分为几个小的数据集合,每个小的数据集合单独加载到内存来解决,然后再将小数据集合合并成大数据集合。

​ 利用分治的处理思路不仅能克服内存的限制,还能利用多线程或者多机处理,加快处理的速度

DFS

​ DFS,也就是深度优先搜索

​ 它的原理是: 沿着一条路走到头,走着走着发现走不通时就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。

​ 动画演示:传送门

伪代码

/**
 * DFS核心伪代码
 * 前置条件是visit数组全部设置成false
 * @param n 当前开始搜索的节点
 * @param d 当前到达的深度
 * @return 是否有解
 */
 
bool DFS(Node n, int d){
    if (isEnd(n, d)){//一旦搜索深度到达一个结束状态,就返回true
        return true;
    }
 
    for (Node nextNode in n){//遍历n相邻的节点nextNode
        if (!visit[nextNode]){//
            visit[nextNode] = true;//在下一步搜索中,nextNode不能再次出现
            if (DFS(nextNode, d+1)){//如果搜索出有解
                //做些其他事情,例如记录结果深度等
                return true;
            }
        }
    }
    return false;//本次搜索无解
}

复杂度

​ 深度度优先搜索 每条边最多会被访问两次(一次遍历,一次回退)。所以,深度优先搜索算法的时间复杂度是 O(E),E 表示边的个数

BFS

​ BFS,也就是广度优先搜索

​ 它实际上是一种“地毯式”层层推进的搜索,即先查离起始顶点最近的,然后是次近,依次往外搜索 ,主要包含三个元素:

visited:是用来记录已经被访问的顶点,用来避免顶点被重复访问。如果顶点 q 被访问,那相应的 visited[q] 会被设置为 true。

Q:队列,用来存储已经被访问、但其相连顶点还未处理的顶点。

​ 动画演示:传送门

伪代码

void BFS(){
    1.初始化:遍历需要的队列Q、标志节点是否访问过的,visited
    2.起始节点入队列:Q.push(startNode)
    3.标志起始节点已经在队列当中了: visited[startNode] = true
    while(队列非空:!Q.empty()){
        需要遍历的节点出队列:node = Q.dequeue()
        访问节点:visit(node)
        获取node节点的旁边,邻接节点集合:adjlist = getadjList(node)
        for adjNode : adjlist: 对每一个邻接点,执行
            如果邻接点没有被访问:visited[adjNode] != true
                把邻接点加入队列,等待访问 Q.add(adjlist)
                标志邻接点已经在队列当中了:visited[adjlist] = true
    }
}

复杂度

​ 最坏情况下终止顶点离起始顶点很远,需要遍历完整个图才能找到。这时时间复杂度是 O(E)

对比dfs和bfs

​ 虽然dfs和bfs时间复杂度相同,但是对于无权图来说,bfs得到的路径或解,一定是最优(比如最短路等),而dfs却不能保证。

​ 而且dfs需要有函数栈的递归和回溯,个人建议还是用bfs~