前言
二叉树节点结构:
public static class Node{
public Node left;
public Node right;
public int value;
public Node(int data){
this.value=data;
}
}
代码中实现的二叉树都为图片所示二叉树
一、二叉树的先序、中序、后序遍历
二叉树递归序:1->2->3->3->3->2->4->4->4->2->1->6->7->7->7->6->1
先序:1->2->3->4->6->7
中序:3->2->4->1->7->6
后序:3->4->2->7->6->1
1、递归方式(先序、中序、后序)
public class traverse {
public static void main(String[] args) {
Node head=new Node(1);
head.left=new Node(2);
head.right=new Node(6);
head.left.left=new Node(3);
head.left.right=new Node(4);
head.right.left=new Node(7);
System.out.println("==============二叉树遍历===============");
System.out.print("pree-order: ");
preorderTraversal(head);
System.out.println();
System.out.print("in-order: ");
inorderTraversal(head);
System.out.println();
System.out.print("pos-order: ");
postOrderTraversal(head);
}
// 树节点
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value=data;
}
}
//先序遍历
public static void preorderTraversal(Node head){
if (head==null){
return;
}
System.out.print(head.value+" ");
preorderTraversal(head.left);
preorderTraversal(head.right);
}
//中序遍历
public static void inorderTraversal(Node head){
if (head==null){
return;
}
inorderTraversal(head.left);
System.out.print(head.value+" ");
inorderTraversal(head.right);
}
//后序遍历
public static void postOrderTraversal(Node head){
if (head==null){
return;
}
postOrderTraversal(head.left);
postOrderTraversal(head.right);
System.out.print(head.value+" ");
}
}
运行结果:
2、非递归(先序、中序、后序)
public static void main(String[] args) {
traverse.Node head=new traverse.Node(1);
head.left=new traverse.Node(2);
head.right=new traverse.Node(6);
head.left.left=new traverse.Node(3);
head.left.right=new traverse.Node(4);
head.right.left=new traverse.Node(7);
System.out.println("==============二叉树非递归遍历===============");
preOrderUnRecur(head);
System.out.println();
inOrderUnRecur(head);
System.out.println();
posOrderUnRecur1(head);
}
// 树节点
public static class Node{
public int value;
public traverse.Node left;
public traverse.Node right;
public Node(int data){
this.value=data;
}
}
//先序遍历
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 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 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();
}
运行结果:
二、宽度优先遍历
1.遍历
public static void main(String[] args) {
Node head=new Node(1);
head.left=new Node(2);
head.right=new Node(6);
head.left.left=new Node(3);
head.left.right=new Node(4);
head.right.left=new Node(7);
System.out.println("==========宽度优先遍历===========");
w(head);
}
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value=data;
}
}
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.print(cur.value+" ");
if (cur.left!=null){
queue.add(cur.left);
}
if (cur.right!=null){
queue.add(cur.right);
}
}
}
运行结果:
2、最大宽度查找
public static void main(String[] args) {
Node head=new Node(1);
head.left=new Node(2);
head.right=new Node(6);
head.left.left=new Node(3);
head.left.right=new Node(4);
head.right.left=new Node(7);
System.out.println("==========最大宽度===========");
System.out.println("该树最大宽度为: "+w(head));
}
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value=data;
}
}
public static int 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;
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);
queue.add(cur.left);
}
if (cur.right!=null){
levelMap.put(cur.right,curNodeLevel+1);
queue.add(cur.right);
}
}
max=Math.max(max,curLevelNodes);
return max;
}
运行结果:
三、是否为搜索二叉树
1、搜索二叉树
整棵树右子树要大于左子树的值,称该树为搜索二叉树,如图:
2、判断搜索二叉树
利用中序遍历的特点(左中右),如果为是搜索二叉树,则中序遍历是逐渐增大的。
分为递归和非递归方式
public static void main(String[] args) {
Node head=new Node(8);
head.left=new Node(3);
head.right=new Node(10);
head.left.left=new Node(1);
head.left.right=new Node(6);
head.left.right.left=new Node(4);
head.left.right.right=new Node(7);
head.right.right=new Node(14);
head.right.right.left=new Node(13);
System.out.print("递归方式: ");
System.out.println(checkBST(head)?"是一颗搜索二叉树":"不是一颗搜索二叉树");
System.out.print("非递归方式: ");
System.out.println(checkBST2(head)?"是一颗搜索二叉树":"不是一颗搜索二叉树");
}
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value=data;
}
}
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 int preValue2=Integer.MIN_VALUE;
public static Boolean checkBST2(Node head){
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();
if (head.value<=preValue2){
return false;
}else {
preValue2= head.value;
}
head=head.right;
}
}
}
return true;
}
运行结果:
套路递归:
每一颗子树都应该满足:左子树<父节点<右子树
构建信息体:
public static class ReturnData{// 信息体
public boolean isBST;
public int min;
public int max;
public ReturnData(boolean is,int min,int max){
this.isBST=is;
this.min=min;
this.max=max;
}
}
加工信息:
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 ||x.value>=rightData.min)){
isBST=false;
}
return new ReturnData(isBST,min,max);//最终返回
}
四、是否为完全二叉树
完全二叉树的两个条件
- 某个节点左孩子为空右孩子不能为空
- 在一的条件下遇到过左右两孩子不双全的节点,当前节点不能有叶子节点
代码如下
public static void main(String[] args) {
Node head=new Node(1);
head.left=new Node(2);
head.right=new Node(3);
head.left.left=new Node(4);
head.left.right=new Node(5);
head.right.left=new Node(6);
head.right.right=new Node(7);
head.left.left.left=new Node(8);
head.left.left.right=new Node(9);
head.left.right.left=new Node(10);
head.left.right.right=new Node(11);
head.right.left.left=new Node(12);
System.out.println("是否为完全二叉树: "+isBCT(head));
}
public static class Node{
public Node left;
public Node right;
public int value;
public Node(int data ){
this.value=data;
}
}
/*
判断是否为完全二叉树需要两个条件
1、左孩子为空右孩子不能为空
2、在一的条件下遇到过左右两孩子不双全的节点,当前节点不能有叶子节点
*/
public static Boolean isBCT(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;
}
运行结果
五、是否为满二叉树
满二叉树的条件
记一棵树最大深度为L,所有节点数为N,满二叉树存在以下关系:
N=2^L-1
构建信息体,只要知道树的高度和节点个数
public static class ReturnData{
public boolean isF;
public int height;
public int nodes;
public ReturnData(boolean isF ,int height,int nodes){
this.height=height;
this.nodes=nodes;
this.isF=isF;
}
}
信息处理
//加工信息
public static ReturnData process(Node x){
if (x==null){
return new ReturnData(true,0,0);
}
ReturnData leftData=process(x.left);
ReturnData rightData=process(x.right);
boolean isF=true;
int height=Math.max(leftData.height,rightData.height)+1;
int nodes=leftData.nodes+ rightData.nodes+1;
// 是否符合 N=2^L-1
if (nodes!=Math.pow(2,height)-1){
isF=false;
}
return new ReturnData(isF,height,nodes);
}
}
完整代码
public static void main(String[] args) {
Node head=new Node(1);
head.left=new Node(2);
head.right=new Node(3);
head.left.left=new Node(4);
head.left.right=new Node(5);
head.right.left=new Node(6);
head.right.right=new Node(7);
head.left.left.left=new Node(8);
head.left.left.right=new Node(9);
head.left.right.left=new Node(10);
head.left.right.right=new Node(11);
head.right.left.left=new Node(12);
head.right.left.right=new Node(13);
head.right.right.left=new Node(14);
head.right.right.right=new Node(15);
System.out.println("节点数: "+process(head).nodes);
System.out.println("高度: "+process(head).height);
System.out.println("是否为满二叉树: "+process(head).isF);
}
public static class Node{
public Node left;
public Node right;
public int value;
public Node(int data){
this.value=data;
}
}
/*
构建信息体
*/
public static class ReturnData{
public boolean isF;
public int height;
public int nodes;
public ReturnData(boolean isF ,int height,int nodes){
this.height=height;
this.nodes=nodes;
this.isF=isF;
}
}
//加工信息
public static ReturnData process(Node x){
if (x==null){
return new ReturnData(true,0,0);
}
ReturnData leftData=process(x.left);
ReturnData rightData=process(x.right);
boolean isF=true;
int height=Math.max(leftData.height,rightData.height)+1;
int nodes=leftData.nodes+ rightData.nodes+1;
// 是否符合 N=2^L-1
if (nodes!=Math.pow(2,height)-1){
isF=false;
}
return new ReturnData(isF,height,nodes);
}
六、是否为平衡二叉树
平衡二叉树
对任何一个子树来说,左数和右树的高度差不超过1。
- 左子树为平衡二叉树
- 右子树为平衡二叉树
- |左子树高度-右子树高度|<=1
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 ReturnType{ // 子树信息结构体
public boolean isBalanced;// 是否平衡
public int height;// 树高度
public ReturnType(boolean isB,int height){// 构造函数
this.isBalanced=isB;
this.height=height;
}
}
完整代码:
public static void main(String[] args) {
Node head=new Node(5);
head.left=new Node(3);
head.right=new Node(7);
head.left.left=new Node(2);
head.left.right=new Node(4);
head.right.left=new Node(6);
System.out.println("树高为:"+isBalanced(head).height);
System.out.println("是否为平衡二叉树:"+isBalanced(head).isBalanced);
}
public static class Node{
public Node left;
public Node right;
public int value;
public Node(int data){
this.value=data;
}
}
public static ReturnType isBalanced(Node head){// 主函数
return process(head);
}
public static class ReturnType{ // 子树信息结构体
public boolean isBalanced;// 是否平衡
public int height;// 树高度
public ReturnType(boolean isB,int height){// 构造函数
this.isBalanced=isB;
this.height=height;
}
}
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);
}
运行结果:
七、二叉树题目应用
1、最低公共祖先节点
题目描述:
给定两个二叉树的节点node1和node2,找到他们最低公共祖先节点
如图G和F节点的最低公共祖先节点为C,E和G的 最低公共祖先为E。
解题思路:
理解版:
将其中一个节点往上回溯的其余节点和他本身放到一个集合中,如F节点往上回溯将A、C、F三个节点放入集合中,再将另一个节点往上回溯,每次回溯都去第一个集合查询是否存在该节点,没有就往上回溯,直到第一个出现在第一集合中的节点就是最低公共祖父节点,如G节点往上回溯,发现G、E不存在,最终找到C,C就是该最低祖父节点。
储存关系集合:
//迭代将整个树关系储存
public static void process(Node head, HashMap<Node,Node>fatherMap){
if (head==null){
return;
}
fatherMap.put(head.left,head);
fatherMap.put(head.right,head);
process(head.left,fatherMap);
process(head.right,fatherMap);
}
回溯查找:
public static Node lca(Node head,Node o1,Node o2){
HashMap<Node,Node>fatherMap=new HashMap<>();
fatherMap.put(head,head);
process(head,fatherMap);//储存整棵树各个节点的回溯关系
HashSet<Node>set1=new HashSet<>();
HashSet<Node>set2=new HashSet<>();
Node cur=o1;
while (cur!=fatherMap.get(cur)){//第一个节点回溯添加
set1.add(cur);
cur=fatherMap.get(cur);
}
set1.add(head);// 将头节点添加
cur=o2;
while (cur!=fatherMap.get(cur)){//第二个节点回溯添加
set2.add(cur);
cur=fatherMap.get(cur);
}
for (Node node1:set2){
for (Node node2 : set1) {
if (node1==node2){
return node2;
}
}
}
简易版:
1、当o1是 o2的最低公共祖先或当o2是 o1的最低公共祖先
2、o1和o2不互为公共祖先,必须向上回溯才能找到
public static Node lca2(Node head,Node o1,Node o2){
if (head==null || head==o1 || head==o2){
return head;
}
//以头节点分成左右两个树
Node left=lca2(head.left, o1, o2);//左树返回
Node right=lca2(head.right, o1, o2);//右树返回
if (left!=null && right!=null){// 条件二判断
return head;
}
//左右两棵树,并不都有返回值(条件一)
return left!=null ? left:right;
}
完整代码:
public static void main(String[] args) {
Node head=new Node("A");
head.left=new Node("B");
head.right=new Node("C");
head.left.left=new Node("D");
head.right.left=new Node("E");
head.right.right=new Node("F");
head.right.left.left=new Node("G");
System.out.println("理解版最低祖父公共节点为:"+lca(head,head.right.left.left,head.right.right).value);
System.out.println("简洁版最低祖父公共节点为:"+lca2(head,head.right.left.left,head.right.right).value);
}
public static class Node{
public Node left;
public Node right;
public String value;
public Node(String data){
this.value=data;
}
}
/*
易理解版
*/
public static Node lca(Node head,Node o1,Node o2){
HashMap<Node,Node>fatherMap=new HashMap<>();
fatherMap.put(head,head);
process(head,fatherMap);//储存整棵树各个节点的回溯关系
HashSet<Node>set1=new HashSet<>();
HashSet<Node>set2=new HashSet<>();
Node cur=o1;
while (cur!=fatherMap.get(cur)){//第一个节点回溯添加
set1.add(cur);
cur=fatherMap.get(cur);
}
set1.add(head);// 将头节点添加
cur=o2;
while (cur!=fatherMap.get(cur)){//第二个节点回溯添加
set2.add(cur);
cur=fatherMap.get(cur);
}
for (Node node1:set2){
for (Node node2 : set1) {
if (node1==node2){
return node2;
}
}
}
return head;
}
//迭代将整个树关系储存
public static void process(Node head, HashMap<Node,Node>fatherMap){
if (head==null){
return;
}
fatherMap.put(head.left,head);
fatherMap.put(head.right,head);
process(head.left,fatherMap);
process(head.right,fatherMap);
}
/*
简洁版:
1、当o1是 o2的最低公共祖先或当o2是 o1的最低公共祖先
2、o1和o2不互为公共祖先,必须向上回溯才能找到
*/
public static Node lca2(Node head,Node o1,Node o2){
if (head==null || head==o1 || head==o2){
return head;
}
//以头节点分成左右两个树
Node left=lca2(head.left, o1, o2);//左树返回
Node right=lca2(head.right, o1, o2);//右树返回
if (left!=null && right!=null){// 条件二判断
return head;
}
//左右两棵树,并不都有返回值(条件一)
return left!=null ? left:right;
}
运行结果:
2、后继节点
题目描述:
不通过中序遍历找到某个节点的后继(前继)节点
后继节点就是中序遍历的当前节点的后一个节点,前继相反
如图二叉树中序遍历为:D->B->A->G->E->C->F
则B的后继为A,前继为D
解题思路:
后继:
- 当需要找的节点有右子树时,他的后继就是右子树的最左子树(没有最左时就是本身)
- 当需要找的节点有右子树时,向上寻找节点,直到找到某个节点是该节点头节点的左子树时,该节点的头就是后继
根据情况一向左一直找最左子树
// 向左寻找最左子树
public static Node getLeftMost(Node node){
if (node==null){
return null;
}
while (node.left!=null){
node=node.left;
}
return node;
}
根据情况二找
Node parent=node.parent;
while (parent!=null && parent.left!=node){//当前节点是其父亲节点右孩子 情况二
node=parent; //往上移动
parent=node.parent;
}
return parent;
完整代码
public static void main(String[] args) {
Node head=new Node("A");
head.left=new Node("B");
head.right=new Node("C");
head.left.left=new Node("D");
head.right.left=new Node("E");
head.right.right=new Node("F");
head.right.left.left=new Node("G");
head.parent=null;
head.left.parent=head;
head.right.parent=head;
head.left.left.parent=head.left;
head.right.left.parent=head.right;
head.right.right.parent=head.right;
head.right.left.left.parent=head.right.left;
System.out.println("G节点的后继为:"+getSuccessorNode(head.right.left.left).value);
}
public static class Node{
public String value;
public Node left;
public Node right;
public Node parent;// 指向自己的父节点
public Node(String value){
this.value=value;
}
}
public static Node getSuccessorNode(Node node){
if (node==null){
return null;
}
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;
}
}
// 向左寻找最左子树
public static Node getLeftMost(Node node){
if (node==null){
return null;
}
while (node.left!=null){
node=node.left;
}
return node;
}
运行结果
3、二叉树的序列化与反序列化
题目描述:
从字符串到内存结构的转换叫反序列化,从内存结构到字符串的转换叫序列化
以‘#’表示null,节点之间用’_'隔开
如图所示二叉树的先序为:A_B_D_C_E_G_F
(1)二叉树的序列化
public static String serialByPre(Node head){
if (head==null){
return "#_";
}
String res=head.value+"_";
res+=serialByPre(head.left);
res+=serialByPre(head.right);
return res;
}
运行结果
(1)二叉树的反序列化
//反序列化主函数
public static Node reconByPreString(String preStr){
String[]values=preStr.split("_");
Queue<String> queue = new LinkedList<String>(Arrays.asList(values));//将每一项复制给队列
return reconPreOrder(queue);
}
//反序列化处理
public static Node reconPreOrder(Queue<String>queue){
String value=queue.poll();
if (Objects.equals(value, "#")){
return null;
}
Node head=new Node(value);
head.left=reconPreOrder(queue);//左树反序列
head.right=reconPreOrder(queue);//右树反序列
return head;
}
4、折纸条问题
问题描述:
一个纸条向自己的方向向上折N次,依次打印折痕方向(折痕向外为‘凹’,向内为‘凸’)。如折一次时,纸条的折痕为‘凹’;折两次时,折痕为‘凹凹凸’;三次时,折痕为‘凹凹凸凹凹凸凸’。
解题思路:
经过模拟发现折痕是一颗总头节点为‘凹’ 左子树为‘凹’,右子树为‘凸’的满树,如果打印折痕就是将这棵树中序遍历。
代码实现
public static void main(String[] args) {
int N=3;
printAllFolds(N);
}
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?"凹":"凸");
printProcess(i+1,N,false);
}
运行结果
总结
本文简单介绍了二叉树的三种遍历,以及相关二叉树的识别以及一些二叉树的应用题。