一、二叉树定义[以下数据来自百度百科]
二叉树是每个结点最多有两个子树的树结构。
二叉树有5种形态,分别是
-
空二叉树【没有结点】
-
只有根节点的二叉树
-
只有左子树
-
只有右子树
-
完全二叉树
按其类型可以分为 -
完全二叉树【每层的结点树达到了其最大值】
-
满二叉树【除叶结点外其他结点都有左右结点,且叶结点都处于最底层】
-
平衡二叉树【AVL树,左右子树的高度差不超过1】
相关名词: -
树的结点(node):包含一个数据元素及若干指向子树的分支;
-
孩子结点(child node):结点的子树的根称为该结点的孩子;
-
双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;
-
兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;
-
祖先结点: 从根到该结点的所经分支上的所有结点
-
子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
-
结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;
-
树的深度:树中最大的结点层
-
结点的度:结点子树的个数【度只有0,1,2三种情况】
-
树的度: 树中最大的结点度。
-
叶子结点:也叫终端结点,是度为 0 的结点;
-
分枝结点:度不为0的结点;
-
有序树:子树有序的树,如:家族树;
-
无序树:不考虑子树的顺序
二、二叉树性质
- 在二叉树的第i(i>=1)层最多有2^(i - 1)个结点。
- 深度为k(k>=0)的二叉树最少有k个结点,最多有2^k-1个结点。
- 对于任一棵非空二叉树,若其叶结点数为n0,度为2的非叶结点数为n2,则n0 = n2 +1。
- 具有n个结点的完全二叉树的深度为int_UP(log(2,n+1))。
- 如果将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号1,2,3,......,n,然后按此结点编号将树中各结点顺序的存放于一个一维数组,并简称编号为i的结点为结点i( i>=1 && i<=n),则有以下关系:
(1)若 i= 1,则结点i为根,无父结点;若 i> 1,则结点 i 的父结点为结点int_DOWN(i / 2);
(2)若 2*i <= n,则结点 i 的左子女为结点 2*i;
(3)若2*i<=n,则结点i的右子女为结点2*i+1;
(4)若结点编号i为奇数,且i!=1,它处于右兄弟位置,则它的左兄弟为结点i-1;
(5)若结点编号i为偶数,且i!=n,它处于左兄弟位置,则它的右兄弟为结点i+1;
(6)结点i所在的层次为 int_DOWN(log(2,i))+1。
三、二叉树的常见算法
3.1 先序遍历
先访问本结点,再访问左结点,右结点。一个典型特征是根节点在第一位
public List<Object> preOrder(TreeNode node,List<Object> list){
if(node!=null){
list.add(node);
preOrder(node.getLeftTr(),list);
preOrder(node.getRightTr(),list);
}
return list;
}
3.2 中序遍历
先访问左结点,再访问本结点,右结点。特征就是根节点居中【完全二叉树】
如果是有序二叉树,那么使用该排序方式可以大小有序的打印该树。
public List<Object> midOrder(TreeNode node,List<Object> list){
if(node!=null){
midOrder(node.getLeftTr(),list);
list.add(node);
midOrder(node.getRightTr(),list);
}
return list;
}
3.3 后续遍历
先访问左结点,再访问右结点,本结点。根节点在最后一位
public List<Object> lastOrder(TreeNode node,List<Object> list){
if(node!=null){
lastOrder(node.getLeftTr(),list);
lastOrder(node.getRightTr(),list);
list.add(node);
}
return list;
}
3.4 层次遍历
从根节点(第一层)开始遍历
public List<Object> rankOrder(TreeNode node,List<Object> list){
Queue<TreeNode> q= Queues.newArrayDeque();
q.offer(node);
while(!q.isEmpty()){
TreeNode n=q.poll();
if(n!=null){
list.add(n);
if(n.hasLeftTr()){
q.offer(n.getLeftTr());
}
if(n.hasRightTr()){
q.offer(n.getRightTr());
}
}
}
return list;
}
3.5 有序二叉树增加节点
从根节点开始遍历,若lnode<newNode<node ,则插入左子树,反之则右子树。遍历到子树的页结点,最后赋值。
public void addNode(TreeNode newNode){
if(newNode.compareTo(this)<0){//左边
if(this.leftTr==null){
this.leftTr=newNode;
}else{
this.leftTr.addNode(newNode);
}
}else{
if(this.rightTr==null){
this.rightTr=newNode;
}else{
this.rightTr.addNode(newNode);
}
}
}
3.6 有序二叉树删除结点
删除结点较与增加结点会更复杂一点,涉及到删除结点的子树的位置调换。
删除结点分为以下四种情况
1、根节点,特殊处理
2、删除结点为叶子节点:直接将删除结点的父节点的左 或者右结点置为null
3、删除结点只有左结点:需将删除结点的左子树,移到删除结点的父结点的右结点上
4、删除结点有右结点:需要判断右结点的子树的最小结点,并将最小结点的右子树移到删除结点的父节点的右子树
public void delete(TreeNode node){
TreeNode x=find(node);
TreeNode parent=findParent(x);
if(!x.hasLeftTr()|| !x.hasRightTr()){//删除节点为叶子节点
if(parent==null){//根节点
this.setData(null);
}else{
if(parent.hasLeftTr()&& parent.getLeftTr().equals(x)){
parent.setLeftTr(null);//如果删除节点在父节点的左侧
}
if(parent.hasRightTr() && parent.getRightTr().equals(x)){
parent.setRightTr(null);//如果删除节点在父节点的右侧
}
}
}
if(x.hasLeftTr() &&!x.hasRightTr()){//删除的节点有左叶节点,没有右叶节点
if(parent==null){
this.setData(x.getLeftTr().getData());
this.setLeftTr(x.getLeftTr().getLeftTr());
}else{
if(parent.hasLeftTr() && parent.getLeftTr().equals(x)){
parent.setLeftTr(x.getLeftTr());
}if(parent.hasRightTr() && parent.getRightTr().equals(x)){
parent.setRightTr(x.getLeftTr());
}
}
}
if(x.hasRightTr()){//有右叶节点
TreeNode minNode=x.getRightTr().finMinNode();
parent=this.findParent(minNode);
if(minNode.equals(x.getRightTr())){
parent.setRightTr(minNode.getRightTr());
}else{
parent.setLeftTr(minNode.getRightTr());
}
x.setData(minNode.getData());
}
}
3.7 查找结点
从根结点开始遍历,才用3.1-3.4的方法均可以
也可以使用迭代遍历
public TreeNode find(TreeNode node){
if(this==null){
return null;
}
else if(this.compareTo(node)<0){
return this.getRightTr().find(node);
}else if(this.compareTo(node)>0){
return this.getLeftTr().find(node);
}
return this;
}
3.8 查找父结点
public TreeNode findParent(TreeNode node1){
if(this!=null && (node1.equals(this.getLeftTr()) || node1.equals(this.getRightTr()))){
return this;
}
TreeNode parent=null;
if(this.hasLeftTr()){
parent=this.getLeftTr().findParent(node1);
if(parent!=null){
return parent;
}
}
if(this.hasRightTr()){
parent= this.getRightTr().findParent(node1);
if(parent!=null){
return parent;
}
}
return parent;
}
3.9 最小结点 or 最大结点
public TreeNode finMinNode(){
while (this.getLeftTr()!=null){
return this.getLeftTr().finMinNode();
}
return this;
}
public TreeNode finMaxNode(){
while(this.getRightTr()!=null){
return this.getRightTr().finMaxNode();
}
return this;
}
四、平衡树
平衡树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
4.1 Avl平衡树
参考资料:AVL树-自平衡二叉查找树(Java实现)
参考资料:手把手教,手写AVL树
4.2实现
4.2.1 左旋
public AvlTreeNode rotateLeft(AvlTreeNode node){
AvlTreeNode parent=node.getParent();
AvlTreeNode anoNode=node.getRightTr();
if(parent!=null){
if (node.equals(parent.getLeftTr())){
parent.setLeftTr(anoNode);
}
if(node.equals(parent.getRightTr())){
parent.setRightTr(anoNode);
}
}
anoNode.setLeftTr(node);
node.setRightTr(anoNode.getRightTr());
anoNode.setParent(node);
anoNode.setParent(parent);
return anoNode;
}
4.2.2 右旋
public AvlTreeNode rotateRight(AvlTreeNode node){
AvlTreeNode parent=node.getParent();
AvlTreeNode anoNode=node.getLeftTr();
if(parent!=null){
if (node.equals(parent.getLeftTr())){
parent.setLeftTr(anoNode);
}
if(node.equals(parent.getRightTr())){
parent.setRightTr(anoNode);
}
}
anoNode.setRightTr(node);
node.setLeftTr(anoNode.getRightTr());
//父节点
node.setParent(anoNode);
anoNode.setParent(parent);
return anoNode;
}
4.2.3 先左旋再右旋
public AvlTreeNode rotateLeftRight(AvlTreeNode node){
AvlTreeNode parent=node.getParent();
if(parent!=null){
if (node.equals(parent.getLeftTr())){
parent.setLeftTr(node.getLeftTr());
}
if(node.equals(parent.getRightTr())){
parent.setRightTr(node.getRightTr());
}
}
node.setLeftTr(rotateLeft(node.getLeftTr()));
return rotateRight(node);
}
4.2.4 先右旋再左旋
public AvlTreeNode rotateRightLeft(AvlTreeNode node){
AvlTreeNode parent=node.getParent();
if(parent!=null){
if (node.equals(parent.getLeftTr())){
parent.setLeftTr(node.getLeftTr());
}
if(node.equals(parent.getRightTr())){
parent.setRightTr(node.getRightTr());
}
}
node.setRightTr( rotateRight(node.getRightTr()));
return rotateLeft(node);
}