数据结构及算法基础--树(Tree)(二)二叉树(BinaryTree)
说句老实话,二叉树应该是一个很简单的数据结构,但是我在实现的时候碰到了极大的问题。最主要的就是对二叉树各项功能的具体定义不明确,导致实现二叉树的方法的时候出现混乱。这里,我自己定义了二叉树插入、删除的方法,当然这些都是可以改变的,插入的规则也是可以根据需求进行更改的。我们学习二叉树最主要的是了解二叉树的结构。对每个方法的定义根据项目的不同而不同。
首先,我们来对二叉树的一些基本概念进行了解:
1)二叉树定义:
二叉树是有限个节点的集合,这个集合可以是空集,也可以是一个根节点和至多两个子二叉树组成的集合,其中一颗树叫做根的左子树,另一棵叫做根的右子树。
这是一个递归的定义!(相信很多人都看的出来。)
2)特殊的二叉树
这个时候,我们有两个特殊的二叉树:
1、满二叉树(Full Tree):
我们直接用数学的方法进行定义,假设一棵树的高度为k,这个树的如果拥有2^k-1个节点,那么这个树则为满二叉树。
通俗点讲,每个结点(除了叶节点)都有两个子节点。
2、完全二叉树(Complete Tree):
假设一个深度为k的树,所有的叶节点都在k层或者k-1层。并且第k层的所有节点都在最左边。
下面的图示可以很清晰的展现出两者:
3)二叉树节点的实现
在我们实现二叉树节点的时候,根据上篇文章。我们仍然可以通过两种方法实现二叉树:
1、数组实现:
package BinaryTree; public class BinaryTreeArrayNode<item> { private item mData; private int mLeftChild; private int mRightChild; public BinaryTreeArrayNode(item mData,int leftchild,int rightchild){ this.mData=mData; this.mLeftChild=leftchild; this.mRightChild=rightchild; } public item getData() { return mData; } public void setData(item data) { mData = data; } public int getLeftChild() { return mLeftChild; } public void setLeftChild(int leftChild) { mLeftChild = leftChild; } public int getRightChild() { return mRightChild; } public void setRightChild(int rightChild) { mRightChild = rightChild; } }
我们发现左右节点都是通过整数的数组下标来进行标示。我及其不建议这种做法,相信我,你会得到好处的。
2、链表实现:
package BinaryTree; public class BinaryTreeNode<item> { private item mData; private BinaryTreeNode mLeftChild; private BinaryTreeNode mRightChild; public BinaryTreeNode(item data,BinaryTreeNode leftchild,BinaryTreeNode rightchild){ mData=data; mLeftChild=leftchild; mRightChild=rightchild; } public item getData(){ return mData; } public void setData(item data){ mData=data; } public BinaryTreeNode getLeftChild(){ return mLeftChild; } public void setLeftChild(BinaryTreeNode leftchild){ mLeftChild=leftchild; } public BinaryTreeNode getRightChild(){ return mRightChild; } public void setRightChild(BinaryTreeNode rightchild){ mRightChild=rightchild; } }
当我们使用包含同类引用的时候,我们可以轻易的画出内存结构,不要给我说数组也可以,我们用数组的结构去实现一个树类型结构,结构中的转换真的让人抓狂的。
4)二叉树部分方法的实现:
以下方法只是针对讲义,我们可以根据需求不同进行大量的修改和不同的设计。
二叉树构造器和创建:
public class BinaryTree { public BinaryTreeNode mRoot; public BinaryTree(){ } public BinaryTree(BinaryTreeNode root){ mRoot=root; } public BinaryTreeNode getRoot(){ return mRoot; } public void setRoot(BinaryTreeNode root){ mRoot=root; } }
二叉树添加元素:
在实现这个的时候,我们要注意,二叉树是一个能够非常灵活运用递归的数据结构,前文对与二叉树的定义都是一个递归定义。
所以我们在这里可以使用递归的方法,查找制定元素,并在制定元素的左右添加元素:
public void insertAsLeftChild(BinaryTreeNode target,BinaryTreeNode child){ insertAsLeftChild(mRoot,target,child); } public void insertAsRightChild(BinaryTreeNode target,BinaryTreeNode child){ insertAsRightChild(mRoot,target,child); } public BinaryTreeNode insertAsLeftChild(BinaryTreeNode subTree,BinaryTreeNode target,BinaryTreeNode child){ checkTreeEmpty(); if(subTree.equals(null))return null; if(subTree.equals(target)){ subTree.setLeftChild(child); return target; } BinaryTreeNode a; if((a=insertAsLeftChild(subTree.getLeftChild(),target,child)).equals(null)!=false) return a; else return insertAsRightChild(subTree.getLeftChild(),target,child); } public BinaryTreeNode insertAsRightChild(BinaryTreeNode subTree,BinaryTreeNode target,BinaryTreeNode child){ checkTreeEmpty(); if(subTree.equals(null))return null; if(subTree.equals(target)){ subTree.setRightChild(child); return target; } BinaryTreeNode a; if((a=insertAsLeftChild(subTree.getLeftChild(),target,child)).equals(null)!=false) return a; else return insertAsRightChild(subTree.getLeftChild(),target,child); } public void checkTreeEmpty(){ if(mRoot==null) throw new IllegalStateException("Can't insert to a null tree! Did you forget set value for root?"); }
删除节点及清空:
虽然java提供了GC来帮助我们处理无用的内存,但是我们还是使用递归将节点下所有子节点设置NULL来避免内存浪费:
public void deleteNode(BinaryTreeNode node){ checkTreeEmpty(); if(node==null)return; deleteNode(node.getLeftChild()); deleteNode(node.getRightChild()); node=null; } public void clear(){ if(mRoot!=null){ deleteNode(mRoot); } }
获取高度与节点个数:
public int getTreeHeight(){ return getHeight(mRoot); } public int getHeight(BinaryTreeNode node){ if(node==null)return 0; int leftChildHeight=getHeight(node.getLeftChild()); int rightChildHeight=getHeight(node.getRightChild()); return Math.max(leftChildHeight, rightChildHeight)+1; } public int getSize(){ return getChildSize(mRoot); } public int getChildSize(BinaryTreeNode node){ if(node==null)return 0; int leftChildSize=getChildSize(node.getLeftChild()); int rightChildSize=getChildSize(node.getRightChild()); return leftChildSize+rightChildSize+1; }
获取某个节点的父节点:
public BinaryTreeNode getParent(BinaryTreeNode node){ if(mRoot.equals(null))return null; else return getParent(mRoot,node); } public BinaryTreeNode getParent(BinaryTreeNode subTree,BinaryTreeNode node){ if(subTree.equals(null))return null; if(subTree.getLeftChild().equals(node)||subTree.getRightChild().equals(node))return subTree; BinaryTreeNode parent; if((parent=getParent(subTree.getLeftChild(),node)).equals(null)==false)return parent; else return getParent(subTree.getRightChild(),node); }
接下来我们要实现二叉树非常重要的东西,遍历:
二叉树中有三种遍历方法,先序遍历(first order iterate),中序遍历(medium order iterate),后序遍历(last order iterate):
其实三种遍历的区别可以很明显的通过代码来进行区别,也很容易理解:
先序遍历:
public void operate(BinaryTreeNode node){ if(node.equals(null))return; System.out.println(node.getData()); } //iterateFirstOrder public void iterateFirstOrder(BinaryTreeNode node){ if(node.equals(null))return; operate(node); iterateFirstOrder(node.getLeftChild()); iterateFirstOrder(node.getRightChild()); }
中序遍历:
public void iterateMediumOrder(BinaryTreeNode node){ if(node.equals(null))return; iterateMediumOrder(node.getLeftChild()); operate(node); iterateMediumOrder(node.getRightChild()); }
后续遍历:
public void iterateLastOrder(BinaryTreeNode node){ if(node.equals(null))return; iterateLastOrder(node.getLeftChild()); iterateLastOrder(node.getRightChild()); operate(node); }
通过上面各项方法的实现,我们可以很清楚的认识到递归的运用在二叉树的使用中起到了非常重要的作用。我们只要熟练掌握了递归的用法,我们便可以很好的实现二叉树了。