DFS入门必看

dfs深搜

说明和基础概念:

说明:预计一刷时间:7小时,这个文章建议你可以直接复制粘贴,然后打印出来

dfs是图的一种搜索算法,每一个可能的分支路径深入到不能再深入为止,且每个节点只能访问一次。

它们的区别跟关联都在于它们的数据结构回溯算法是树结构,深度优先搜索是图结构,dfs用栈,bfs用队列

递归和迭代区别:

递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环

没有找到什么好的介绍和开始dfs的地方,怎么说呢,就是它作为一种算法比较分散

栈可以实现dfs深搜,然后dfs算法又是属于图,而二叉树又属于特殊的图,所以二叉树数据结构实现了很多dfs算法,也有不是二叉树,而是属于图的dfs算法,因为算法:指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制

二叉树基本上可以分成BFS和DFS这两种以及正常用前中后层次遍历,穿插交错

其中dfs的分治法用的很多

比较好的是:https://greyireland.gitbook.io/algorithm-pattern/shu-ju-jie-gou-pian/binary_tree
声明:本篇文章有很多是借鉴这个链接的,但是介绍概念和说明等部分和题解的注释部分是我个人理解写的,绝对没有什么可借鉴的,必须要自己的理解才可以写出来的,所以大家可以结合这学习
里边的所涉及到的dfs在二叉树部分和栈与队列部分,

所以你刷dfs等于是刷了部分的二叉树,和栈的一部分

这有个视频,感觉也不错,但是我没看,16分钟:

https://www.bilibili.com/video/BV1Qa411t7NA/?spm_id_from=333.337.search-card.all.click&vd_source=46a8aeb0bd2b7168a7bd0fbe5d3532f4

模板

DFS 深度搜索-从上到下

看着熟悉不?这妥妥的回溯思想,不过是套到了二叉树结构

毕竟回溯和dfs思想特别像,但是又有区别

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func preorderTraversal(root *TreeNode) []int {
    result := make([]int, 0)
    dfs(root, &result)
    return result
}

// V1:深度遍历,结果指针作为参数传入到函数内部
func dfs(root *TreeNode, result *[]int) {
    if root == nil {
        return
    }
    *result = append(*result, root.Val)
    dfs(root.Left, result)
    dfs(root.Right, result)
}

DFS 深度搜索-从下向上(分治法)

// V2:通过分治法遍历
func preorderTraversal(root *TreeNode) []int {
    result := divideAndConquer(root)
    return result
}
func divideAndConquer(root *TreeNode) []int {
    result := make([]int, 0)
    // 返回条件(null & leaf)
    if root == nil {
        return result
    }
    // 分治(Divide)
    left := divideAndConquer(root.Left)
    right := divideAndConquer(root.Right)
    // 合并结果(Conquer)
    result = append(result, root.Val)
    result = append(result, left...)
    result = append(result, right...)
    return result
}

注意点:

DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并

分治法以及应用

因为分治法用的太多咱们再了解一点:

先分别处理局部,再合并结果

适用场景

  • 快速排序
  • 归并排序
  • 二叉树相关问题

分治法模板

  • 递归返回条件
  • 分段处理
  • 合并结果
归并排序:
func MergeSort(nums []int) []int {
    return mergeSort(nums)
}
func mergeSort(nums []int) []int {
    if len(nums) <= 1 {
        return nums
    }
    // 分治法:divide 分为两段
    mid := len(nums) / 2
    left := mergeSort(nums[:mid])
    right := mergeSort(nums[mid:])
    // 合并两段数据
    result := merge(left, right)
    return result
}
func merge(left, right []int) (result []int) {
    // 两边数组合并游标
    l := 0
    r := 0
    // 注意不能越界
    for l < len(left) && r < len(right) {
        // 谁小合并谁
        if left[l] > right[r] {
            result = append(result, right[r])
            r++
        } else {
            result = append(result, left[l])
            l++
        }
    }
    // 剩余部分合并
    result = append(result, left[l:]...)
    result = append(result, right[r:]...)
    return
}
快速排序:
func QuickSort(nums []int) []int {
    // 思路:把一个数组分为左右两段,左段小于右段,类似分治法没有合并过程
    quickSort(nums, 0, len(nums)-1)
    return nums

}
// 原地交换,所以传入交换索引
func quickSort(nums []int, start, end int) {
    if start < end {
        // 分治法:divide
        pivot := partition(nums, start, end)
        quickSort(nums, 0, pivot-1)
        quickSort(nums, pivot+1, end)
    }
}
// 分区
func partition(nums []int, start, end int) int {
    p := nums[end]
    i := start
    for j := start; j < end; j++ {
        if nums[j] < p {
            swap(nums, i, j)
            i++
        }
    }
    // 把中间的值换为用于比较的基准值
    swap(nums, i, end)
    return i
}
func swap(nums []int, i, j int) {
    t := nums[i]
    nums[i] = nums[j]
    nums[j] = t
}
1、 104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

    3
   / \
  9  20
    /  \
   15   7

