为什么要使用二叉树
因为二叉树结合了有序数组和链表的特点。在二叉树中查找数据的速度和在有序数组中查找一样快,并且插入数据项和删除数据项的速度也和链表一样快
查找
从根开始进行查找,当前节点的key值与所查key值相等时返回该节点;当前节点的key值小于所查key值时,则去其左子树进行查找;当前节点的key值大于所查key值时,则去其右子树进行查找。
public Node find(int key){
Node current = root;
while(current.iData != key){
if(key < current.iData){
current = current.leftChild;
} else {
current = current.rightChild;
}
if(current == null){
return null;
}
}
return current;
}
插入
二叉树的插入首先需要确定新节点插入的位置,即先找插入位置,再插入新节点
public void insert(int id, double dd){
Node newNode = new Node();
newNode.iData = id;
newNode.fData = dd;
if(root == null){
root = newNode;
} else {
Node current = root;
Node parent;
while(true){
parent = current;
if(id < current.iData){
current = current.leftChild;
if(current == null){
parent.leftChild = newNode;
return;
}
} else {
current = current.rightChild;
if(current == null){
parent.rightChild = newNode;
return;
}
}
}
}
}
遍历
遍历树的意思是根据一种特定的顺序访问树的每一个节点,树的遍历分为以下三种形式:
-
前序遍历
1.访问节点
2.调用自身遍历该节点的左子树
3.调用自身遍历该节点的右子树public void preOrder(Node localRoot){ if(localRoot != null){ System.out.print(localRoot.iData + " "); inOrder(localRoot.leftChild); inOrder(localRoot.rightChild); } }
-
中序遍历
1.调用自身遍历该节点的左子树
2.访问节点
3.调用自身遍历该节点的右子树public void inOrder(Node localRoot){ if(localRoot != null){ inOrder(localRoot.leftChild); System.out.print(localRoot.iData + " "); inOrder(localRoot.rightChild); } }
-
后序遍历
1.调用自身遍历该节点的左子树
2.调用自身遍历该节点的右子树
3.访问节点public void preOrder(Node localRoot){ if(localRoot != null){ inOrder(localRoot.leftChild); inOrder(localRoot.rightChild); System.out.print(localRoot.iData + " "); } }
查找最大值和最小值
查找最小值时,先走到根的左子节点处,然后接着走到那个节点的左子节点,如此类推,知道找到一个没有左子节点的节点,这个节点即为最小值所在的节点。
public Node mininum(){
Node current = root;
Node last = null;
while(current != null){
last = current;
current = current.leftChild;
}
return last;
}
查找最大值时,先走到根的右子节点处,然后接着走到那个节点的右子节点,如此类推,知道找到一个没有右子节点的节点,这个节点即为最大值所在的节点。
public Node maxnum(){
Node current = root;
Node last = null;
while(current != null){
last = current;
current = current.righttChild;
}
return last;
}
删除节点
删除节点是二叉树中最复杂的常用操作,删除节点的方法为先查找到所要删除的节点,找到该节点后有以下三种情况需要考虑:
-
该节点是叶节点(没有子节点)
要删除叶节点,只需要改变该节点的父节点的对应字段的值,由该节点改为null就可以了。要删除的节点仍然存在,但是它已经不是树的一部分了。if(current.leftChild == null && current.rightChild == null){ if(current == root){ root = null; }else if(isLeftChild){ parent.leftChild = null; }else{ parent.rightChild = null; } }
-
该节点有一个子节点
该节点只有两个连接:连向父节点和连向它唯一的子节点。删除这个节点的时候,需要从这个序列中“剪断”这个节点,把它的子节点直接连到它的父节点上。这个过程要求改变父节点适当的引用(左子节点还是右子节点),指向要删除节点的子节点。if(current.rightChild == null){ if(current == root){ root = current.leftChild; }else if(isLeftChild){ parent.leftChild = current.leftChild; }else{ parent.rightChild = current.leftChild; } }else if(current.leftChild == null){ if(current == root){ root =current.rightChild; }else if(isLeftChild){ parent.leftChild = current.rightChild; }else{ parent.rightChild = current.rightChild; } }
-
该节点有两个子节点
如果要删除的节点有两个子节点,就不能只是用它的一个子节点代替它。删除有两个子节点的节点,用它的中序后继来代替该节点。删除的过程分为以下三个步骤:
1. 找后继节点
首先,找到初始节点的右子节点,它的关键字值一定比初始节点大。然后转到初始节点的右子节点的左子节点那里(如果有的话),然后到这个左子节点的左子节点,以此类推,顺着左子节点的路径一直向下找。这个路径上的最后一个左子节点就是初始节点的后继。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; } return successor; }
2. 后继节点是删除节点的右子节点
如果后继节点是current节点的右子节点,只需要把以后继为根的子树移到删除的节点的位置。这个操作需要两个步骤:把current从它父节点的rightChild字段删掉(也有可能是leftChild),把这个 字段指向后继;把current的左子节点移出来,把它插到后继的leftChild字段。
Node successor = getSuccessor(current);
if(current == root){
root = successor;
}else if(isLeftChild){
parent.leftChild = successor;
}else {
parent.rightChild = successor;
}
successor.leftChild = current.leftChild;
**3. 后继节点是删除节点的左子节点 **
如果后继节点是要删除节点右子节点的左后代,执行删除操作需要以下四个步骤:把后继父节点的leftChild字段置为后继的右节点;把后继的rightChild字段置为要删除节点的右子节点;把current从它父节点的rightChild字段移除,把这个字段置为后继;把current的左子节点从current移除,把后继的leftChild字段置为current的左子节点。
if(successor != delNode.rightChild){
successorParent.leftChild = successor.rightChild;
successor.rightChild = delNode.rightChild;
}
if(current == root){
root = successor;
}else if(isLeftChild){
parent.leftChild = successor;
}else {
parent.rightChild = successor;
}
successor.leftChild = current.leftChild;
删除是必要的么
二叉树的删除是一个比较复杂的操作,实际上因为它非常复杂,一些程序言都尝试着躲开它。他们会在Node类中加一个Boolean字段,名称如isDeleted。要删除一个节点时,就把此节点的这个字段置为true。其他操作,如find(),在查找之前先判断这个节点是不是标志为已经删除了。这样删除的节点不会改变树的结构。当然,这样做存储中还保留着这种“已经删除”的节点。