二叉树的插入、删除、修改、先序、中序、后序遍历
由于最近想要阅读下 JDK1.8 中 HashMap 的具体实现,但是由于 HashMap 的实现中用到了红黑树,所以我觉得有必要先复习下红黑树的相关知识,所以写下这篇随笔备忘,有不对的地方请指出~
学习红黑树,我觉得有必要从二叉搜索树开始学起,本篇随笔就主要介绍 Java 实现二叉搜索树的查找、插入、删除、遍历等内容。
二叉搜索树需满足以下四个条件:
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
二叉搜索树举例:
图一
接下来将基于图一介绍二叉搜索树相关操作。
首先,应先有一个节点对象相关的类,命名为 Node。
1 class Node { 2 int key; 3 int value; 4 Node leftChild; 5 Node rightChild; 6 7 public Node(int key, int value) { 8 this.key = key; 9 this.value = value; 10 } 11 12 public void displayNode() { 13 14 } 15 }
Node 类中包含 key 值,用于确定节点在树中相应位置,value 值代表要存储的内容,还含有指向左右孩子节点的两个引用。
接下来看下搜索树相应的类:
1 class Tree { 2 Node root;//保存树的根 3 4 public Node find(int key) {//查找指定节点 5 6 } 7 8 public void insert(int key, int value) {//插入节点 9 10 } 11 12 public boolean delete(int key) {//删除指定节点 13 14 } 15 16 private Node getDirectPostNode(Node delNode) {//得到待删除节点的直接后继节点 17 18 } 19 20 public void preOrder(Node rootNode) {//先序遍历树 21 22 } 23 24 public void inOrder(Node rootNode) {//中序遍历树 25 26 } 27 28 public void postOrder(Node rootNode) {//后序遍历树 29 30 } 31 }
类中表示树的框架,包含查找、插入、遍历、删除相应方法,其中删除节点操作最为复杂,接下来一一介绍。
一、查找某个节点
由于二叉搜索树定义上的特殊性,只需根据输入的 key 值从根开始进行比较,若小于根的 key 值,则与根的左子树比较,大于根的key值与根的右子树比较,以此类推,找到则返回相应节点,否则返回 null。
1 public Node find(int key) { 2 Node currentNode = root; 3 while (currentNode != null && currentNode.key != key) { 4 if (key < currentNode.key) { 5 currentNode = currentNode.leftChild; 6 } else { 7 currentNode = currentNode.rightChild; 8 } 9 } 10 return currentNode; 11 }
二、插入节点
与查找操作相似,由于二叉搜索树的特殊性,待插入的节点也需要从根节点开始进行比较,小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父节点的信息 及 待插入的位置是父节点的左子树还是右子树,才能插入到正确的位置。
1 public void insert(int key, int value) { 2 if (root == null) { 3 root = new Node(key, value); 4 return; 5 } 6 Node currentNode = root; 7 Node parentNode = root; 8 boolean isLeftChild = true; 9 while (currentNode != null) { 10 parentNode = currentNode; 11 if (key < currentNode.key) { 12 currentNode = currentNode.leftChild; 13 isLeftChild = true; 14 } else { 15 currentNode = currentNode.rightChild; 16 isLeftChild = false; 17 } 18 } 19 Node newNode = new Node(key, value); 20 if (isLeftChild) { 21 parentNode.leftChild = newNode; 22 } else { 23 parentNode.rightChild = newNode; 24 } 25 }
三、遍历二叉搜索树
遍历操作与遍历普通二叉树操作完全相同,不赘述。
1 public void preOrder(Node rootNode) { 2 if (rootNode != null) { 3 System.out.println(rootNode.key + " " + rootNode.value); 4 preOrder(rootNode.leftChild); 5 preOrder(rootNode.rightChild); 6 } 7 } 8 9 public void inOrder(Node rootNode) { 10 if (rootNode != null) { 11 inOrder(rootNode.leftChild); 12 System.out.println(rootNode.key + " " + rootNode.value); 13 inOrder(rootNode.rightChild); 14 } 15 } 16 17 public void postOrder(Node rootNode) { 18 if (rootNode != null) { 19 postOrder(rootNode.leftChild); 20 postOrder(rootNode.rightChild); 21 System.out.println(rootNode.key + " " + rootNode.value); 22 } 23 }
四、删除指定节点。
在二叉搜索树中删除节点操作较复杂,可分为以下三种情况。
1、待删除的节点为叶子节点,可直接删除。
public boolean delete(int key) { Node currentNode = root;//用来保存待删除节点 Node parentNode = root;//用来保存待删除节点的父亲节点 boolean isLeftChild = true;//用来确定待删除节点是父亲节点的左孩子还是右孩子 while (currentNode != null && currentNode.key != key) { parentNode = currentNode; if (key < currentNode.key) { currentNode = currentNode.leftChild; isLeftChild = true; } else { currentNode = currentNode.rightChild; isLeftChild = false; } } if (currentNode == null) { return false; } if (currentNode.leftChild == null && currentNode.rightChild == null) {//要删除的节点为叶子节点 if (currentNode == root) root = null; else if (isLeftChild) parentNode.leftChild = null; else parentNode.rightChild = null; } ...... }
2、待删除节点只有一个孩子节点
例如删除图一中的 key 值为 11 的节点,只需将 key 值为 13 的节点的左孩子指向 key 值为 12的节点即可达到删除 key 值为 11 的节点的目的。
由以上分析可得代码如下(接上述 delete 方法省略号后):
1 else if (currentNode.rightChild == null) {//要删除的节点只有左孩子 2 if (currentNode == root) 3 root = currentNode.leftChild; 4 else if (isLeftChild) 5 parentNode.leftChild = currentNode.leftChild; 6 else 7 parentNode.rightChild = currentNode.leftChild; 8 } else if (currentNode.leftChild == null) {//要删除的节点只有右孩子 9 if (currentNode == root) 10 root = currentNode.rightChild; 11 else if (isLeftChild) 12 parentNode.leftChild = currentNode.rightChild; 13 else 14 parentNode.rightChild = currentNode.rightChild; 15 }
......
3、待删除节点既有左孩子,又有右孩子。
例如删除图一中 key 值为 10 的节点,这时就需要用 key 值为 10 的节点的中序后继节点(节点 11)来代替 key 值为 10 的节点,并删除 key 值为 10 的节点的中序后继节点,由中序遍历相关规则可知, key 值为 10 的节点的直接中序后继节点一定是其右子树中 key 值最小的节点,所以此中序后继节点一定不含子节点或者只含有一个右孩子,删除此中序后继节点就属于上述 1,2 所述情况。图一中 key 值为 10 的节点的直接中序后继节点 为 11,节点 11 含有一个右孩子 12。
所以删除 图一中 key 值为 10 的节点分为以下几步:
a、找到 key 值为 10 的节点的直接中序后继节点(即其右子树中值最小的节点 11),并删除此直接中序后继节点。
1 private Node getDirectPostNode(Node delNode) {//方法作用为得到待删除节点的直接后继节点 2 3 Node parentNode = delNode;//用来保存待删除节点的直接后继节点的父亲节点 4 Node direcrPostNode = delNode;//用来保存待删除节点的直接后继节点 5 Node currentNode = delNode.rightChild; 6 while (currentNode != null) { 7 parentNode = direcrPostNode; 8 direcrPostNode = currentNode; 9 currentNode = currentNode.leftChild; 10 } 11 if (direcrPostNode != delNode.rightChild) {//从树中删除此直接后继节点 12 parentNode.leftChild = direcrPostNode.rightChild; 13 direcrPostNode.rightChild = null; 14 } 15 return direcrPostNode;//返回此直接后继节点 16 17 }
b、将此后继节点的 key、value 值赋给待删除节点的 key,value值。(接情况二中省略号代码之后)
1 else { //要删除的节点既有左孩子又有右孩子 2 3 //思路:用待删除节点右子树中的key值最小节点的值来替代要删除的节点的值,然后删除右子树中key值最小的节点 4 //右子树key最小的节点一定不含左子树,所以删除这个key最小的节点一定是属于叶子节点或者只有右子树的节点 5 Node directPostNode = getDirectPostNode(currentNode); 6 currentNode.key = directPostNode.key; 7 currentNode.value = directPostNode.value; 8 9 }
package ZhongjieMoShi;
public class Tree{Node root;
//找到指定节点
public Node Find(int key){
if(root==null){
return null;
}
Node currentNode=root;
while(currentNode!=null&¤tNode.key!=key){
if(currentNode.key>key){
currentNode=currentNode.leftChild;
}else{
currentNode=currentNode.rightChild;
}
}
return currentNode;
}
//得到待删除节点的直接后继节点
public Node getDirectNode(Node delNode){
Node currenrNode=delNode;
Node parentNode=delNode;
Node directNode=delNode;
while(currenrNode!=null){
parentNode=directNode;
directNode=currenrNode;
currenrNode=currenrNode.leftChild;
}
if(directNode!=delNode.rightChild){
parentNode.leftChild=directNode.rightChild;
directNode.rightChild=null;
}
return directNode;
}
//删除指定节点
public boolean delete(int key){
if(root==null){
return false;
}
Node currentNode=root;
Node parentNode=root;
boolean isLeftNode=true;
//如果删除的节点左右子树都为空
while(currentNode!=null&¤tNode.key!=key){
parentNode=currentNode;
if(key<currentNode.key){
currentNode=currentNode.leftChild;
isLeftNode=true;
}else{
currentNode=currentNode.rightChild;
isLeftNode=false;
}
}
if(currentNode==null){
return false;
}
if(currentNode.leftChild==null&¤tNode.rightChild==null){
if(currentNode==root){
root=null;
}else if(isLeftNode){
parentNode.leftChild=null;
}else {
parentNode.rightChild=null;
}
}else if(currentNode.leftChild==null){
if(currentNode==root){
root=currentNode.rightChild;
}else if(isLeftNode){
parentNode.leftChild=currentNode.rightChild;
}else {
parentNode.rightChild=currentNode.rightChild;
}
}else if(currentNode.rightChild==null){
if(root==null){
root=currentNode.leftChild;
}else if(isLeftNode){
parentNode.leftChild=currentNode.leftChild;
}else{
parentNode.rightChild=currentNode.leftChild;
}
}else{//这里是左右都不是空的情况,要找到删除的节点的直接后继节点的最小值
Node directNode=getDirectNode(currentNode);
currentNode.key=directNode.key;
// currentNode.value=directNode.value;
}
return true;
}
//插入指定节点
public void insert(int key){
if(root==null){
root=new Node(key);
return;
}
Node currentNode=root;
Node parentNode=root;
boolean isLeftNode=true;
while(currentNode!=null){
parentNode=currentNode;
if(currentNode.key>key){
currentNode=currentNode.leftChild;
isLeftNode=true;
}else{
currentNode=currentNode.rightChild;
isLeftNode=false;
}
}
Node newNode=new Node(key);
if(isLeftNode){
parentNode.leftChild=newNode;
}else{
parentNode.rightChild=newNode;
}
}
//先序遍历 中左右
public void preOrder(Node rootNode){
if(rootNode!=null){
System.out.println("key:"+rootNode.key);
preOrder(rootNode.leftChild);
preOrder(rootNode.rightChild);
}
}
//中序遍历 左中右
public void inOrder(Node rootNode){
if(rootNode!=null){
inOrder(rootNode.leftChild);
System.out.print(" key:"+rootNode.key);
inOrder(rootNode.rightChild);
}
}
//后序遍历 左右中
public void postOrder(Node rootNode){
if(rootNode!=null){
postOrder(rootNode.leftChild);
postOrder(rootNode.rightChild);
System.out.print (" key:"+rootNode.key);
}
}
private void destroy(Node tree){
if(tree==null){
return;
}
if(tree.leftChild!=null){
destroy(tree.leftChild);
}
if(tree.rightChild!=null){
destroy(tree.rightChild);
}
tree=null;
}
public void destroy(){
destroy(root);
}
public static void main(String[] args) {
Tree tree=new Tree();
tree.insert(6);
tree.insert(3);
tree.insert(16);
tree.insert(10);
tree.insert(9);
tree.insert(13);
tree.insert(11);
tree.insert(12);
tree.inOrder(tree.root);
}
}