四:数据结构之树
4.1树的遍历方式
前中后三种遍历方式的模板(只需要在具体的执行操作位置写相应的代码逻辑即可):有迭代和递归方式实现,总的来说递归简单
4.1.1三种遍历递归形式
//前序递归 public static void preOrderRecursion(BinTreeNode top) { if (top != null) { System.out.println(top.data);//此处为具体的执行操作 preOrderRecursion(top.lchild); preOrderRecursion(top.rchild); } } // 中序递归 public static void inOrderRecursion(BinTreeNode top) { if (top != null) { inOrderRecursion(top.lchild); System.out.println(top.data);//此处为具体的执行操作 inOrderRecursion(top.rchild); } } // 后序递归 public static void postOrderRecursion(BinTreeNode top) { if (top != null) { postOrderRecursion(top.lchild); postOrderRecursion(top.rchild); System.out.println(top.data);//此处为具体的执行操作 } }
4.1.2三种遍历迭代方式:
前序遍历(根左右)逻辑:先将根节点放入栈中后每次进行如下操作:
-
从栈中弹出一个节点car
-
打印(处理)节点car
-
先压右再压左(如果有子节点)
-
周而复始重复操作
后序遍历(左右根)逻辑:我们可以由先序遍历转换而来,先序遍历是根左右,那么如果我想实现前序变形(根右走)的话,只需要压栈时候先压左在压右即可。但此时如果我们将根右走这个栈里面东西存入到另外一个栈中则会变成左右根,自然实现后续遍历了!!!
因此需要设置两个栈,一个用于完成前序的变形,另一个用于接收第一个栈的值后打印则是我们需要的后序遍历。操作如下:
-
同理先放入头节点进入1栈中,弹出节点car
-
将弹出节点car放入栈2中
-
先左再右
-
周而复始
-
最后打印栈2的结果就是后序遍历
中序遍历(左根右)逻辑:每根子树对,整棵树左边界进栈,依次弹出的过程中,打印结果,对弹出节点的右树同样进行如上操作,故操作如下:
-
将树的头节点以及他的左边界left全部依次压入栈中
-
当左边界全部压入完后,依次弹出节点
-
每当弹出一个节点的时候打印结果,并将该节点的右节点和右节点的左边界全部压入栈中
-
重复2,3操作
代码如下:
// 前序 //前序遍历非递归实现 public static void preOrder(TreeNode head){ if (head !=null){ Stack<TreeNode> stack=new Stack<TreeNode>(); stack.add(head);//首先将根节点放入其中 while (!stack.empty()){//只要栈不是空 周而复始 第4步 head=stack.pop(); System.out.println(head.data);//第二步骤 处理部分 if (head.rightNode!=null){ stack.push(head.rightNode); } if (head.leftNode!=null){ stack.push(head.leftNode); } } } System.out.println(); } //中序 public static void inOrder(TreeNode head) { if (head != null) { Stack<TreeNode> stack = new Stack<TreeNode>(); while (head != null || !stack.empty()) { if (head != null) {//此分支的目的是不停的把左边界进栈,知道遇到null stack.push(head); head = head.leftNode; } else {//弹出该节点,并打印,并移动到它的右孩子节点上,继续走第一个分支 head = stack.pop(); System.out.print(head.data); head = head.rightNode; } } System.out.println(); } } // 后序 public static void postOrder(TreeNode head){ if (head!=null){ Stack<TreeNode> s1=new Stack<TreeNode>(); Stack<TreeNode> s2=new Stack<TreeNode>(); s1.push(head); while (!s1.empty()) {//只要栈不是空 周而复始 第4步 head = s1.pop(); s2.push(head); if (head.rightNode!=null){ s1.push(head.rightNode); } if (head.leftNode!=null){ s1.push(head.leftNode); } while (!s2.empty()){ System.out.println(s2.pop().data); } } } System.out.println(); }
4.2树的宽度优先遍历和广度优先遍历
备注:DFS可以使用递归和栈实现;BFS只能使用队列实现。
4.2.1 BFS(宽度优先遍历)
深度优先遍历(先序遍历):从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完成所有可达节点为止。
数据结构:队列(先进先出) 遍历操作:父节点入队列,父节点出队列,先左子节点入队列,后右子节点处队列,递归遍历全部节点。
//使用Queue实现BFS public void BFSWithQueue(TreeNode root) { Queue<TreeNode> queue = new LinkedList<>(); if (root != null) queue.add(root); while (!queue.isEmpty()) { TreeNode treeNode = queue.poll(); if (treeNode.left != null) queue.add(treeNode.left); if (treeNode.right != null) queue.add(treeNode.right); } }
4.2.2广度优先遍历(DFS)
广度优先遍历(层序遍历):从根节点出发,在横向遍历二叉树层段节点的技术上纵向遍历二叉树的层次。
迭代实现:
数据结构:栈(后进先出) 遍历操作:父节点入栈,父节点出栈,先右子节点入栈,后左子节点入栈,递归遍历全部节点.
//DFS的迭代实现版本(Stack) public void DFSWithStack(TreeNode root) { if (root != null) return; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode treeNode = stack.pop(); if (treeNode.right != null) stack.push(treeNode.right); if (treeNode.left != null) stack.push(treeNode.left); } }
递归实现:
//DFS递归实现 public void DFSWithRecursion(TreeNode root) { if (root == null) return; if (root.left != null) DFSWithRecursion(root.left); if (root.right != null) DFSWithRecursion(root.right); }
4.3树型DP问题套路解法
首先从以下概念及实现判断代码来引出!
如何判断一颗二叉树是否是搜索二叉树?(判断条件:每一棵子树都是左小右大)
如何判断一颗二叉树是否是完全二叉树?
如何判断一颗二叉树是否是满二叉树?(判断条件:最大深度l与节点数N满足N=2l-1)
如何判断一颗二叉树是否是平衡二叉树?(判断条件:每一颗子树他的左树与右树的高度差不能超过1)
套路:针对一个问题,首先我们需要罗列判断的条件,然后根据判断的条件去找左子树和右子树要相关的信息(即确定了递归要返回的信息结构体),务必要左右子树所给的信息一致,然后最后给整个树信息,拿整个数信息去判断!!
4.3.1 判断是否是平衡二叉树
比如:如何判断平衡二叉树,判断图如下:
从图可见,返回的结构体信息就是(是否是平衡二叉树,树的高度)
//判断一棵树是否是平衡二叉树 public static class ReturnType{//此处为递归返回值的结构体 public boolean isBalanced; public int height; public ReturnType(boolean isBalanced, int height) { this.isBalanced = isBalanced; this.height = height; } } public static boolean isBalanced(Node head){ return process(head).isBalanced; } public static ReturnType process(Node x){ if (x==null){ return new ReturnType(true,0); } ReturnType leftData=process(x.left);//返回左树上的两个信息 ReturnType rightData=process(x.right);//返回右树上的两个信息 //接下来的代码则是返回以X为头的树的两个信息,这样递归才能连起来 保证所有的信息都有 int height=Math.max(leftData.height,rightData.height)+1; //整个数的高度; boolean isb=leftData.isBalanced && rightData.isBalanced && Math.abs (leftData.height-rightData.height)<2; return new ReturnType(isb,height); }
4.3.2判断搜索二叉树
同理 我们接下来判断是否为搜索二叉树!判断图如下:
备注:此时左树和右树的条件不一致,那么我们就可以考虑让他们都返回最大值,最小值,是否是搜索二叉树三个条件,这样就保证了左右树条件一致!
//判断是否是搜索二叉树套路解法 public static class ReturnType1{ public boolean isBST; public int min; public int max; public ReturnType1(boolean isBST, int min, int max) { this.isBST = isBST; this.min = min; this.max = max; } } public static boolean isBST(Node head){ return processBst(head).isBST; } public static ReturnType1 processBst(Node x){ if (x==null){ return null; } ReturnType1 leftData=processBst(x.left); ReturnType1 rightData=processBst(x.right); int min=x.value; int max= x.value; if (leftData!=null){ min=Math.min(min,leftData.min); max=Math.max(max,leftData.max); } if (rightData!=null){ max=Math.min(min,rightData.min); max=Math.max(max,rightData.max); } boolean isBst=true;//此时默认是bst,根据后序条件判断更改 if (leftData!=null && !(leftData.isBST || leftData.max>=x.value)){ //当我左边有东西但我左边已经不是搜索二叉树了 则改为false //或者当我左边有东西但我左边的最大值已经大于当前根节点的值,则整体也不是搜索二叉树,改为false isBst= false; } if (rightData!=null && !(rightData.isBST || rightData.min<=x.value)){ isBst= false; } return new ReturnType1(isBst,min,max); }
4.3.3满二叉树
//判断一棵树是否是满二叉树 public static class ReturnType2{ public int height; public int nodes; public ReturnType2(int height, int nodes) { this.height = height; this.nodes = nodes; } } public static boolean isBFT(Node head){ if (head==null){ return true; } ReturnType2 data=processBFT(head); return data.nodes==(1<<data.height-1); //判断节点数与高度的关系是否满足2l-1 } public static ReturnType2 processBFT(Node x){ if (x==null){ return new ReturnType2(0,0); } ReturnType2 leftData=processBFT(x.left); ReturnType2 rightData=processBFT(x.right); int height=Math.max(leftData.height, rightData.height)+1; int nodes= leftData.nodes+rightData.nodes+1; return new ReturnType2(height,nodes); }
4.4树的经典题目
题目1.求后继节点(可以称为线索二叉树)
题目2.二叉树的序列化和反序列化
就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的数。
{ public static class Node{ public int value; public Node left; public Node right; public Node(int data) { this.value=data; } } //以head为头节点,返回序列化后的字符串 public static String serialByPre(Node head){ if (head==null){ return "#_"; } String res=head.value+"_"; res+=serialByPre(head.left); res+=serialByPre(head.right); return res; } public static Node reconByPreString(String preStr){ String[] values=preStr.split("_"); Queue<String> queue=new LinkedList<String>(); for (int i=0;i !=values.length;i++){ queue.add(values[i]);//将数组中的值放入队列中 } return reconPreOrder(queue);//一直消费这个队列建树 } public static Node reconPreOrder(Queue<String> queue){ String value=queue.poll(); if (value.equals("#")){//遇到则为空节点 return null; } Node head=new Node(Integer.valueOf(value)); head.left=reconPreOrder(queue); head.right=reconPreOrder(queue); return head; } }
题目3.
public class code_6 { public static void printAllFolds(int N){ printProcess(1,N,true); } //递归规程来求某个节点 //i是节点的层数,N一共的层数,down==true 表示凹 false 表示凸 public static void printProcess(int i,int N, boolean down){ if (i>N){ return; } printProcess(i+1,N,true); System.out.println(down?"凹":"凸"); printProcess(i+1,N,false); } public static void main(String[] args) { int N=3; printAllFolds(N); } }