二叉树是有限个节点的集合,这个集合可以是空集,也可以是一个根节点和至多两个子二叉树组成的集合,其中一颗树叫做根的左子树,另一棵叫做根的右子树。
二叉树的实现
1. 节点实现
class BinaryTreeNode{
private int data;//数据
private BinaryTreeNode leftChild;//左儿子
private BinaryTreeNode rightChild;//右儿子
public BinaryTreeNode(int data, BinaryTreeNode leftChild, BinaryTreeNode rightChild) {
this.data = data;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public BinaryTreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(BinaryTreeNode leftChild) {
this.leftChild = leftChild;
}
public BinaryTreeNode getRightChild() {
return rightChild;
}
public void setRightChild(BinaryTreeNode rightChild) {
this.rightChild = rightChild;
}
}
2. 二叉树的构造函数
2.1 二叉树的成员变量是根节点,只用保存根节点,之后的孩子节点都可以依次遍历出来
public class BinaryTree {
private BinaryTreeNode root;//根节点
}
2.2 无参构造
//初始化空二叉树
public BinaryTree( ) {
}
2.3 有参构造
//初始化二叉树
public BinaryTree(BinaryTreeNode root) {
this.root = root;
}
3. 基本方法实现
public class BinaryTree {
private BinaryTreeNode root;//根节点
//初始化空二叉树
public BinaryTree( ) {
}
//初始化二叉树
public BinaryTree(BinaryTreeNode root) {
this.root = root;
}
public BinaryTreeNode getRoot() {
return root;
}
public void setRoot(BinaryTreeNode root) {
this.root = root;
}
//插入,如果插入位原先有节点会被覆盖
public void insertLeftChild(BinaryTreeNode parent, BinaryTreeNode newNode){
//先判断父节点是否为空
checkTreeEmpty(parent);
parent.setLeftChild(newNode);
}
public void insertRightChild(BinaryTreeNode parent, BinaryTreeNode newNode){
checkTreeEmpty(parent);
parent.setRightChild(newNode);
}
//删除,删除节点会把以该节点为根的树递归删除
public void clear(BinaryTreeNode node){
if(node!=null){
clear(node.getLeftChild());
clear(node.getRightChild());
node = null;
}
}
//删除整个树
public void clear(){
clear(root);
}
//判断二叉树是否为空
public boolean isEmpty(){
return root == null;
}
/**
* 求二叉树的高度:
* 首先要一种获取以某个节点为子树的高度的方法,使用递归调用。
* 如果一个节点为空,那么这个节点肯定是一颗空树,高度为0;
* 如果不为空,那么我们要遍历地比较它的左子树高度和右子树高度,
* 高的一个为这个子树的最大高度,然后加上自己本身的高度就是了
* 获取二叉树的高度,只需要调用第一种方法,即传入根节点
*/
//获取二叉树的高度
public int heigh(){
return heigh(root);
}
//获取以某节点为子树的高度
public int heigh(BinaryTreeNode node){
if(node==null){
return 0; //递归结束,空子树高度为0
}else{
//递归获取左子树高度
int l = heigh(node.getLeftChild());
//递归获取右子树高度
int r = heigh(node.getRightChild());
//高度应该算更高的一边,(+1是因为要算上自身这一层)
return l>r? (l+1):(r+1);
}
}
/**
* 获取二叉树的节点数
*/
public int size(){
return size(root);
}
/**
* 求二叉树的节点数:
* 求节点数时,我们看看获取某个节点为子树的节点数的实现。
* 首先节点为空,则个数肯定为0;
* 如果不为空,那就算上这个节点之后继续递归所有左右子树的子节点数,
* 全部相加就是以所给节点为根的子树的节点数
* 如果求二叉树的节点数,则输入根节点即可
*/
public int size(BinaryTreeNode node){
if(node==null){
return 0; //如果节点为空,则返回节点数为0
}else{
//计算本节点 所以要+1
//递归获取左子树节点数和右子树节点数,最终相加
return 1+size(node.getLeftChild())+size(node.getRightChild());
}
}
//node节点在subTree子树中的父节点
public BinaryTreeNode getParent(BinaryTreeNode subTree,BinaryTreeNode node){
if(subTree==null){
return null; //如果是空子树,则没有父节点
}
if(subTree.getLeftChild()==node || subTree.getRightChild() == node){
return subTree; //如果子树的根节点的左右孩子之一是待查节点,则返回子树的根节点
}
BinaryTreeNode parent = null;
if(getParent(subTree.getLeftChild(),node)!=null){
parent = getParent(subTree.getRightChild(),node);
return parent;
}else{
//递归左右子树
return getParent(subTree.getRightChild(),node);
}
}
//查找node节点在二叉树中的父节点
public BinaryTreeNode getParent(BinaryTreeNode node){
return (root==null||root==node)? null:getParent(root,node);
}
private void checkTreeEmpty(BinaryTreeNode parent) {
if (parent == null) {
throw new IllegalStateException("Can't insert to a null tree! Did you forget set value for root?");
}
}
}
4. 二叉树的遍历
所有的序都是根节点的访问顺序
4.1 先序遍历(PreOrder)
① 访问根节点
② 先根遍历左子树
③ 先根遍历右子树
按照先根遍历地方式,遍历如下二叉树,则访问顺序为:A、B、D、H、I、E、J、C、F、G
//先序遍历
public void PreOrder(BinaryTreeNode node){
if(node!=null){
System.out.println(node.getData()); //先访问根节点
PreOrder(node.getLeftChild()); //先根遍历左子树
PreOrder(node.getRightChild()); //先根遍历右子树
}
}
使用栈的非递归实现
整个遍历过程如下:首先将A节点压入栈中,stack(A);
将A节点弹出,同时将A的子节点C,B压入栈中,此时B在栈的顶部,stack(B,C);
将B节点弹出,同时将B的子节点E,D压入栈中,此时D在栈的顶部,stack(D,E,C);
将D节点弹出,同时将D的子节点I,H压入栈中,此时D在栈的顶部,stack(H,I,E,C);
…依次往下,最终遍历完成,遍历结果是:A、B、D、H、I、E、J、C、F、G
public ArrayList<Integer> DfsTree(TreeNode root) {
ArrayList<Integer> lists = new ArrayList<I>();
if(root == null)
return lists;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode tree = stack.pop();
//先往栈中压入右节点,再压左节点,出栈就是先左节点后出右节点了(先序遍历推广)。
if(tree.right != null)
stack.push(tree.right);
if(tree.left != null)
stack.push(tree.left);
lists.add(tree.val);
}
return lists;
}
4.2 中序遍历(InOrder)
① 中根遍历左子树
② 访问根节点
③ 中根遍历右子树
按照中根遍历地方式,遍历如下二叉树,则访问顺序为:H、D、I、B、J、E、A、F、C、G
//中序
public void InOrder(BinaryTreeNode node){
if(node!=null){
InOrder(node.getLeftChild()); //中根遍历左子树
System.out.println(node); //访问根节点
InOrder(node.getRightChild()); //中根遍历右子树
}
}
基于栈的中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
}
return list;
}
}
颜色标记法实现中序遍历
使用栈辅助迭代实现颜色标记法,使用颜色标记节点的状态,新节点为白色,已访问的节点为黑色。
如果遇到的节点为白色,则将其标记为黑色,然后将其右子节点、自身、左子节点依次入栈。
如果遇到的节点为黑色,则将节点的值输出。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.List;
import java.util.Stack;
class ColorNode{
int color;//0为白 1为黑 黑色是遍历过的
TreeNode node;
public ColorNode(int color, TreeNode node){
this.color = color;
this.node = node;
}
}
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<ColorNode> stack = new Stack();
List<Integer> list = new ArrayList();
if(root!=null){
stack.push(new ColorNode(0,root));
while(!stack.isEmpty()){
ColorNode colorNode = stack.pop();
if(colorNode.color == 0){//没有遍历过
if(colorNode.node.right!=null){
stack.push(new ColorNode(0,colorNode.node.right));//加入右儿子
}
stack.push(new ColorNode(1,colorNode.node));
if(colorNode.node.left!=null){
stack.push(new ColorNode(0,colorNode.node.left));//加入左儿子
}
}else{
list.add(colorNode.node.val);
}
}
}
return list;
}
}
4.3 后根遍历(PostOrder)
① 后根遍历左子树
② 后根遍历右子树
③ 访问根节点
按照后根遍历地方式,遍历如下二叉树,则访问顺序为:H、I、D、J、E、B、F、G、C、A
//后序
public void PostOrder(BinaryTreeNode node){
if(node!=null){
PostOrder(node.getLeftChild()); //后根遍历左子树
PostOrder(node.getRightChild()); //后根遍历右子树
System.out.println(node); //访问根节点
}
}
使用迭代实现后序遍历有一种取巧的方法,利用先序遍历“根左右”的遍历顺序,将先序遍历顺序更改为“根右左”,反转结果List,得到结果顺序为“左右根”
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
if(node.left != null) stack.push(node.left);//和传统先序遍历不一样,先将左结点入栈
if(node.right != null) stack.push(node.right);//后将右结点入栈
res.add(0,node.val); //逆序添加结点值
}
return res;
}
5. 完整代码
package test;
public class BinaryTree {
private BinaryTreeNode root;//根节点
//初始化空二叉树
public BinaryTree( ) {
}
//初始化二叉树
public BinaryTree(BinaryTreeNode root) {
this.root = root;
}
public BinaryTreeNode getRoot() {
return root;
}
public void setRoot(BinaryTreeNode root) {
this.root = root;
}
//插入,如果插入位原先有节点会被覆盖
public void insertLeftChild(BinaryTreeNode parent, BinaryTreeNode newNode){
//先判断父节点是否为空
checkTreeEmpty(parent);
parent.setLeftChild(newNode);
}
public void insertRightChild(BinaryTreeNode parent, BinaryTreeNode newNode){
checkTreeEmpty(parent);
parent.setRightChild(newNode);
}
//删除,删除节点会把以该节点为根的树递归删除
public void clear(BinaryTreeNode node){
if(node!=null){
clear(node.getLeftChild());
clear(node.getRightChild());
node = null;
}
}
//删除整个树
public void clear(){
clear(root);
}
//判断二叉树是否为空
public boolean isEmpty(){
return root == null;
}
/**
* 求二叉树的高度:
* 首先要一种获取以某个节点为子树的高度的方法,使用递归调用。
* 如果一个节点为空,那么这个节点肯定是一颗空树,高度为0;
* 如果不为空,那么我们要遍历地比较它的左子树高度和右子树高度,
* 高的一个为这个子树的最大高度,然后加上自己本身的高度就是了
* 获取二叉树的高度,只需要调用第一种方法,即传入根节点
*/
//获取二叉树的高度
public int heigh(){
return heigh(root);
}
//获取以某节点为子树的高度
public int heigh(BinaryTreeNode node){
if(node==null){
return 0; //递归结束,空子树高度为0
}else{
//递归获取左子树高度
int l = heigh(node.getLeftChild());
//递归获取右子树高度
int r = heigh(node.getRightChild());
//高度应该算更高的一边,(+1是因为要算上自身这一层)
return l>r? (l+1):(r+1);
}
}
/**
* 获取二叉树的节点数
*/
public int size(){
return size(root);
}
/**
* 求二叉树的节点数:
* 求节点数时,我们看看获取某个节点为子树的节点数的实现。
* 首先节点为空,则个数肯定为0;
* 如果不为空,那就算上这个节点之后继续递归所有左右子树的子节点数,
* 全部相加就是以所给节点为根的子树的节点数
* 如果求二叉树的节点数,则输入根节点即可
*/
public int size(BinaryTreeNode node){
if(node==null){
return 0; //如果节点为空,则返回节点数为0
}else{
//计算本节点 所以要+1
//递归获取左子树节点数和右子树节点数,最终相加
return 1+size(node.getLeftChild())+size(node.getRightChild());
}
}
//node节点在subTree子树中的父节点
public BinaryTreeNode getParent(BinaryTreeNode subTree,BinaryTreeNode node){
if(subTree==null){
return null; //如果是空子树,则没有父节点
}
if(subTree.getLeftChild()==node || subTree.getRightChild() == node){
return subTree; //如果子树的根节点的左右孩子之一是待查节点,则返回子树的根节点
}
BinaryTreeNode parent = null;
if(getParent(subTree.getLeftChild(),node)!=null){
parent = getParent(subTree.getRightChild(),node);
return parent;
}else{
//递归左右子树
return getParent(subTree.getRightChild(),node);
}
}
//查找node节点在二叉树中的父节点
public BinaryTreeNode getParent(BinaryTreeNode node){
return (root==null||root==node)? null:getParent(root,node);
}
private void checkTreeEmpty(BinaryTreeNode parent) {
if (parent == null) {
throw new IllegalStateException("Can't insert to a null tree! Did you forget set value for root?");
}
}
/**
* 二叉树的遍历
* ① 先序遍历:根-->左子树-->右子树
* ② 中序遍历:左子树-->根-->右子树
* ③ 后序遍历:左子树 -- >右子树-->根
*/
//先序遍历
public void PreOrder(BinaryTreeNode node){
if(node!=null){
System.out.println(node.getData()); //先访问根节点
PreOrder(node.getLeftChild()); //先根遍历左子树
PreOrder(node.getRightChild()); //先根遍历右子树
}
}
//中序
public void InOrder(BinaryTreeNode node){
if(node!=null){
InOrder(node.getLeftChild()); //中根遍历左子树
System.out.println(node); //访问根节点
InOrder(node.getRightChild()); //中根遍历右子树
}
}
//后序
public void PostOrder(BinaryTreeNode node){
if(node!=null){
PostOrder(node.getLeftChild()); //后根遍历左子树
PostOrder(node.getRightChild()); //后根遍历右子树
System.out.println(node); //访问根节点
}
}
}
class BinaryTreeNode{
private int data;//数据
private BinaryTreeNode leftChild;//左儿子
private BinaryTreeNode rightChild;//右儿子
public BinaryTreeNode(int data, BinaryTreeNode leftChild, BinaryTreeNode rightChild) {
this.data = data;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public BinaryTreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(BinaryTreeNode leftChild) {
this.leftChild = leftChild;
}
public BinaryTreeNode getRightChild() {
return rightChild;
}
public void setRightChild(BinaryTreeNode rightChild) {
this.rightChild = rightChild;
}
}