二叉查找树
二叉查找树(Binary Search Tree,BST),又叫做二叉排序树、二叉搜索树
性质:
- 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 它的左右子树也分别为二叉排序树。
插入操作:
-
如果该树是空树,将该结点作为根结点插入
-
如果该树不是空树,按照查找逻辑(大的放左边,小的放右边)确定插入位置,插入新结点,
插入结点的位置一定是叶子结点。
public void insert(int id, T data) { Node<T> newNode = new Node<T>(); newNode.setIndex(id); newNode.setData(data); if (root == null) { root = newNode; } else { //从根节点开始查找 Node<T> current = root; //声明父节点的引用 Node<T> parent; while (true) { //父节点的引用指向当前节点 parent = current; //如果角标小于当前节点,插入到左节点 if (id < current.getIndex()) { current = current.getLeftChild(); //节点为空才进行赋值,否则继续查找 if (current == null) { parent.setLeftChild(newNode); return; } } else { //否则插入到右节点 current = current.getRightChild(); if (current == null) { parent.setRightChild(newNode); return; } } } } }
查找操作:
- 如果树为空树,返回空,查找失败
- 如果查找值x比根结点值大,往右子树找。
- 如果查找值x比根结点值小,往左子树找
- 如果查找值x等于根节点值,返回该结点
由于左子树结点值 < 根结点值 < 右子树结点值,对二叉排序树进行中序遍历,可以得到一个递增的有序序列。
针对数据 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 对于的二叉排序树如图所示。
删除操作:
删除节点是二叉搜索树常用的一般操作中最复杂的。但是,删除节点在很多树的应用中又非常重要,所以要详细研究并总结特点。
找到节点后,这个要删除的节点要分三种情况讨论:
- 该结点是叶节点(没有子结点)。
- 该结点有一个子结点。
- 该结点有两个子结点。
情况一:删除没有子节点的节点(叶子结点)
要删除叶节点,只需要改变该节点的父节点的对应子字段的值,由指向该节点、改为null就可以了。
情况二: 删除只有一个子节点的节点
删除的这个节点只有两个连接:连向父节点的和连向它惟一的子节点的。(所以分四种情况,leftChildren和rightChildren为左子树和右子树)
情况三: 删除有两个子节点的节点
如果要删除的节点有两个子节点,就不能只是用它的一个子节点代替它。
假设要删除节点25,并且用它的根是35的右子树取代它。那么35的左子节点应该是谁呢?是要删除节点25的左子节点15,还是35原来的左子节点30?然而在这两种情况中30都会被放得不对,但又不能删掉它。
对每一个节点来说,比该节点的关键字值次高的节点是它的中序后继,可以简称为该节点的后继。在上图中,节点30就是节点25的后继。
public Node<T> find(int key) {
if (root == null) {
return null;
}
Node<T> current = root;
//如果不是当前节点while (current.getIndex() != key) {
while (current.getIndex() != key) {
if (key < current.getIndex()) {
current = current.getLeftChild();
} else {
current = current.getRightChild();
}
//如果左右节点均为null,查找失败
if (current == null) {
return null;
}
}
return current;
}
Node类
package tree2;
public class Node<T> {
/**
* 角标
*/
private Integer index;
/**
* 数据
*/
private T data;
/**
* 左节点
*/
private Node leftChild;
/**
* 右节点
*/
private Node rightChild;
public Node() {
}
/**
* 构造函数
*
* @param index 角标
* @param data 数据
*/
public Node(Integer index, T data) {
this.index = index;
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node getLeftChild() {
return leftChild;
}
public void setLeftChild(Node leftChild) {
this.leftChild = leftChild;
}
public Node getRightChild() {
return rightChild;
}
public void setRightChild(Node rightChild) {
this.rightChild = rightChild;
}
@Override
public String toString() {
return "Node{" +
"index=" + index +
", data=" + data +
'}';
}
}
BinarySearchTree类
package tree2;
public class BinarySortTree<T> {
private Node<T> root;
public Node<T> find(int key) {
if (root == null) {
return null;
}
Node<T> current = root;
//如果不是当前节点while (current.getIndex() != key) {
while (current.getIndex() != key) {
if (key < current.getIndex()) {
current = current.getLeftChild();
} else {
current = current.getRightChild();
}
//如果左右节点均为null,查找失败
if (current == null) {
return null;
}
}
return current;
}
public void inOrder(Node<T> localRoot) {
if (localRoot != null) {
inOrder(localRoot.getLeftChild());
System.out.print(localRoot.getIndex() + " ");
inOrder(localRoot.getRightChild());
}
}
public void insert(int index, T data) {
Node<T> newNode = new Node<T>();
newNode.setIndex(index);
newNode.setData(data);
if (root == null) {
root = newNode;
} else {
//从根节点开始查找
Node<T> current = root;
//声明父节点的引用
Node<T> parent;
while (true) {
//父节点的引用指向当前节点
parent = current;
//如果角标小于当前节点,插入到左节点
if (index < current.getIndex()) {
current = current.getLeftChild();
//节点为空才进行赋值,否则继续查找
if (current == null) {
parent.setLeftChild(newNode);
return;
}
} else {
//否则插入到右节点
current = current.getRightChild();
if (current == null) {
parent.setRightChild(newNode);
return;
}
}
}
}
}
public Node<T> getSuccessor(Node<T> delNode) {
Node<T> successorParent = delNode;
Node<T> successor = delNode;
//go to rightChild
Node<T> current = delNode.getRightChild();
while (current != null) {
//一直往下找左节点
successorParent = successor;
successor = current;
current = current.getLeftChild();
}
//跳出循环,此时successor为最后的一个左节点,也就是被删除节点的后继节点
// System.out.println(current);
return successor;
}
public Node<T> delete(int key) {
if (root == null) {
return null;
}
Node<T> parent = root;
//要删除的结点
Node<T> current = root;
boolean isLeftChild = true;
//删除操作第一步,查找要删除的节点
while (current.getIndex() != key) {
parent = current;
if (key < current.getIndex()) {
isLeftChild = true;
current = current.getLeftChild();
} else {
isLeftChild = false;
current = current.getRightChild();
}
//如果左右节点均为null,没有找到要删除的元素
if (current == null) {
return null;
}
}
//跳出循环,找到要删除的元素:current
// System.out.println(current);
// System.out.println(isLeftChild);
// 情况一:删除的结点是叶子结点,没有子结点的时候
if (current.getLeftChild() == null && current.getRightChild() == null) {
if (current == root) {
//如果当前节点是根节点,将树清空
root = null;
return current;
} else if (isLeftChild) {
//如果当前节点是其父节点的做节点,将父节点的左节点清空
parent.setLeftChild(null);
} else {
parent.setRightChild(null);
}
//情况二:删除的结点有左子树和右子树的时候
} else if (current.getLeftChild() != null && current.getRightChild() != null) {
//查找后继节点
Node<T> successor = getSuccessor(current);
Node<T> successorParent = getSuccessorParent(current);
// System.out.println(successor);
if (current.getRightChild() != successor) {
current.setData(successor.getData());
current.setIndex(successor.getIndex());
successorParent.setLeftChild(successor.getRightChild());
} else {
//情况3.1 如果如果删除节点有两个子节点且后继节点是删除节点的右子节点
if (isLeftChild) {
parent.setLeftChild(successor);
successor.setLeftChild(current.getLeftChild());
}else {
parent.setRightChild(successor);
successor.setLeftChild(current.getLeftChild());
}
// parent.setRightChild(current.getRightChild());
//
}
//
//情况三:删除的结点只有一个子节点时
}else {
//3.1如果删除节点只有一个子节点且没有右节点
if (current.getRightChild() == null) {
if (current == root) {
root = current.getLeftChild();
} else if (isLeftChild) {
parent.setLeftChild(current.getLeftChild());
} else {
parent.setRightChild(current.getLeftChild());
}
} else if (current.getLeftChild() == null) {
//3.2 如果删除节点只有一个子节点且没有左节点
if (current == root) {
root = current.getRightChild();
} else if (isLeftChild) {
parent.setLeftChild(current.getRightChild());
} else {
parent.setRightChild(current.getRightChild());
}
}
}
return current;
}
public Node getRootNode(){
return root;
}
public Node<T> getSuccessorParent(Node<T> delNode) {
Node<T> successorParent = delNode;
Node<T> successor = delNode;
//go to rightChild
Node<T> current = delNode.getRightChild();
while (current != null) {
//一直往下找左节点
successorParent = successor;
successor = current;
current = current.getLeftChild();
}
//跳出循环,此时successor为最后的一个左节点,也就是被删除节点的后继节点
//如果successor是要删除节点右子节点的左后代
if (delNode.getRightChild() != successor) {
delNode.setData(successor.getData());
delNode.setIndex(successor.getIndex());
successorParent.setLeftChild(successor.getRightChild());
把后继节点的父节点的leftChild字段置为successor的右子节点
// successorParent.setLeftChild(successor.getRightChild());
把successor的rightChild字段置为要删除节点的右子节点。
// successor.setRightChild(delNode.getRightChild());
}
return successorParent;
}
}
测试类
package tree2;
import org.junit.Before;
import org.junit.Test;
public class TestBinaryTree2<T> {
BinarySortTree<Integer> tree = new BinarySortTree<Integer>();
@Before
public void init() {
int[] arr = {62, 88, 58, 47, 35, 73, 51, 99, 37, 93};
for (int i = 0; i < arr.length; i++) {
tree.insert(arr[i],i);
}
}
@Test
public void testMidOrder() {
tree.inOrder(tree.getRootNode());
// System.out.println(tree.getRootNode());
}
@Test
public void testFind() {
Node<Integer> node = tree.find(47);
if (node == null) {
System.out.println("-1");
}else {
System.out.println(node.getData());
}
}
@Test
public void testDeleteNode() {
tree.inOrder(tree.getRootNode());
System.out.println();
tree.delete(58);
tree.inOrder(tree.getRootNode());
}
@Test
public void testSuccessor() {
tree.inOrder(tree.getRootNode());
Node<Integer> node = tree.find(62);
Node successor = tree.getSuccessor(node);
System.out.println(successor);
}
}