DFS深度优先搜索
深度优先通俗理解:从树的根节点开始,一直往下遍历,访问其左孩子结点,直到遇到叶子结点,则退回到上一个结点,访问这个节点的另一个孩子(右)结点,这样便可以遍历完整个树。这一个访问的过程,是否觉得很像栈的递归调用?如果是子节点则取上一个结点进行递归。
应用场景:一般题目为待求解找祖先?找路径?
最近刷题遇到的DFS(力扣257)遍历二叉树的所有路径,废话少说,上题目:
代码如下:
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func binaryTreePaths(root *TreeNode) []string {
var res []string
dfs(root,"",&res)
return res
}
func dfs(root *TreeNode,value string,result *[]string){
if root!=nil{
value+=strconv.Itoa(root.Val)
if root.Left==nil&&root.Right==nil{//如果是叶子结点
*result=append(*result,value)
}else{//如果不是叶子结点
value+="->"
dfs(root.Left,value,result)
dfs(root.Right,value,result)
}
}
}
有几个地方要注意
- dfs函数的第三个参数要传递的是[]string的指针,因为dfs是无返回值的函数,必须将形参设为指针类型,否则值传递是没办法改变内容的
- dfs函数需要自己设计,但是第一个参数一般都是root.Left或者root.Right,第二个参数可能是深度,或者题目待求解的val等
- 关于指针的append,这里需要
*result=append(*result,value)
一定要取* !!!
再来一题(力扣1123)最深叶节点的公共祖先
上题目:
上代码:
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func lcaDeepestLeaves(root *TreeNode) *TreeNode {
res,_:=dfs(root,0)//第一层深度为0
return res
}
func dfs(root *TreeNode,deepth int)(nextNode *TreeNode,nextDeepth int){//深度优先,找左右子树的关系
if root==nil{
return root,deepth
}
leftNode,lDeepth:=dfs(root.Left,deepth+1)
rightNode,rDeepth:=dfs(root.Right,deepth+1)
if lDeepth==rDeepth{//如果叶子高度都一样
return root,lDeepth //返回公共父节点,给后面遍历
}
if lDeepth>rDeepth{//左边高,右边没有孩子
return leftNode,lDeepth
}
return rightNode,rDeepth //最后一种情况,右边高
}
其实我目前对递归也不是很熟悉,因为刷题还是太少了,需要加强。但是有递归的地方你就一定要设置一个出口(如n=1?root==nil?),写好递归的返回值之后,不要去想里面递归的过程,否则你会把自己绕晕,我们只要操作递归的返回值就行,你可以把这些返回值当成已知条件来进行后续的判断、操作等。
BFS广度优先搜索(层次遍历)
广度优先通俗理解:对于一棵树,无论是几叉树,都是从根节点开始,沿着树的宽度遍历树的节点。如果所有结点均被访问,则算法中止。很容易联想到队列吧,因为队列先进先出,那么当我访问了这个节点的时候,我就直接把他的孩子结点也放入队列中,直到队列为空即是终止。
应用场景:按层次遍历N叉树的所有结点
(力扣429)N叉树的层次遍历
上题目:
上代码:
/**
* Definition for a Node.
* type Node struct {
* Val int
* Children []*Node
* }
*/
func levelOrder(root *Node) [][]int { //返回值为2维,则先创一个一维,在append进二维
result:=make([][]int,0,500)
if root==nil{
return result
}
q:=[] *Node{root}//用切片充当队列
for len(q)!=0{//只要队列里面还有东西
long:=len(q)
res:=make([]int,0)
for i:=0;i<long;i++{//遍历队列中的结点
//fmt.Println(q[i].Val)
res=append(res,q[i].Val)
for _,value:=range q[i].Children{//遍历孩子结点,放入队列中
q=append(q,value)
}
}
result=append(result,res)
q=q[long:]//往后取
}
return result
}
BFS过程讲解:
-
先讲一个二维切片的细节,这也是经常被忽略的地方。题目要求返回的是一个二维整型切片,那么我们需要先创建一个二维整型切片[][]int,append二维切片的内容,一定是一个一维整型切片[]int,那么我们又需要设置一个一维来存结果,然后append到二维里面,没毛病!
-
我们需要一个队列,那么我们可以直接把Node指针切片直接当成队列,为什么呢?你可以想象把所有每一个结点都是*Node,刚好放入切片,遍历完我直接修改他的下标(改为上一次长度开始)重新赋值不就可以了吗?
-
循环条件为队列不为空,即len(q)!=0,不为空时,我们循环遍历队列中的每一个元素,添加到res一维切片,这个就是该层的结果,然后顺便将这个节点的所有孩子结点都添加到队列中,这是我们下一层循环要访问的结点
-
之后把一维切片append进二维切片中,就是我们最终要的结果了
-
还有一个地方要注意,那就是len(q)不能写在for循环的判断条件中,否则你for循环的时候q长度是一直变的,也就意味着你这个长度一直在变化,用来做for循环的判断条件明显是不对的,因此应该先赋值给一个变量,将其固定,当做for循环的条件即可,我之前也在这里踩了一次坑,以后会留意。
层次遍历结构很清晰,个人感觉比DFS遍历好理解很多,因为层次遍历只需要从队列中取元素,然后再把他的孩子放入队列中就行了