这是第二次写,
不同的是这次使用的go写的,用的也是分治思想
相比labuladong的解法更为精简

一个字,妙

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    //分
    left := maxDepth(root.Left) // 注意left和right是一个int类型的数,同时这是一个初始化变量,
    right := maxDepth(root.Right)
    //合
    if left > right {
        return left + 1
    }
    return right + 1
}
2、110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isBalanced(root *TreeNode) bool {
    if maxDepth(root) == -1 {
        return false
    }
    return true
}
func maxDepth(root *TreeNode) int {
    //返回条件处理
    if root == nil {
        return 0
    }
    //fen
    left := maxDepth(root.Left)
    right := maxDepth(root.Right)
    //前两个条件判断叶子节点会不会出现,高度差超过1,后两个条件就不用说了哈
    if left == -1 || right == -1 || left-right > 1 || right-left > 1 {
        return -1
    }
    if left > right {
        return left + 1
    }
    return right + 1
}
3、124. 二叉树中的最大路径和

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
在这里插入图片描述

type ResultType struct {
    SinglePath int // 保存单边最大值
    MaxPath int // 保存最大值(单边或者两个单边+根的值)
}
func maxPathSum(root *TreeNode) int {
    result := helper(root)
    return result.MaxPath
}
func helper(root *TreeNode) ResultType {
    // check
    if root == nil {
        return ResultType{
            SinglePath: 0,
            MaxPath: -(1 << 31),
        }
    }
    // Divide
    left := helper(root.Left)
    right := helper(root.Right)

    // Conquer
    result := ResultType{}
    // 求单边最大值
    if left.SinglePath > right.SinglePath {
        result.SinglePath = max(left.SinglePath + root.Val, 0)
    } else {
        result.SinglePath = max(right.SinglePath + root.Val, 0)
    }
    // 求两边加根最大值
    maxPath := max(right.MaxPath, left.MaxPath)
    result.MaxPath = max(maxPath,left.SinglePath+right.SinglePath+root.Val)
    return result
}
func max(a,b int) int {
    if a > b {
        return a
    }
    return b
}
4、236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7pm7zJHh-1666753353267)(../../my_images/image-20221024163209293.png)]

看了题解自己带入才懂…

 func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
     // nil
     // 首先当给的root是nil时候判断一下,最近公共祖先就是他自己nil,也是root
    if root == nil {
        return root 
    }
    // 相等 直接返回Root节点
    // 带入,当一开始第一个函数执行时候,root是p或者q,那么就是最本的根了,结果只能是他自己,返回root
    if root == p || root == q {
        return root
    }
    // 分
    left := lowestCommonAncestor(root.Left, p, q)
    right := lowestCommonAncestor(root.Right, p, q)
    // 合
    // 1.  第一个判断,带入5,1(5,0当然也可以,这时候right要向下找,最后right返回的还是0,而left自然是5,满足合的第一条件),试一下就知道,left为5,right为1,两边不为空,所以返回root为3
    if left != nil && right != nil {
        return root
    }
    // 下一个判断和下下一个判断原理相同一起说了(当其中一方为空了),你带入5,4试一下,你会发现righ为nil
		// 这时候就直接返回5了,也就是left为5,right为空,因为右边是1,他往下一直找找找,但是5和4都没有找到,最后一直返回空空空,所以right是nil而left是5,所以最终返回了5为最近公共祖先
    if left != nil {
        return left
    }
    if right != nil {
        return right
    }
    return nil
}
5、701. 二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjSKyVkR-1666753353267)(../../my_images/image-20221024174046816.png)]

func insertIntoBST(root *TreeNode, val int) *TreeNode {
    if root == nil {
        root = &TreeNode{Val: val}
        return root
    }
    if root.Val > val { 
        root.Left = insertIntoBST(root.Left, val)
    } else {
        root.Right = insertIntoBST(root.Right, val)
    }
    return root
}

BFS层序遍历

模板:

func levelOrder(root *TreeNode) [][]int {
    // 通过上一层的长度确定下一层的元素
    result := make([][]int, 0)
    if root == nil {
        return result
    }
    queue := make([]*TreeNode, 0)
    queue = append(queue, root)
    for len(queue) > 0 {
        list := make([]int, 0)
        // 为什么要取length?
        // 记录当前层有多少元素(遍历当前层,再添加下一层)
        l := len(queue)
        for i := 0; i < l; i++ {
            // 出队列
            level := queue[0]
            queue = queue[1:]
            list = append(list, level.Val)
            if level.Left != nil {
                queue = append(queue, level.Left)
            }
            if level.Right != nil {
                queue = append(queue, level.Right)
            }
        }
        result = append(result, list)
    }
    return result
}
1、102. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qbNn0qkY-1666753353268)(../../my_images/image-20221025145601632.png)]

在了解队列的特性后,我们理解这题目的方式就是,带入一个节点去试一下,就懂了

