文章目录
- 一、二叉排序树定义
- 二、二叉树的表现形式(Java)
- 三、创建二叉树
- 四、遍历二叉排序树
- 1、前序遍历
- 2、中序遍历
- 3、后序遍历左、右、根
- 五、查找某个结点
- 六、删除结点
- 七、总结二叉排序树
一、二叉排序树定义
特点:一颗空树,或者具有以下性质的二叉树
- 如果左子树不为空,则左子树上所有结点的值均小于根结点的值
- 如果右子树不为空,则右子树上所有结点的值均大于根结点的值
- 它的左、右子树也分别为二叉排序树。
- 对二叉排序树进行中序遍历,会得到一个递增序列。
二、二叉树的表现形式(Java)
public class BinarySearchTree {
private class Node {
int data; // 数据域
Node left; // 左子树
Node right; // 右子树
}
private Node root; // 二叉排序树根节点
}
三、创建二叉树
这里通过借助parent,curretn来记录新结点插入的位置。
/**
* 创建二叉排序树
* @param key
*/
public void insert(int key) {
Node p = new Node(); // 要插入的结点
p.data = key;
if(root == null) {
root = p;
} else {
Node parent = new Node();
Node current = root; // 当前结点
while (true) {
parent = current;
if(key > current.data) {
current = current.right;
if (current == null) {
parent.right = p;
return;
}
} else { // 这里不考虑key出现和二叉排序树结点中的值相等的情况
current = current.left;
if (current == null) {
parent.left = p;
return;
}
}
}
}
}
四、遍历二叉排序树
1、前序遍历
根、左、右
/**
* 前序遍历 根、左、有
* @param root
*/
public void preOrder(Node root) {
if (root != null) {
System.out.print(root.data+" | ");
preOrder(root.left);
preOrder(root.right);
}
}
2、中序遍历
左、根、右
/**
* 中序遍历 左、根、右
* @param root
*/
public void inOrder(Node root) {
if (root != null) {
inOrder(root.left);
System.out.print(root.data+" | ");
inOrder(root.right);
}
}
3、后序遍历左、右、根
/**
* 后续遍历 左、右、根
* @param root
*/
public void nextOrder(Node root) {
if (root != null) {
nextOrder(root.left);
nextOrder(root.right);
System.out.print(root.data+" | ");
}
}
五、查找某个结点
/**
* 按照关键值,查找结点。
* @param key
* @return
*/
public Node find(int key) {
Node current = root;
while (current.data != key) {
if (key > current.data) {
current = current.right;
} else {
current = current.left;
}
if (current == null) {
return null;
}
}
return current;
}
六、删除结点
删除结点比较复杂。我们要分多种情况来讨论
- 眼删除的结点没有孩子,即叶子结点
如上图所示,只需要将parent的左孩子设置为null,就可以了。
- 删除结点有一个孩子
- 有一个结点,相对来说,也是比较简单的。只需要将parent结点的左孩子设置成current结点的右孩子即可。parent.left = current.rigth;
- 要删除的结点有两个孩子
这种情况比较负责,在讲这个之前,我们先引入一个有关后继结点的概念,如果一颗二叉树按照中序遍历,那么下一个结点就是该结点的后继结点。
1、后继节点为待删除节点的右子,只需要将curren用successor替换即可,注意处理好current.left和successor.right.
注意:这种情况下,successor一定没有左孩子,一但它有左孩子,那它必然不是current的后继节点。
2、**后继节点为待删除结点的右孩子的左子树,**这种情况稍微复杂点。
首先我们要弄明白的是successor为后继结点。successorParent为后继结点的父结点。要删除current结点。我们应该怎么做呢?
眼删除current结点我们需要重新排列二叉树。
1、删除current结点之后,我们需要重新排列current的左、右、子树。
2、我们知道二叉排序树的性质,在current的右子树中,successor结点为最小值。successor的右子树上的任何一个结点的值都比successor结点的值大。所以我们需要做一个操作
successorParent.left = successor.right;
将successor结点的右子树挂在到successorParent的左子树使其有序。
3、然后我们将有序的successorParent二叉树挂在到successor的右子树
successor.right = successorParent;
4、现在还剩下current的左子树了,将其挂在到successor的左子树
successor.left = current.left;
5、这样我们就构建好了有序的二叉树,最后将其连接到parent结点上
parent.left = successor;
- 这里总结一下,总的来说,就是我们要删除current结点之后,我们还是要让current的左、右子树按照二叉排序树的性质,连接到parent结点上。我们这里为什么要找后继结点呢。其实后继结点是最后会作为左、右子树的根。因为我们是通过中序遍历来找的后继结点。
- 算法步骤:
1、successorParent.left = successor.right;
2、successor.left = current.left;
3、parent.left = successor;
七、总结二叉排序树
package com.gwz.datastructure.bstree;
/**
* 二叉排序树
*/
public class BinarySearchTree {
private class Node {
int data; // 数据域
Node left; // 左子树
Node right; // 右子树
}
private Node root; // 二叉排序树根节点
/**
* 创建二叉排序树
* @param key
*/
public void insert(int key) {
Node p = new Node(); // 要插入的结点
p.data = key;
if(root == null) {
root = p;
} else {
Node parent = new Node();
Node current = root; // 当前结点
while (true) {
parent = current;
if(key > current.data) {
current = current.right;
if (current == null) {
parent.right = p;
return;
}
} else { // 这里不考虑key出现和二叉排序树结点中的值相等的情况
current = current.left;
if (current == null) {
parent.left = p;
return;
}
}
}
}
}
/**
* 前序遍历 根、左、有
* @param root
*/
public void preOrder(Node root) {
if (root != null) {
System.out.print(root.data+" | ");
preOrder(root.left);
preOrder(root.right);
}
}
/**
* 中序遍历 左、根、右
* @param root
*/
public void inOrder(Node root) {
if (root != null) {
inOrder(root.left);
System.out.print(root.data+" | ");
inOrder(root.right);
}
}
/**
* 后续遍历 左、右、根
* @param root
*/
public void nextOrder(Node root) {
if (root != null) {
nextOrder(root.left);
nextOrder(root.right);
System.out.print(root.data+" | ");
}
}
/**
* 按照关键值,查找结点。
* @param key
* @return
*/
public Node find(int key) {
Node current = root;
while (current.data != key) {
if (key > current.data) {
current = current.right;
} else {
current = current.left;
}
if (current == null) {
return null;
}
}
return current;
}
/**
* 打印结点域中的值
* @param node
*/
public void show(Node node) {
if (node != null) {
System.out.println(node.data);
} else {
System.out.println("此结点为空!");
}
}
/**
* 删除结点
* @param key
* @return
*/
public boolean delete(int key) {
Node current = root;
Node parent = new Node();
boolean isRightChild = true;
// 查找要删除的结点是否存在
while (current.data != key) {
parent = current;
if (key > current.data) {
current = current.right;
isRightChild = true;
} else {
current = current.left;
isRightChild = false;
}
if (current == null) {
return false;
}
}
// 找到的current就是要删除的结点。parent是删除结点的符结点,只有根结点。
if (current.right == null && current.left == null) {
if (current == root) {
root = null; //删除根,变成空树
} else if (isRightChild){
parent.right = null;
} else {
parent.left = null;
}
return true;
}
// 要删除的结点,有一个子结点
// 当前current是查找到的结点,不一定是根结点。
else if (current.left == null) {
if (current == root) {
current = current.right;
} else if (isRightChild) {
parent.right = current.right;
} else {
parent.left = current.left;
}
return true;
} else if (current.right == null) {
if (current == root) {
current = current.left;
} else if (isRightChild) {
parent.right = current.left;
} else {
parent.left = current.left;
}
return true;
}
// 要删除的结点有两个子结点
else {
// 找到要删除结点的后继结点
Node successor = getSuccessor(current);
if (current == root) {
root = successor;
} else if (isRightChild) {
parent.right = successor;
} else {
parent.left = successor;
}
successor.left = current.left;
return true;
}
}
/**
* 查找要删除结点的后继结点
* 后继结点:按照中序遍历,下一个结点就是后继结点。
* @param delNode
* @return
*/
private Node getSuccessor(Node delNode) {
Node successorParent = delNode;
Node successor = delNode;
Node current = delNode.right; //左、根、右、右为后继。
// 寻找后继结点
while (current != null) {
successorParent = successor;
successor = current;
// 右子树的遍历方式、左、根、右、
// 所以左、结点为后继
current = current.left;
}
// 如果后继结点为要删除结点的右子树
// 需要调整一下删除结点的右子树
if (successor != delNode.right) {
successorParent.left = successor.right;
successor.right = delNode.right;
}
return successor; // 转换好的有序的二叉树。
}
public void traverse(int traverseType)
{ // 选择以何种方式遍历
switch (traverseType)
{
case 1:
System.out.print("preOrder traversal ");
preOrder(root);
System.out.println();
break;
case 2:
System.out.print("inOrder traversal ");
inOrder(root);
System.out.println();
break;
case 3:
System.out.print("postOrder traversal ");
nextOrder(root);
System.out.println();
break;
}
}
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
tree.insert(39);
tree.insert(24);
tree.insert(64);
tree.insert(23);
tree.insert(30);
tree.insert(53);
tree.insert(60);
tree.traverse(1);
tree.traverse(2);
tree.traverse(3);
tree.show(tree.find(23));
tree.show(tree.find(60));
tree.show(tree.find(64));
tree.delete(23);
tree.delete(60);
tree.delete(64);
tree.show(tree.find(23));
tree.show(tree.find(60));
tree.show(tree.find(64));
}
}
动态图片来源: https://visualgo.net/en/bst