二叉搜索树实现(基于链表)
除叶节点外,其余节点中左节点小于父节点,右节点大于父节点,其通常用于查找(二分查找)
代码实现
- add()方法:当根节点为空时,则新元素即为根节点,否则循环判断新元素放左边还是右边
- getParent():从根节点开始寻找指定节点的父节点
- MaxNode()和Max():获取最大的节点及其对应的值
- MinNode()和Min():获取最小的节点及其对应的值
- innerSearch()和search():递归找到对应的值返回boolean
- visitNode()、midOrderTraversal()和innerMidOrderTraversal():中序遍历,用于打印验证树结果是否正确
- remove():删除,在下面测试代码中介绍
getPre():获取指定节点的前驱节点(如寻找10的前驱节点pre),分以下情况
- 情况1:若该节点有左子树,其前驱节点是其左子树中值最大的节点(添加11-10-8-7-9,pre=9)
- 情况2:若该节点无左子树,那么判断该节点和其父节点的关系
- 情况2-1:若该节点是无左子树的根节点,无前驱节点(添加10-11-12,pre=null)
- 情况2-2:若该节点是其父节点的右节点,其前驱结点即为其父节点(添加9-10-11,pre=9)
- 情况2-3:若该节点是其父节点的左节点,其前驱节点为第一个小于该节点的父节点(添加8-9-11-10,pre=9),无则为null(添加13-10-11,pre=null)
getSuc():获取指定节点的后继节点(如寻找10的后继节点suc),分以下情况
- 情况1:若该节点有右子树,其后继节点是其右子树中值最小的节点(添加9-10-12-11-13,suc=11)
- 情况2:若该节点无右子树,那么判断该节点和其父节点的关系
- 情况2-1:若该节点是无右子树的根节点,无后继节点(添加10-9-8,suc=null)
- 情况2-2:若该节点是其父节点的左节点,其后继结点即为其父节点(添加11-10-9,suc=11)
- 情况2-3:若该节点是其父节点的右节点,其后继结点为第一个大于该节点的父节点(添加11-9-8-10,suc=11),无则为null(添加8-10-9,suc=null)
class BinarySearchTree {
private class TreeNode {
int value;
TreeNode leftChild;
TreeNode rightChild;
TreeNode(int value) {
this.value = value;
}
}
private TreeNode root;
public void add(int value) {
if (root == null) {
root = new TreeNode(value);
} else {
TreeNode parent;
TreeNode current = root;
while (true) {
parent = current;
if (value < current.value) {
current = current.leftChild;
if (current == null) {
parent.leftChild = new TreeNode(value);
return;
}
} else {
current = current.rightChild;
if (current == null) {
parent.rightChild = new TreeNode(value);
return;
}
}
}
}
}
private TreeNode getPre(TreeNode node) {
if (node.leftChild != null) {
return MaxNode(node.leftChild);
} else {
TreeNode parent = getParent(node);
if (parent == null) {
return null;
}
if (node == parent.rightChild) {
return parent;
} else { //node == parent.leftChild
while (parent != null) {
if (parent.value < node.value) {
break;
}
parent = getParent(node);
}
return parent;
}
}
}
private TreeNode getSuc(TreeNode node) {
if (node.rightChild != null) {
return MinNode(node.rightChild);
} else {
TreeNode parent = getParent(node);
if (parent == null) {
return null;
}
if (node == parent.leftChild) {
return parent;
} else { //node == parent.rightChild
while (parent != null) {
if (parent.value > node.value) {
break;
}
parent = getParent(node);
}
return parent;
}
}
}
private TreeNode getParent(TreeNode node) {
if (root == node) {
return null;
}
if (node == null) {
return null;
}
TreeNode current = root;
TreeNode parent = null;
while (current != null) {
if (node.value < current.value) {
parent = current;
current = current.leftChild;
} else if (node.value > current.value) {
parent = current;
current = current.rightChild;
} else {
return parent;
}
}
return parent;
}
public void Max() {
TreeNode maxNode = MaxNode(root);
if (maxNode != null) {
visitNode(maxNode);
} else {
System.out.print("null");
}
}
private TreeNode MaxNode(TreeNode node) {
if (node == null) {
return null;
}
TreeNode current = node;
while (current.rightChild != null) {
current = current.rightChild;
}
return current;
}
public void Min() {
TreeNode minNode = MinNode(root);
if (minNode != null) {
visitNode(minNode);
} else {
System.out.print("null");
}
}
private TreeNode MinNode(TreeNode node) {
if (node == null) {
return null;
}
TreeNode current = node;
while (current.leftChild != null) {
current = current.leftChild;
}
return current;
}
public boolean search(int value) {
if (innerSearch(root, value) != null) {
return true;
} else {
return false;
}
}
private TreeNode innerSearch(TreeNode root, int value) {
if (root == null) {
return null;
}
if (value < root.value) {
return innerSearch(root.leftChild, value);
} else if (value > root.value) {
return innerSearch(root.rightChild, value);
} else {
return root;
}
}
private void visitNode(TreeNode node) {
System.out.print(node.value + " ");
}
public void midOrderTraversal() {
innerMidOrderTraversal(root);
}
private void innerMidOrderTraversal(TreeNode root) {
if (root == null) {
return;
}
innerMidOrderTraversal(root.leftChild);
visitNode(root);
innerMidOrderTraversal(root.rightChild);
}
public void remove(int value) {
TreeNode delNode = innerSearch(root, value);
TreeNode parent = getParent(delNode);
if (delNode.leftChild == null && delNode.rightChild == null) { //删除的节点无左右节点,则断开父节点和它的连接(若是根节点则置空)
if (parent == null) { //delNode = root
root = null;
} else {
if (delNode == parent.leftChild) {
parent.leftChild = null;
} else {
parent.rightChild = null;
}
}
} else if (delNode.leftChild == null && delNode.rightChild != null) { //删除的节点有右节点,则让其右节点连上父节点(若是根节点,则右节点成为新根节点)
if (parent == null) { //delNode = root
root = delNode.rightChild;
} else {
if (delNode == parent.leftChild) {
parent.leftChild = delNode.rightChild;
} else {
parent.rightChild = delNode.rightChild;
}
}
} else if (delNode.leftChild != null && delNode.rightChild == null) { //删除的节点有左节点,则让其左节点连上父节点(若是根节点,则左节点成为新根节点)
if (parent == null) { //delNode = root
root = delNode.leftChild;
} else {
if (delNode == parent.leftChild) {
parent.leftChild = delNode.leftChild;
} else {
parent.rightChild = delNode.leftChild;
}
}
} else { //删除的节点左右节点都有
if (parent == null) { //delNode = root,则左右节点任意一个都可成为新根节点
root = delNode.leftChild;
//root = delNode.rightChild;
} else {
System.out.print("删除的节点:");
visitNode(delNode);
System.out.println();
System.out.print("删除节点的后继节点:");
TreeNode successor = getSuc(delNode);
if (successor == null) {
System.out.print("null");
} else {
visitNode(successor);
}
System.out.println();
System.out.print("删除节点的后继节点的父节点:");
TreeNode successorParent = getParent(successor);
if (successorParent == null) {
System.out.print("null");
} else {
visitNode(successorParent);
}
System.out.println();
if (successorParent == delNode) { //若后继节点是删除节点的右节点
parent.rightChild = successor;
successor.leftChild = delNode.leftChild;
delNode.leftChild = null;
} else { //若后继节点是删除节点的右子树的左节点
successorParent.leftChild = successor.rightChild; //断开successor
successor.rightChild = null;
parent.leftChild = successor; //del父节点指向successor,即断开del
successor.rightChild = delNode.rightChild; //successor连接delNode右子树
delNode.rightChild = null; //断开delNode右子树
successor.leftChild = delNode.leftChild; //successor连接delNode左子树
delNode.leftChild = null; //断开delNode左子树
}
}
}
}
}
测试代码1
下面代码测试添加、遍历、最大最小值
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(3);
binarySearchTree.add(5);
binarySearchTree.add(2);
binarySearchTree.add(1);
binarySearchTree.add(4);
System.out.print("add后中序遍历测试:");
binarySearchTree.midOrderTraversal();
System.out.println();
System.out.print("最大值:");
binarySearchTree.Max();
System.out.println();
System.out.print("最小值:");
binarySearchTree.Min();
System.out.println();
System.out.print("查找value=2:");
if (binarySearchTree.search(2)) {
System.out.print("found");
} else {
System.out.print("not found");
}
System.out.println();
打印如下
add后中序遍历测试:1 2 3 4 5
最大值:5
最小值:1
查找value=2:found
测试代码2
下面单独测试删除
- 情况1:删除的节点无左右节点(添加1-2,删除2),则断开父节点和它的连接(若是根节点则置空)
- 打印:remove前中序遍历为:1 2 ————> remove后中序遍历为:1
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(1);
binarySearchTree.add(2);
System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.print("————> remove后中序遍历为:");
binarySearchTree.remove(2);
binarySearchTree.midOrderTraversal();
System.out.println();
- 情况2:删除的节点有右节点无左节点(添加1-2-3,删除2),则让其右节点连上其父节点(若是根节点,则右节点成为新根节点)
- 打印:remove前中序遍历为:1 2 3 ————> remove后中序遍历为:1 3
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(1);
binarySearchTree.add(2);
binarySearchTree.add(3);
System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.print("————> remove后中序遍历为:");
binarySearchTree.remove(2);
binarySearchTree.midOrderTraversal();
System.out.println();
- 情况3:删除的节点有左节点无右节点(添加3-2-1,删除2),则让其左节点连上其父节点(若是根节点,则右节点成为新根节点)
- 打印:remove前中序遍历为:1 2 3 ————> remove后中序遍历为:1 3
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(3);
binarySearchTree.add(2);
binarySearchTree.add(1);
System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.print("————> remove后中序遍历为:");
binarySearchTree.remove(2);
binarySearchTree.midOrderTraversal();
System.out.println();
- 情况4-1:删除的节点既有左节点又有右节点,其后继节点为待删除节点的右节点(添加2-4-3-6-9-7,删除4)
- 操作为:待删除节点的父节点的右节点指向其后继节点,后继节点的左节点指向待删除节点的左节点,断开待删除节点的左节点
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(2);
binarySearchTree.add(4);
binarySearchTree.add(3);
binarySearchTree.add(6);
binarySearchTree.add(9);
binarySearchTree.add(7);
System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();
binarySearchTree.remove(4);
System.out.print("remove后中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();
打印如下
remove前中序遍历为:2 3 4 6 7 9
删除的节点:4
删除节点的后继节点:6
删除节点的后继节点的父节点:4
remove后中序遍历为:2 3 6 7 9
- 情况4-2:删除的节点既有左节点又有右节点,其后继节点为待删除节点的右子树的左节点(添加10-4-3-8-9-6-7,删除4)
- 操作为:①后继节点的父节点的左节点指向后继节点的右节点,断开后继节点的右节点。②待删除节点的父节点的左节点指向后继节点,后继节点的右节点指向待删除节点的右节点,断开待删除节点的右节点。③后继节点的左节点指向待删除节点的左节点,断开待删除节点的左节点
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(10);
binarySearchTree.add(4);
binarySearchTree.add(3);
binarySearchTree.add(8);
binarySearchTree.add(9);
binarySearchTree.add(6);
binarySearchTree.add(7);
//binarySearchTree.add(5);
System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();
binarySearchTree.remove(4);
System.out.print("remove后中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();
打印如下
remove前中序遍历为:3 4 6 7 8 9 10
删除的节点:4
删除节点的后继节点:6
删除节点的后继节点的父节点:8
remove后中序遍历为:3 6 7 8 9 10
时间复杂度和优化
-
最好情况:如果二叉搜索树刚好对半平分,n个节点的二叉搜索树的高度为log2n+1,复杂度为O(logn),同二分查找法
-
最坏情况:如果二叉搜索树完全斜向一边,高度为n,复杂度为O(n),退化为顺序查找法
为了获得较好的查找性能,就要构造一棵平衡的二叉搜索树