声明res–> 判断root为空情况–>初始化节点型切片,就是队列–>给队列追加root,第一个节点–>第一个for循环遍历,看len(queue)–>初始化暂时的list, --> 拿到queue长度–>第二层for循环–>出队列,拿到level–> list追加list–>入队–> res追加list–> 最后return res

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func levelOrder(root *TreeNode) [][]int {
    res := make([][]int, 0)
    if root == nil {
        return res
    }
    queue := make([]*TreeNode, 0) // 初始化一个TreeNode型切片
    queue = append(queue, root) // 给队列加上root节点
    for len(queue) > 0 {
        list := make([]int, 0)
        // 为什么要取length?
        // 记录当前层有多少元素(指的是节点) (遍历当前层, 再添加下一层)
        l := len(queue) // queue的长度是怎么算的?
        for i := 0; i < l; i++ {
            level := queue[0]
            queue = queue[1:] // 这两步出队列
            list = append(list, level.Val) // 然后取出节点的值,赋值给list
            if level.Left != nil { // 如果当前节点左子节点存在,则赋给queue队列的下一个
                queue = append(queue, level.Left)
            }
            if level.Right != nil { // 这个也是同样的道理,从左向右
                queue = append(queue, level.Right)
            }
        }
        res = append(res,list) // 给结果res加上临时的list
    }
    return res
}
2、107. 二叉树的层序遍历 II

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aSzXMN3b-1666753353269)(../../my_images/image-20221025153429832.png)]

  1. 和102题差不多,只不过是结果反着来了,
  2. 先写个levelOrder函数,思路、声明res–> 判断root为空情况–>初始化节点型切片,就是队列–>给队列追加root,第一个节点–>第一个for循环遍历,看len(queue)–>初始化暂时的list, --> 拿到queue长度–>第二层for循环–>出队列,拿到level–> list追加list–>入队–> res追加list–> 最后return res
  3. 再写一个reverser反转切片元素函数,给levelOrder反转就可以了
func levelOrderBottom(root *TreeNode) [][]int {
    res := levelOrder(root)
    reverse(res)
    return res
}
func reverse(nums [][]int) {
    for i,j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
        nums[i], nums[j] = nums[j], nums[i]
    }
}
func levelOrder(root *TreeNode) [][]int {
    res := make([][]int, 0)
    if root == nil {
        return res
    }
    queue := make([]*TreeNode, 0)
    queue = append(queue, root)
    for len(queue) > 0 {
        list := make([]int, 0)
        l := len(queue)
        for i := 0; i < l; i++ {
            level := queue[0]
            queue = queue[1:]
            list = append(list, level.Val)
            if level.Left != nil {
                queue = append(queue, level.Left)
            }
            if level.Right != nil {
                queue = append(queue, level.Right)
            }
        }
        res = append(res, list)
    }
    return res
}
3、103. 二叉树的锯齿形层序遍历

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eMu5Gw2p-1666753353270)(../../my_images/image-20221025155935445.png)]

和102题比较,知道,只需要加一个层序控制器toogle,用来判断list是否需要反转后再让res追加即可

func zigzagLevelOrder(root *TreeNode) [][]int {
    res := make([][]int, 0)
    if root == nil {
        return res
    }
    queue := make([]*TreeNode, 0)
    queue = append(queue, root)
    // 第一层for循环控制的层序遍历的层序
    toogle := false // 是否反转的触发器
    for len(queue) > 0 {
        list := make([]int, 0)
        l := len(queue)
        for i := 0; i < l; i++ {
            level := queue[0]
            queue = queue[1:]
            list = append(list, level.Val)
            if level.Left != nil {
                queue = append(queue, level.Left)
            }
            if level.Right != nil {
                queue = append(queue, level.Right)
            }
        }
        if toogle { // 奇数层为true,需要反转list
            reverser(list)
        }
        res = append(res, list)
        toogle = !toogle
    }
    return res
}
func reverser(nums []int) {
    for i,j := 0, len(nums)-1; i < j; i,j = i+1,j-1 {
        nums[i], nums[j] = nums[j], nums[i]
    }
}

二叉树

1、98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7kL9fMHJ-1666753353270)(../../my_images/image-20221024172751755.png)]

中序递归

  1. 通过一个inOrder函数从底层开始把他们通过中序遍历以此放入切片result中
  2. inOrder中要先判断给的root是不是nil,这是一种情况不能忘记
  3. 通过 for 循环遍历判断即可
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isValidBST(root *TreeNode) bool {
    result := make([]int, 0)
    inOrder(root, &result)
    // 检查有序
    for i := 0; i < len(result) - 1; i++ {
        if result[i] >= result[i+1] {
            return false
        }
    }
    return true
}
func inOrder(root *TreeNode, result *[]int) {
    if root == nil {
        return
    }
    inOrder(root.Left, result)
    *result = append(*result, root.Val)
    inOrder(root.Right, result)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不之道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值