标签:
一、为什么使用二叉树?
为什么要用到树?因为它通常结合了另外两种数据结构的优点:一种是有序数组,另一种是链表。在树中查找数据项的速度和在有序数组中查找一样快,并且插入数据项和删除数据项的速度也和链表一样。
二、二叉树的定义
如果数种每个节点最多只能有两个子节点,这样的树称为“二叉树”。二叉树的每个节点的两个子节点称为“左节点”和“右节点”。二叉树中的节点不是必须有两个子节点;它可以只有一个左子节点,或者只有一个右子节点,或者干脆没有子节点(这种情况下它就是叶节点)。
三、二叉树的实现
实现二叉树需要一个节点对象的内部类。这些对象包含数据,数据代表要存储的内容,而且还有指向节点的两个子节点的引用。还需要一个表示根的Node变量,以及查询、插入、遍历和删除的方法。下面是这个类的骨架。
package binaryTree; import org.junit.Test; public class BinaryTree { private Node root; /** * 插入数据 * @param iDate 插入的数据 */ public void insert(int iDate){} /** * 查找结点 * @param key 查找关键字 * @return */ public Node find(int key){} /** * 求最大值 * @param localRoot * @return */ public Node findMax(Node localRoot){ } /** * 求最小值 * @param localRoot * @return */ public Node findMin(Node localRoot){ } /** * 中序遍历 * @param localRoot */ public void inOrder(Node localRoot){ } /** * 前序遍历 * @param localRoot */ public void preOrder(Node localRoot){ } /** * 后序遍历 * @param localRoot */ public void lastOrder(Node localRoot){ } /** * 删除结点 * @param key * @return */ public boolean delete(int key){} }
1)查找节点:查找比较容易实现。从根节点开始比较,如果查找值比当前值小,则跳到左子节点,如果大,跳到右子节点,如果相等直接返回。
/** * 查找结点 * @param key 查找关键字 * @return */ public Node find(int key){ Node current = root; //如果链表为空,就返回null if(current==null){ return null; } while(current.iDate!=key){ if(current.iDate>key) current = current.leftChild; else current = current.rightChild; if(current==null) return null; } return current; }
2)插入节点:插入节点必须先确定新节点插入的位置。这段代码与查找节点的代码大致相同,区别是原来简单的查找节点时,遇到null(不存在)值后,表明要找的节点不存在,于是就立即返回。现在要插入一个节点,就在返回前插入(要是必要的话,要先创建)节点。
/** * 插入数据 * @param iDate 插入的数据 */ public void insert(int iDate){ Node newNode = new Node(); newNode.iDate=iDate; if(root==null){ root = newNode; }else{ Node current = root; Node parent; //当前结点的父结点 while(true){ parent = current; if(current.iDate>iDate){ current = current.leftChild; if(current==null){ parent.leftChild = newNode; return; } }else{ current = current.rightChild; if(current==null){ parent.rightChild=newNode; return; } } } } }
3)遍历树
3.1 中序遍历的步骤:
a.调用自身来遍历节点的左子树 b.访问该节点 c.调用自身来遍历节点的右子树
/** * 中序遍历 * @param localRoot */ public void inOrder(Node localRoot){ if(localRoot != null){ inOrder(localRoot.leftChild); System.out.print(localRoot.iDate+" "); inOrder(localRoot.rightChild); } }
中序遍历的调用
3.2 前序遍历的步骤:
a.访问该节点 b.调用自身来遍历节点的左子树 c.调用自身来遍历节点的右子树
/** * 前序遍历 * @param localRoot */ public void preOrder(Node localRoot){ if(localRoot != null){ System.out.print(localRoot.iDate+" "); preOrder(localRoot.leftChild); preOrder(localRoot.rightChild); } }
3.3 后序遍历的步骤:
a.调用自身来遍历节点的左子树 b.调用自身来遍历节点的右子树 c.访问该节点
/** * 后序遍历 * @param localRoot */ public void lastOrder(Node localRoot){ if(localRoot != null){ preOrder(localRoot.leftChild); preOrder(localRoot.rightChild); System.out.print(localRoot.iDate+" "); } }
4)查找最大和最小值
要找最小值,先走到根的左子节点处,然后接着走到那个子节点的左子节点,如果类推,直接找到一个没有左子节点的节点,这个节点就是最小值得节点。最大值得原理是一样的,只是查找的是最有节点。
/** * 求最大值 * @param localRoot * @return */ public Node findMax(Node localRoot){ Node current = localRoot; Node max=null; while(current != null){ max=current; current = current.rightChild; } return max; } /** * 求最小值 * @param localRoot * @return */ public Node findMin(Node localRoot){ Node current = localRoot; Node min=null; while(current != null){ min=current; current = current.leftChild; } return min; }
5)删除节点:删除节点很麻烦也是方法中最难实现的。删除方法的第一部分和find()和insert()方法很像。它查找的要删除的节点。和insert()一样,需要保存要删除的父节点和要删除的节点是左节点还是右节点,这样就可以修改它的子字段的值了,并且判断修改的是左还是右。找到之后还要分三种情况考虑:
情况1:删除没有子节点的节点:直接删除就行
情况2:删除只有一个子节点的节点:将这个子节点放到删除的节点的位置就行
情况3:删除的有两个子节点的节点:这个有个窍门,用该节点的中序后继代替该节点的位置。在代替的时候又分为两种情况,一种是中序后继节点是删除节点的右子节点,一种是中序后继节点是删除节点的左后代。
中序后继:比该节点的关键字值次高的节点是它的中序后继,也就是用中序遍历它后面的那个值。
1.如何得到中序后继: 首先,程序找到初始节点的右子节点,他的关键字值一定比初始节点大,然后转到初始节点的右子节点的左子节点那里(如果有的话),然后到这个左子节点的左子节点,以此类推,顺着左子节点的路径一直找,这个路径上的最后一个左子节点就是初始节点的的后继。
/** * 得到中序后继节点 * @param delNode * @return */ private Node getSuccessor(Node delNode){ Node successorParent = delNode; //后继父节点 Node successor =delNode; //后继节点 Node current = delNode.rightChild;
//得到中序后继 while(current!=null){ successorParent = successor; successor = current; current=current.leftChild; } //如果中继节点是删除节点的右节点的左子孙节点(这个是为了方便后面操作) if(successor!=delNode.rightChild){
//把后继父节点的leftChild字段置为后继的右子节点 successorParent.leftChild=successor.rightChild;
//把后继节点的rightChild字段置为要删除节点的右子节点 successor.rightChild=delNode.rightChild; } return successor; }
2.后继节点是删除节点的右子节点 操作步骤
a.把要删除的节点从它父节点的rightChild字段删除(当然也可能是leftChild字段),把这个字段指向后继
b.把要删除的节点的左子节点移出来,把它插到后继的leftChild字段。
3.中序后继节点是删除节点的左后代 操作步骤
a.把后继父节点的leftChild字段置为后继的右子节点。
b.把后继节点的rightChild字段置为要删除节点的右子节点。
c.把要删除节点从它父节点的rightChild字段移除,把这个字段置为后继节点。
d.把要删除节点的左子节点从删除节点上移除,后继节点的leftChild字段置为删除节点的左子节点。
a和b由getSuccessor()方法完成。
/** * 删除结点 * @param key * @return */ public boolean delete(int key){ Node current = root; //当前节点 Node parent = root; //当前节点父节点 boolean isLeftChild = true; //要删除的节点是不是左节点 //如果链表为空 if(current==null) return false; //查找关键字所在的位置 while(current.iDate!=key){ parent = current; if(current.iDate>key){ current = current.leftChild; isLeftChild = true; } else{ current = current.rightChild; isLeftChild = false; } //到达节点尾部仍没找到, if(current==null){ return false; } } //a.如果没有子节点 if(current.leftChild==null && current.rightChild==null){ if(current == root){ root = null; }else if(isLeftChild){ parent.leftChild=null; }else{ parent.rightChild=null; } } //b.如果有一个子节点 else //如果是右节点 if(current.leftChild==null){ if(current == root) root = current.rightChild; else if(isLeftChild){ parent.leftChild=current.rightChild; }else{ parent.rightChild = current.rightChild; } } //如果是左节点 else if(current.rightChild==null){ if(current == root) root = current.leftChild; else if(isLeftChild){ parent.leftChild = current.leftChild; }else{ parent.leftChild = current.rightChild; } } //c.如果删除的节点有两个子节点 else{ Node successor = getSuccessor(current); if(current == root){ root =successor; //把要删除的节点从它父节点的rightChild(当然也可能是leftChild字段),把这个字段指向后继 }else if(isLeftChild){ parent.leftChild=successor; }else{ parent.rightChild=successor; } //把要删除的节点的左子节点移出来,把它插到后继的leftChild字段 successor.leftChild=current.leftChild; } return true; }
标签:
原文地址:http://www.cnblogs.com/duoluomengxing/p/4690675.html