常用算法的总结——二叉树
1 先序、中序、后序
1.1 递归
递归方式实现二叉树的先序、中序、后序遍历
1)理解递归序
2)先序、中序、后序都可以在递归序的基础上加工出来
3)第一次到达一个节点就打印就是先序、第二次打印即中序、第三次即后序
1.2 非递归
利用栈去解决:
1.2.1 先序:1)先压入头结点到栈里;
2)弹出一个就打印;
3)弹出的有右就压入右结点到栈;
4)弹出的有左就压入左结点到栈;
1.2.2 头->右->左(后序的逆序):只需要把1.2.1的2)和3)调换顺序即可;
1.2.3 后序遍历:在1.2.2里面,弹出的时候不打印,而是把值放到另外一个栈(栈2)里面去,等待栈1全部完事之后,再从这个栈2里面一个一个弹出并打印即可;
1.2.4 中序遍历:1)先压入头结点到栈里;
2)头结点的左结点不为空则压入该左结点;
3)再看左结点的左结点是否为空,为空则压入;
。。。
4)等到左结点全部压入,则弹出最上面的结点,并打印该弹出的结点;
5)将当前结点的右节点赋值给当前结点,继续循环
代码如下:
public static void in(Node head) { System.out.print("in-order: "); if (head != null) { Stack<Node> stack = new Stack<Node>(); while (!stack.isEmpty() | head != null) { if (head != null) { stack.push(head); head = head.left; } else { head = stack.pop(); System.out.print(head.value + " "); head = head.right; } } System.out.println(); } }
2 实现二叉树的按层遍历
1)其实就是宽度优先遍历,用队列即可;
[进阶] 返回二叉树的最大宽度,用队列解决,可以通过设置flag变量的方式,来发现某一层的结束
-用Map时,代码如下:
public static int maxWidthUseMap(Node head) { if (head == null) { return 0; } Queue<Node> queue = new LinkedList<>(); queue.add(head); // key在哪一层,就是那个value HashMap<Node, Integer> levelMap = new HashMap<>(); levelMap.put(head, 1); int curLevel = 1; //当前你正在统计哪一层的宽度,入队列就看 int curLevelNodes = 0; //当前层curLeve1层,宽度目前是多少,出队列时再看的 int max = 0;//宽度的最大值 while (!queue.isEmpty()) { Node cur = queue.poll(); int curNodeLevel = levelMap.get(cur); if (cur.left != null) { //将左叶子结点放进并设置层数为:在当前层+1 levelMap.put(cur.left, curNodeLevel + 1); queue.add(cur.left); } if (cur.right != null) { levelMap.put(cur.right, curNodeLevel + 1); queue.add(cur.right); } //判断当前出队列的结点是否还在这一层 //是,则说明当前结点还没结束,当前层结点数目+1 if (curNodeLevel == curLevel) { curLevelNodes++; } else {//否,则说明当前层已经完事了,当前出队列的结点已经是下一层的结点了 max = Math.max(max, curLevelNodes); //当前层数+1 curLevel++; //需要重置当前层的结点数目 curLevelNodes = 1; } } max = Math.max(max, curLevelNodes); return max; }
-不用Map,用有限几个变量的方法,代码如下:
public static int maxWidthNoMap(Node head) { if (head == null) { return 0; } Queue<Node> queue = new LinkedList<>(); queue.add(head); Node curEnd = head; //当前层,最右节点是谁 Node nextEnd = null; // 下一一层,最右节点是谁 int max = 0; int curLevelNodes = 0; //当前层的节点数 while (!queue.isEmpty()) { Node cur = queue.po11(); if (cur.left != null) { queue.add(cur.left); nextEnd = cur.left; } if (cur.right != null) { queue.add(cur.right); nextEnd = cur.right; } curLevelNodes++; if (cur == curEnd) { max = Math.max(max, curLevelNodes); curLevelNodes = 0; curEnd = nextEnd; } } return max; }
3 序列化、反序列化二叉树
3.1 按照先序遍历的方式
中序、后序遍历只需要调整顺序即可。
public static Queue<String> preSerial(Node head) { Queue<String> ans = new LinkedList<>(); pres(head, ans); return ans; } public static void pres(Node head, Queue<String> ans) { if (head == null) { ans.add(null); } else { ans.add(String.vaLueOf(head.value)); pres(head.left, ans); pres(head.right, ans); } }
反序列化,代码如下:
public static Node buildByPreQueue(Queue<String> prelist) { if (prelist == null | prelist.size() == 0) { return null; } return preb(prelist); } public static Node preb(Queue<String> prelist) { String value = prelist.poll(); if (value == null) { return null; } Node head = new Node(Integer.valueOf(value)); head.left = preb(prelist); head.right = preb(prelist); return head; }
3.2 按层遍历序列化二叉树
public static Queue<String> levelSerial(Node head) { Queue<String> ans = new LinkedList<>(); if (head == null) { ans.add(null); } else { ans.add(String.valueOf(head.value)); Queue<Node> queue = new LinkedList<Node>(); queue.add(head); while (!queue.isEmpty()) { head = queue.poll(); if (head.left != null) { ans.add(String.valueOf(head.left.value)); queue.add(head.left); } else { ans.add(null); } if (head.right != null) { ans.add(String.valueOf(head.right.value)); queue.add(head.right); } else { ans.add(null); } } } return ans; }
反序列化:
public static Node buildByLeve1Queue(Queue<String> levelList) { if (levelList == null || levelList.size() == 0) { return null; } Node head = generateNode(levelList.poll()); Queue<Node> queue = new LinkedList<Node>(); if (head != null) { queue.add(head); } Node node = null; while (!queue.isEmpty()) { node = queue.poll(); node.left = generateNode(levelList.poll()); node.right = generateNode(levelList.poll()); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } return head; } //获取结点 public static Node generateNode(String val) { if (val == null) { return null; } return new Node(Integer.valueOf(val)); }
4 后继结点的获取
给出一个结点,返回其后继结点(中序遍历时的下一个结点),这里的结点有左结点、右结点、父结点,代码如下:
public static Node getSuccessorNode(Node node) { if (node == null) { return node; } //右子树不为空,说明后继结点为右子树的最左子结点 if (node.right != null) { return getLeftMost(node.right); } else { //无右子树,需要一直往上找 Node parent = node.parent; //当前结点是其父结点的右子结点,继续循环,直到找到当前结点是父结点的左子结点 while (parent != null && parent.left != node) {// node = parent; parent = node.parent; } return parent;//何时返回空?给的node为最右边的子结点 } } //获取最左的结点 public static Node getLeftMost(Node node) { if (node == null) { return node; } while (node.left != null) { node = node.left; } return node; }
前驱结点同理。
5 二叉树的递归套路
1) 假设以X节点为头,假设可以向X左树和X右树要任何信息;
2) 在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要):
常见分类是:和X无关的答案、和X有关的答案;
3) 列出所有可能性后,确定到底需要向左树和右树要什么样的信息;
4) 把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S;
5)递归函数都返回S,每一棵子树都这么要求;
6)写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息。
例题1:判断二叉树的平衡性
//主函数 public static boolean isBalanced2(Node head) { return process2(head).isBalaced; } //左、右要求一样,Info信息返回的结构体 public static class Info { public boolean isBalaced; public int height; public Info(boolean b, int h) { isBalaced = b; height = h; } } public static Info process2(Node X) { if (X == null) { return new Info(true, 0); } Info leftInfo = process2(X.left); Info rightInfo = process2(X.right); int height = Math.max(leftInfo. height, rightInfo . height) + 1; boolean isBalanced = true; if (!leftInfo.isBalaced || !rightInfo.isBalaced|| Math.abs(leftInfo.height - rightInfo.height) > 1) { isBalanced = false; } return new Info(isBalanced, height); }
例二:获取二叉树的最大距离
给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离
//主函数 public static int maxDistance2(Node head) { return process(head).maxDistance; } //需要你返回的信息体 public static class Info { public int maxDistance; public int height; public Info(int dis, int h) { maxDistance = dis; height = h; } } /** * 得到答案的可能性:①最大距离包含头结点,则最大距离=左子结点的高度+右子结点的高度+1; * ②不包含头结点,则最大距离=Max(左子结点的最大距离, 右子结点的最大距离)。 * 总结:最大距离=Max(左子结点的最大距离, 右子结点的最大距离, 左子结点的高度+右子结点的高度+1) */ public static Info process(Node X) { if (X == null) { return new Info(0, 0); } Info leftInfo = process(X.left); Info rightInfo = process(X.right); //获取当前结点的高度 int height = Math.max(leftInfo.height, rightInfo.height) + 1; int maxDistance = Math.max( Math.max(leftInfo.maxDistance, rightInfo.maxDistance), leftInfo.height + rightInfo.height + 1); return new Info(maxDistance, height); }
例三:给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的头节点
public static int maxSubBSTSize2(Node head) { if (head == null) { return 0; } return process(head).maxSubBSTSize; } /** * 任何子树 * isAllBST 是否是二叉搜索树 * maxSubBSTSize 树的size * min、max 树的最小值和最大值 */ public static class Info { public boolean isAllBST; public int maxSubBSTSize; public int min; public int max; public Info(boolean is, int size, int mi, int ma) { isAllBST = is; maxSubBSTSize = size; min = mi; max = ma; } } public static Info process(Node X) { if (X == null) { return null; } Info leftInfo = process(X.left); Info rightInfo = process(X.right); int min = X.value; int max = X.value; //如何获取Info里面的四个信息,先尝试获取max和min if (leftInfo != null) { min = Math.min(min, leftInfo.min); max = Math.max(max, leftInfo.max); } if (rightInfo != null) { min = Math.min(min, rightInfo.min); max = Math.max(max, rightInfo.max); } //再来获取子树的最大搜索树的size //这部分可以理解为:先考虑和当前结点无关的情况,即最大搜索树出现在子树当中 int maxSubBSTSize = 0; if (leftInfo != null) { maxSubBSTSize = leftInfo.maxSubBSTSize; } if (rightInfo != null) { maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize); } boolean isAllBST = false; //处理当前结点 if ( //左树整体需要是搜索二叉树、右树整体需要是搜索二叉树 (leftInfo == null ? true : leftInfo.isAllBST) &&(rightInfo == null ? true : rightInfo.isAllBST) //左树最大值<X、右树最小值>X &&(leftInfo == null ? true : leftInfo.max < X.value) &&(rightInfo == null ? true : rightInfo.min > X.value) ) { maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize) + (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1; isAllBST = true; } //不管三七二十一,先把这个返回值先写上去 return new Info(isAllBST, maxSubBSTSize, min, max); }
例四:给定一棵二叉树的头节点head,和另外两个节点a和b。返回a和b的最低公共祖先
普通方法:先用一个Map把所有结点都放进去,key为当前结点,value为当前结点的父结点(Map<Node, Node.parent>),完事之后创建一个Set,从a结点开始往上遍历(cur = parentMap.get(cur)),遍历一个存放一个进Set,完事之后又从b结点开始往上遍历,每次遍历查看是否已经存在在Set中,是的话则返回,否则继续
利用二叉树的递归套路亦可解决
请把一段纸条竖着放在桌子上,然后从纸条的下边向.上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下 折痕和.上折痕。
给定一个输入参数N,代表纸条都从下边向上方连续对折N次。请从上到下打印所有折痕的方向。
例如:N=1时,打印: down N=2时,打印: down down up