算法与数据结构——树
树的解题方法总结
以前在算法与数据结构的课上学习的时候,听的迷迷糊糊的,理论学的多,实践操作的少,这几天刷了下leetcode里树相关的题目,有一些体会,遂记录。
关于树的题目一般是遍历题目,而树的遍历又分为两种:深度优先遍历(DFS,Deep Firstly Search)和宽度优先遍历(BFS,Breath Firstly Search)
深度优先
就是尽可能往下找到叶子节点,其遍历方式按照对父亲节点和兄弟节点们的访问顺序不同可以分为三种(前序,中序,后续),这一点以前学习的时候知识很散,没有归纳起来,所以搞不清这些名词之间的关系。由于dfs的特点(尽可能往下),所以解这类题目一般用栈(数据结构)+递归(算法),前序和后序其实是两种类似的,前序是:头——>左——>右,后序是:左——>右——>头,可以说是相反的一种进栈顺序,代码如下:
class Solution {
List<Integer> list=new LinkedList<>();
public List<Integer> preorder(Node root) {
if(root==null) return list;
list.add(root.val);
for(Node c:root.children)
preorder(c);
return list;
}
}
class Solution {
List<Integer> list=new LinkedList();
public List<Integer> postorder(Node root) {
if(root==null) return list;
for(Node node:root.children){
postorder(node);
}
list.add(root.val);
return list;
}
}
- 看代码的话非常简洁和相似,只是头节点的入栈顺序改变下。如果是二叉树的话,判断下子树会不会为空,然后将for循环改为左右子树递归即可
- 接下来看中序遍历,中序遍历只针对二叉树,因为其遍历顺序是左——>中——>右,对于n叉树来说没有这种方式,题目和代码如下。这里为了代码写的少点,将叶子节点的子节点(为空)也进行了一次遍历,还是比较好理解的。
- 二叉树的中序遍历
class Solution {
List<Integer> list=new LinkedList<Integer>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root!=null){
inorderTraversal(root.left);
list.add(root.val);
inorderTraversal(root.right);
}
return list;
}
}
总的来说,用递归加栈解决树的深度遍历还是很清晰明了的。但是递归在一些极限情况下(递归层数过多)性能不那么好,所以还有一种通用的迭代方式来解决,题目和上面的一样,解法如下。
- 前序
class Solution {
public List<Integer> preorder(Node root) {
List<Integer> list=new LinkedList<>();
if(root==null) return list;
Stack<Node> stack=new Stack<>();
stack.add(root);
while(!stack.isEmpty()){
Node x=stack.pop();
if(x!=null){
list.add(x.val);
Collections.reverse(x.children);
for(Node n:x.children)
stack.add(n);
}
}
return list;
}
}
- 后序
class Solution {
public List<Integer> postorder(Node root) {
LinkedList<Integer> list=new LinkedList();
Stack<Node> stack=new Stack();
if(root==null) return list;
stack.push(root);
while(!stack.isEmpty()){
Node x=stack.pop();
list.addFirst(x.val);
for(Node c:x.children)
if(c!=null)
stack.push(c);
}
return list;
}
}
- 中序
class Solution {
class Node{
TreeNode treenode;
int color;
public Node(TreeNode a,int b){
treenode=a;
color=b;
}
}
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
if(root==null)
return list;
Stack<Node> stack=new Stack<>();
Node node=new Node(root,0);
stack.add(node);
while(!stack.isEmpty()){
Node x=stack.pop();
if(x.color==1)
list.add(x.treenode.val);
else{
x.color=1;
if(x.treenode.right!=null)
stack.add(new Node(x.treenode.right,0));
stack.add(x);
if(x.treenode.left!=null)
stack.add(new Node(x.treenode.left,0));
}
}
return list;
}
}
可以看出,迭代的方法复杂一些,尤其是中序,但是方法上来说思想一致,仅需变动下顺序,所以可以说是通用模板。
宽度优先
由于其特点为从左到右,从上到下的遍历方式,因此其不像深度优先有三种遍历方式,只有一种,即不分前中后序。根据其特点,一般采用队列(数据结构)+迭代(算法)的方式来解决。
class Solution {
public List<List<Integer>> levelOrder(Node root) {
LinkedList<Node> A = new LinkedList<>();
LinkedList<List<Integer>> result=new LinkedList<>();
if(root==null) return result;
A.add(root);
while (!A.isEmpty()) {
List<Integer> list=new LinkedList<>();
int sl=A.size();
for(int i=0;i<sl;i++){
list.add(A.getFirst().val);
A.addAll(A.getFirst().children);
A.remove(0);
}
result.add(list);
}
return result;
}
}
也可以采用递归的方式
class Solution {
LinkedList<List<Integer>> result=new LinkedList<>();
public List<List<Integer>> levelOrder(Node root) {
//递归方式:1,创建层,添加层元素
if(root!=null)
dfs(root,0);
return result;
}
void dfs(Node node, int level){
if(result.size()<=level)
result.add(new LinkedList<>());
result.get(level).add(node.val);
for(Node x:node.children){
dfs(x,level+1);
}
}
}
以上就是对树遍历的四种方式总结,后续做图方面算法再进行扩充。