目录
二叉树的遍历
递归遍历
先序遍历:对于二叉树先打印头节点,然后打印左子树,右子树。
中序遍历:对于二叉树先打印左子树,然后打印头节点,右子树。
后序遍历:对于二叉树先打印左子树,然后打印右子树,头节点。
而对于这三个顺序的遍历,可以通过递归顺序遍历二叉树取相应的节点得到。先使用递归遍历完二叉树所有的节点,每一个节点被访问三次,对于先序遍历就是依次取出第一次访问的节点,中序遍历就是依次取出第二次访问的节点,后序遍历就是依次取出第三次访问的节点。
public static void preOrderRecur(Node head) {//先序遍历
if (head == null) {
return;
}
System.out.print(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
public static void inOrderRecur(Node head) {//中序遍历
if (head == null) {
return;
}
inOrderRecur(head.left);
System.out.print(head.value + " ");
inOrderRecur(head.right);
}
public static void posOrderRecur(Node head) {//后序遍历
if (head == null) {
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.print(head.value + " ");
}
非递归遍历
先序遍历:使用栈进行非递归遍历,先将二叉树头节点压入栈中,然后从栈中弹出一个节点,打印,然后先右子树,再左子树(如果有,没有的话直接跳过这个步骤),然后重复上述操作,完成先序遍历。
public static void preOrderUnRecur(Node head) {//先序遍历
System.out.print("pre-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
stack.add(head);
while (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.value + " ");
if (head.right != null) {
stack.push(head.right);
}
if (head.left != null) {
stack.push(head.left);
}
}
}
System.out.println();
}
后序遍历:准备两个栈,先将头节点压入第一个栈,然后弹出放入另一个栈,接着先左子树,后右子树继续同样的操作,执行下去,直到最后,最后把另一个栈的节点全部出栈,打印出来就是后序遍历。(因为压入第一个栈的顺序是头左右,那么到另一个栈的顺序就是头右左,再全部出栈打印出来就是左右头)
public static void posOrderUnRecur1(Node head) {//后续遍历
System.out.print("pos-order: ");
if (head != null) {
//准备两个栈
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while (!s1.isEmpty()) {
head = s1.pop();
s2.push(head);
if (head.left != null) {
s1.push(head.left);
}
if (head.right != null) {
s1.push(head.right);
}
}
while (!s2.isEmpty()) {
System.out.print(s2.pop().value + " ");
}
}
System.out.println();
}
中序遍历:每棵子树整棵树左边界进栈,依次弹出节点打印,同时对弹出节点的右树执行上述操作
public static void inOrderUnRecur(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();
}
如何直观打印一棵树
这个方法可以用来直观查看自己所做的树的形状,实现的代码如下:
public class Code_PrintBinaryTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
printInOrder(head.right, height + 1, "v", len);
String val = to + head.value + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = getSpace(lenL) + val + getSpace(lenR);
System.out.println(getSpace(height * len) + val);
printInOrder(head.left, height + 1, "^", len);
}
public static String getSpace(int num) {
String space = " ";
StringBuffer buf = new StringBuffer("");
for (int i = 0; i < num; i++) {
buf.append(space);
}
return buf.toString();
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(-222222222);
head.right = new Node(3);
head.left.left = new Node(Integer.MIN_VALUE);
head.right.left = new Node(55555555);
head.right.right = new Node(66);
head.left.left.right = new Node(777);
printTree(head);
head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.right.left = new Node(5);
head.right.right = new Node(6);
head.left.left.right = new Node(7);
printTree(head);
head = new Node(1);
head.left = new Node(1);
head.right = new Node(1);
head.left.left = new Node(1);
head.right.left = new Node(1);
head.right.right = new Node(1);
head.left.left.right = new Node(1);
printTree(head);
}
}
宽度优先遍历
介绍及实现
二叉树的先序遍历就是二叉树的深度优先遍历,而宽度优先遍历采用的是队列实现,先将头节点放入队列,然后弹出打印,接着从上到下,先左子树后右子树,进入队列,然后弹出打印,重复操作。
public static void w(Node head){
if(head == null){
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while(!queue.isEmpty()){
Node cur = queue.poll();
System.out.println(cur.value);
if(cur.left!= null){
queue.add(cur.left);
}
if(cur.right!=null){
queue.add(cur.right);
}
}
}
实际应用
求一棵二叉树的最大宽度?
采用哈希表进行解决,记录每一层的层数和节点数,得出最大的宽度
//使用哈希表的方式解决,记录每一层的层数和节点数
public static void w(Node head){
if(head == null){
return 0;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
HashMap<Node,Integer> levelMap = new HashMap<>();//设置一个哈希表,用来存放当前节点的层数
levelMap.put(head,1);//先将头节点放进去作为第一层
int curlevel = 1;//当前的层数
int curLevelNodes = 0;//当前层的节点数,因为head只是进去了还没有弹出
int max = Integer.MIN_VALUE;//全局最大层数的节点数
while(!queue.isEmpty()){
Node cur = queue.poll();//从队列弹出一个节点
int curNodeLevel = levelMap.get(cur);//当前节点的层数
if(curNodeLevel == curLevel){//如果当前节点的层数和当前层数相等,当前层数的节点数增加
curLevelNodes++;
}
else{
max = Math.max(max,curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
if(cur.left!= null){
levelMap.put(cur.left,curNodeLevel+1);//左子树进入时当前节点的层数+1,作为当前层数
queue.add(cur.left);
}
if(cur.right!=null){
levelMap.put(cur.right,curNodeLevel+1);
queue.add(cur.right);
}
}
return max;
}
二叉树的相关概念及其实现判断
如何判断一棵二叉树是否是搜索二叉树?
搜索二叉树指的是每一棵子树,左子树都比根节点小,右子树都比根节点大。搜索二叉树的判断,采用中序遍历的方式,如果将一棵二叉树采用中序遍历得到的节点是升序的,那么它就是一棵搜索二叉树。只需要在中序遍历的基础上进行一些判断即可,递归与非递归的实现方式分别如下:
//递归方式判断
public static int preValue = Integer.MIN_VALUE;
public static boolean checkBST(Node head) {
if (head == null) {
return true;//如果树为空,那么它是一棵搜索二叉树,直接返回。
}
boolean isLeftBst = checkBST(head.left);//先检查左子树
if(!isLeftBst){
return false;
}
if(head.value <= preValue){//将左子树上的最后一个值和当前最小值进行比较
return false;
}
else{
preValue = head.value;//将左子树的最后一个值作为当前的最小值
}
return checkBST(head.right);//检查右子树
}
//非递归方式判断
public static void checkBST(Node head) {
if (head != null) {
int preValue = Integer.MIN_VALUE;
Stack<Node> stack = new Stack<Node>();
while (!stack.isEmpty() || head != null) {
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
if(head.value <= preValue){
return false;
}
else{
preValue = head.value;
}
head = head.right;
}
}
}
return true;
}
如果判断一棵二叉树是否为完全二叉树?
完全二叉树指的是一棵树每一层从左到右依次变满。对二叉树采用宽度优先遍历的方式进行遍历,同时进行判断,对于任意一个节点,如果只有右子树没有左子树,那么它一定不是完全二叉树,在上述满足的基础上,如果遇到了第一个左右子树不全的节点,那么它后面的节点都是叶节点。
public static boolean isCBT(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
boolean leaf = false;//表示是否遇到左右孩子不双全的节点
Node l = null;
Node r = null;
queue.add(head);//头节点进入队列
while (!queue.isEmpty()) {
head = queue.poll();
l = head.left;//左孩子
r = head.right;//右孩子
if ((leaf && (l != null || r != null)) //如果遇到了不双全节点后,又发现当前节点居然有孩子
|| (l == null && r != null)) {
return false;
}
if (l != null) {
queue.add(l);
}
if (r != null) {
queue.add(r);
}
if(l ==null || r==null){
leaf = true;
}
}
return true;
}
二叉树的递归套路的应用
如何判断一棵树是否为平衡二叉树?
平衡二叉树指的是左右两棵子树的深度相差的绝对值小于等于1。这个问题的解决采用的是二叉树的递归套路,这个套路的实现首先你要明白你需要从左右两棵子树得到什么信息,判断一棵树是否为平衡二叉树需要从左右子树得到信息为,左子树是否为平衡二叉树,右子树是否为平衡二叉树,以及两棵子树分别的深度。最后对于整体的自身,也需要同样的信息作为返回,这样才能成功的将递归连起来。具体实现的代码如下:
public static boolean isBalanced(Node head) {
return process(head).isBalanced;
}
public static class ReturnType {
public boolean isBalanced;//是否是平衡二叉树
public int height;//深度
public ReturnType(boolean isB, int hei) {
isBalanced = isB;
height = hei;
}
}
public static ReturnType process(Node x) {
if (x == null) {
return new ReturnType(true, 0);
}
ReturnType leftData = process(x.left);
ReturnType rightData = process(x.right);
int height = Math.max(leftData.height, rightData.height)+1;
boolean isBalanced = leftData.isBalanced && rightData.isBalanced
&& Math.abs(leftData.height - rightData.height) < 2;
return new ReturnType(isBalanced, height);
}
如何判断一棵二叉树是否是搜索二叉树?
对于这个问题同样可以采用上面的递归套路的方式进行求解,首先进行分析,我需要从左右两棵子树得到什么信息,我需要得到的是左子树是否为搜索二叉树,左子树的最大值,右子树是否为搜索二叉树,右子树的最小值,但是由于采用递归的方式实现要求结构相同,所以统一需要左右两棵子树是否为搜索二叉树,以及最大值和最小值的信息。最后对于整体的自身,也需要同样的信息作为返回,这样才能成功的将递归连起来。
public static class ReturnData{
public boolean isBST;
public int min;
public int max;
public ReturnData(boolean is,int mi,int ma){
isBST = is;
min = mi;
max = ma;
}
}
public static ReturnData process(Node x){
if(x == null){
return null;
}
ReturnData leftData = process(x.left);
ReturnData rightData = process(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){
min = Math.min(min,rightData.min);
max = Math.max(max,rightData.max);
}
boolean isBST = true;
if(leftData!=null&&(!leftData.isBST || leftData.max >=x.value)){
isBST = false;
}
if(rightData!=null&&(!rightData.isBST || rightData.min<=x.value)){
isBST = false;
}
return new ReturnData(isBST,min,max);
}
如何判断一棵树是否是满二叉树?
对于这个问题,同样采用递归套路的方式轻松解决。
public static boolean isF(Node head){
if(head == null){
return true;
}
Info data = f(head);
return data.nodes == (1<<data.height - 1);
}
public static class Info{
public int height;
public int nodes;
public Info(int h,int n){
height = h;
nodes = n;
}
}
public static Info f(Node x){
if(x == null){
return new Info(0,0);
}
Info leftData = f(x.left);
Info rightData = f(x.right);
int height = Math.max(leftData.height,rightData.height)+1;
int nodes = leftData.nodes + rightData.nodes+1;
return new Info(height,nodes);
}
上面三个问题都可以采用递归套路去解决,递归套路主要用于解决二叉树中从左右两棵子树中得到信息去求解,然后从每一棵子树中得到的信息是相同的。(这一类问题其实也就是树形DP问题)
二叉树的应用
寻找最低公共祖先
给定两个二叉树的节点node1和node2,找到它们的最低公共祖先节点(指的是两个节点往后上走,第一个汇聚的点)
这个问题分为两种情况,第一种就是node1是node2的最低公共祖先节点,或者node2是node1的最低公共祖先;第二种情况就是node1与node2不互为公共祖先。
public static Node lowestAncestor(Node head, Node o1, Node o2) {
if (head == null || head == o1 || head == o2) {
return head;
}
Node left = lowestAncestor(head.left, o1, o2);
Node right = lowestAncestor(head.right, o1, o2);
if (left != null && right != null) {
return head;
}
//左右两棵树,并不都有返回值
return left != null ? left : right;
}
找一个节点的后继节点
现在有一种新的二叉树节点类型如下:
public class Node{
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val){
value = val;
}
}
该结构比普通二叉树节点结构多了一个指向父节点的parent指针。 假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null。 只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。 在二叉树的中序遍历的序列中, node的下一个节点叫作node的后继节点。
这个问题有两种情况:如果node有右子树,那么它的后继节点就是它的右子树上的最左节点;如果node没有右子树,向上寻找直到向上寻找的节点是它的父节点的左孩子,那么这个父节点就是node的后继节点,但是如果一直到最后都没有找到,那么就是null。
public static Node getSuccessorNode(Node node) {
if (node == null) {
return node;
}
if (node.right != null) {//如果node有右子树,找到它的最左的孩子
return getLeftMost(node.right);
} else {//无右子树
Node parent = node.parent;
while (parent != null && parent.left != node) {
node = parent;
parent = node.parent;
}
return parent;
}
}
public static Node getLeftMost(Node node) {
if (node == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
二叉树的序列化和反序列化
就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树
//序列化,这里采用先序方式
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.offer(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;
}
折纸问题
请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。 此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。 如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。 给定一个输入参数N,代表纸条都从下边向上方连续对折N次。 请从上到下打印所有折痕的方向。 例如:N=1时,打印: down N=2时,打印: down down up
拿一张纸按照上述的要求折出来,然后做上标记,最后统计出来以后,第一次折出来的是凹折痕,然后下一次就是在上一次的凹折痕上面出现一个凹折痕,下面出现一个凸折痕,后面都是同样的规律。最后折出来的情况就是,每一棵子树的左子树都是凹折痕,右子树都是凸折痕的一棵树。
public class Code_PaperFolding {
public static void printAllFolds(int N) {
printProcess(1, N, true);
}
//递归过程,来到了某一个节点
//i是节点的层数,N一共的层数,down==true 凹 down == false 凸
public static void printProcess(int i, int N, boolean down) {
if (i > N) {
return;
}
printProcess(i + 1, N, true);
System.out.println(down ? "down " : "up ");
printProcess(i + 1, N, false);
}
public static void main(String[] args) {
int N = 1;
printAllFolds(N);
}
}