目录
二叉排序树的引入
问题引入:给一个数列{7,3,10,12,5,1,9},要求能够高效的完成对数据的查询与添加
问题解决:
方案一:使用数组存储
数组未排序:直接添加到数组的尾部,速度快;但按照value值查找速度慢。
数组排序:可使用二分查找,查找速度快;但为保证数组有序,在添加新的数据时,找到插入位置后,后面的数据都需要移动,效率低。
方案二:使用链表进行存储
添加数据比数组存储快,但不论链表是否有序,查找速度都慢。
方案三:使用二叉排序树
二叉排序树基本概念
二叉排序树(BST):对于二叉排序树的任何一个非叶子节点,都要求左子节点的值小于当前节点的值,右子节点的值大于当前节点的值。
二叉排序树的创建
思想:要按照二叉排序树的规则进行节点的添加插入操作。
二叉排序树的插入操作
思想:从根节点开始往下找自己要插入的位置。
新节点与当前节点进行比较:
如果小于当前节点,则在当前结点的左子树中寻找:
如果当前结点的左子树为空,则直接将新节点作为当前结点的左子结点;
如果当前结点的左子树不为空,则递归向左子树添加;
如果大于当前节点,则在当前结点的右子树中寻找:
如果当前结点的右子树为空,则直接将新节点作为当前结点的右子结点;
如果当前结点的右子树不为空,则递归向右子树添加;
代码实现:
//二叉排序树的一些操作方法
//添加插入操作
public void add(Node node) {
if(root == null) {
root = node;
}else{
root.add(node);
}
}
//添加节点操作
//递归的形式进行添加节点,注意按照二叉排序树的要求
public void add(Node node) {
if(node == null) {
return;
}
//判断传入的节点的与当前子树的根结点的值之间的关系
//如果小于当前结点的左子结点,则添加在当前结点的左边;
//否则添加到当前结点的右边。
if(node.value < this.value) {
//如果当前结点的左子结点为空,则直接将该节点作为当前结点的左子结点;否则,将递归的向左子树添加
if(this.left == null) {
this.left = node;
}else{
this.left.add(node);
}
}else{
if(this.right == null) {
this.right = node;
}else{
this.right.add(node);
}
}
}
二叉排序树的遍历操作
思想:基本的树的遍历
代码实现:
//遍历操作
//前序遍历
public void preOrder() {
if(this.root == null) {
System.out.println("当前树为空,无法完成遍历");
}else{
root.preOrder();
}
}
//中序遍历
public void infixOrder() {
if(this.root == null) {
System.out.println("当前树为空,无法完成遍历");
}else{
root.infixOrder();
}
}
//后序遍历
public void postOrder() {
if(this.root == null) {
System.out.println("当前树为空,无法完成遍历");
}else{
root.postOrder();
}
}
//遍历操作
//前序遍历
public void preOrder() {
System.out.println(this);
if(this.left != null) {
this.left.preOrder();
}
if(this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void infixOrder() {
if(this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null) {
this.right.infixOrder();
}
}
//后序遍历
public void postOrder() {
if(this.left != null) {
this.left.postOrder();
}
if(this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
二叉排序树的查找操作
思想:先和根节点进行比较,如果等于根节点的值,则返回根节点;如果小于根节点,则到左子树中递归查找;如果大于根节点,则在右子树中递归查找;
代码实现:
//查找操作
public Node search(int value) {
if(value == this.value) {
return this;
}else if(value < this.value) {
if(this.left == null) {
return null;
}
return this.left.search(value);
}else{
if(this.right == null) {
return null;
}
return this.right.search(value);
}
}
二叉排序树的删除操作
三种情况:
1. 要删除的节点是叶子节点
思想:如果要删除的节点是叶子节点,则直接删除该节点,即让其父结点的子节点置空即可。
步骤:① 先去找到要删除的节点targetNode
② 再找到targetNode的父结点parent
③ 确定targetNode是parent的左子结点还是右子结点
④ 删除targetNode
2. 要删除的节点只有一个子节点
思想:如果要删除的节点是只有一个子节点,则替换要删除的节点为其子节点。
步骤:① 先去找到要删除的节点targetNode
② 再找到targetNode的父结点parent
③ 确定targetNode的子节点是targetNode的左子结点还是右子结点
③ 再确定targetNode是parent的左子结点还是右子结点
④ 删除targetNode:
如果targetNode是parent的左子结点:
如果targetNode的子节点是targetNode的左子结点,则:
parent.left = targetNode.left;
如果targetNode的子节点是targetNode的右子结点,则:
parent.left = targetNode.right;
如果targetNode是parent的右子结点:
如果targetNode的子节点是targetNode的左子结点,则:
parent.right = targetNode.left;
如果targetNode的子节点是targetNode的右子结点,则:
parent.right = targetNode.right;
3. 要删除的节点有两个子节点
思想:如果要删除的节点有两个子节点,则先要找到替换删除结点的替换结点(右子树中最小的结点),然后替换删除结点,最后删除替换结点。
步骤:① 先去找到要删除的节点targetNode;
② 再找到targetNode的父结点parent;
③ 从targetNode的右子树中找到最小的结点,用临时变量保存最小结点的值value;
④ 令targetNode.value = temp;
⑤ 删除最小结点.
代码实现:
/**
* 删除节点:3种情况
*/
//查找要删除的节点
public Node search(int value) {
if(root == null) {
return null;
}else{
return root.search(value);
}
}
//查找删除节点的父结点
public Node searchParent(int value) {
if(root == null) {
return null;
}else {
return root.searchParent(value);
}
}
//查找要删除的结点的右子树的最小结点并且删除该右子树中的最小结点
public int delRightTreeMin(Node node) { //传入的是要删除的节点的右子结点
while(node.left != null) {
node = node.left;
}
int temp = node.value;
deleteNode(node.value);
return temp; //返回的是以node为根结点的二叉排序树的最小结点的值
}
//删除节点
public void deleteNode(int value) {
int temp = 0; //用于第三种情况,存储最小的结点的值
if(root == null) {
return;
}else{
//查找要删除的节点
Node targetNode = search(value);
//如果当前这个二叉树中不存在要删除的节点,则直接结束退出
if(targetNode == null) {
System.out.println("二叉排序树中不存在该节点");
return;
}
//如果当前这个二叉树只有一个根节点,并且这个节点就是要删除的节点,那么就直接将根节点置空
if(root.left == null && root.right == null) {
root = null;
return;
}
//去找targetNode的父结点
Node parent = searchParent(value);
//如果删除的节点是一个叶子结点
if(targetNode.left == null && targetNode.right == null) {
if(parent.left != null && parent.left.value == targetNode.value) { //是parent节点的左子结点
parent.left = null;
}
if(parent.right != null && parent.right.value == targetNode.value) { //是parent结点的右子结点
parent.right = null;
}
}
//如果删除的结点只有一个子结点
if((targetNode.left != null && targetNode.right == null) ||(targetNode.left == null &&targetNode.right != null)){
//如果删除的结点是父结点的左子结点
if(parent.left.value == targetNode.value) {
//如果删除结点只有左子结点
if(targetNode.left != null) {
parent.left = targetNode.left;
}else{//如果删除结点只有右子结点
parent.left = targetNode.right;
}
} else {//如果删除的结点是父结点的右子结点
//如果删除的节点只有左子结点
if(targetNode.left != null) {
parent.right = targetNode.left;
}else{
parent.right = targetNode.right;
}
}
} else {//如果删除的结点有两个子结点
if (targetNode.left != null && targetNode.right != null) {
//从要删除的右子树中找到最小的结点,并将最小的结点的值用临时变量存储起来,将最小结点从树中删除
Node rightNode = targetNode.right;
temp = delRightTreeMin(rightNode);
//将要删除的结点的值改为临时变量中保存的最小结点的值
targetNode.value = temp;
}
}
}
}
//删除操作
//查找要删除节点的父结点
public Node searchParent(int value) {
//如果当前节点就是要删除的结点的父结点,就返回
if((this.left != null && value == this.left.value) || (this.right != null && value == this.right.value)) {
return this;
}else {
//如果查找的值小于当前节点的值,并且当前结点的左子结点不为空
if (value < this.value && this.left != null) {
return this.left.searchParent(value);
}else if(value >= this.value && this.right != null) {
return this.right.searchParent(value);
}else{
return null; //没有找到父结点
}
}
}
二叉排序树的代码实现
import org.junit.Test;
import java.util.ArrayList;
/**
* 排序二叉树(二叉排序树) :
*
*/
public class BinarySortTreeDemo {
@Test
public void test() {
int[] arr = {7,3,10,12,5,1,9};
ArrayList<Node> nodes = new ArrayList<>();
//添加测试:
for(int value : arr) {
nodes.add(new Node(value));
}
BinarySortTree binarySortTree = new BinarySortTree();
for(Node node : nodes) {
binarySortTree.add(node);
}
binarySortTree.infixOrder();//1,3,5,7,9,10,12
//删除测试:
// // 删除的节点是叶子结点:
// System.out.println("删除叶子结点1后:");
// binarySortTree.deleteNode(1);
// binarySortTree.infixOrder(); //3,5,7,9,10,12
// //删除的节点只有一个子结点:
// Node newnode = new Node(4);
// binarySortTree.add(newnode);
// System.out.println("删除的节点只有一个子结点,例如删除节点5前:");
// binarySortTree.infixOrder();//1,3,4,5,7,9,10,12
// binarySortTree.deleteNode(5);
// System.out.println("删除的节点只有一个子结点,例如删除节点5后:");
// binarySortTree.infixOrder(); //1,3,4,7,9,10,12
//删除的结点有两个子结点:
Node newnode = new Node(4);
binarySortTree.add(newnode);
System.out.println("删除的节点只有一个子结点,例如删除节点3前:");
binarySortTree.infixOrder();//1,3,4,5,7,9,10,12
System.out.println("删除的节点有两个子结点,例如删除节点3后:");
binarySortTree.deleteNode(3);
binarySortTree.infixOrder();//1,4,5,7,9,10,12
}
}
//创建一个排序二叉树类
class BinarySortTree {
private Node root; //树的根结点
public BinarySortTree() {
}
public BinarySortTree(Node root) {
this.root = root;
}
//二叉排序树的一些操作方法
//添加插入操作
public void add(Node node) {
if(root == null) {
root = node;
}else{
root.add(node);
}
}
/**
* 删除节点:3种情况
*/
//查找要删除的节点
public Node search(int value) {
if(root == null) {
return null;
}else{
return root.search(value);
}
}
//查找删除节点的父结点
public Node searchParent(int value) {
if(root == null) {
return null;
}else {
return root.searchParent(value);
}
}
//查找要删除的结点的右子树的最小结点并且删除该右子树中的最小结点
public int delRightTreeMin(Node node) { //传入的是要删除的节点的右子结点
while(node.left != null) {
node = node.left;
}
int temp = node.value;
deleteNode(node.value);
return temp; //返回的是以node为根结点的二叉排序树的最小结点的值
}
//删除节点
public void deleteNode(int value) {
int temp = 0; //用于第三种情况,存储最小的结点的值
if(root == null) {
return;
}else{
//查找要删除的节点
Node targetNode = search(value);
//如果当前这个二叉树中不存在要删除的节点,则直接结束退出
if(targetNode == null) {
System.out.println("二叉排序树中不存在该节点");
return;
}
//如果当前这个二叉树只有一个根节点,并且这个节点就是要删除的节点,那么就直接将根节点置空
if(root.left == null && root.right == null) {
root = null;
return;
}
//去找targetNode的父结点
Node parent = searchParent(value);
//如果删除的节点是一个叶子结点
if(targetNode.left == null && targetNode.right == null) {
if(parent.left != null && parent.left.value == targetNode.value) { //是parent节点的左子结点
parent.left = null;
}
if(parent.right != null && parent.right.value == targetNode.value) { //是parent结点的右子结点
parent.right = null;
}
}
//如果删除的结点只有一个子结点
if((targetNode.left != null && targetNode.right == null) ||(targetNode.left == null &&targetNode.right != null)){
//如果删除的结点是父结点的左子结点
if(parent.left.value == targetNode.value) {
//如果删除结点只有左子结点
if(targetNode.left != null) {
parent.left = targetNode.left;
}else{//如果删除结点只有右子结点
parent.left = targetNode.right;
}
} else {//如果删除的结点是父结点的右子结点
//如果删除的节点只有左子结点
if(targetNode.left != null) {
parent.right = targetNode.left;
}else{
parent.right = targetNode.right;
}
}
} else {//如果删除的结点有两个子结点
if (targetNode.left != null && targetNode.right != null) {
//从要删除的右子树中找到最小的结点,并将最小的结点的值用临时变量存储起来,将最小结点从树中删除
Node rightNode = targetNode.right;
temp = delRightTreeMin(rightNode);
//将要删除的结点的值改为临时变量中保存的最小结点的值
targetNode.value = temp;
}
}
}
}
//查
//遍历操作
//前序遍历
public void preOrder() {
if(this.root == null) {
System.out.println("当前树为空,无法完成遍历");
}else{
root.preOrder();
}
}
//中序遍历
public void infixOrder() {
if(this.root == null) {
System.out.println("当前树为空,无法完成遍历");
}else{
root.infixOrder();
}
}
//后序遍历
public void postOrder() {
if(this.root == null) {
System.out.println("当前树为空,无法完成遍历");
}else{
root.postOrder();
}
}
}
//创建节点类
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//排序二叉树各个节点相应的方法
//添加节点操作
//递归的形式进行添加节点,注意按照二叉排序树的要求
public void add(Node node) {
if(node == null) {
return;
}
//判断传入的节点的与当前子树的根结点的值之间的关系
//如果小于当前结点的左子结点,则添加在当前结点的左边;
//否则添加到当前结点的右边。
if(node.value < this.value) {
//如果当前结点的左子结点为空,则直接将该节点作为当前结点的左子结点;否则,将递归的向左子树添加
if(this.left == null) {
this.left = node;
}else{
this.left.add(node);
}
}else{
if(this.right == null) {
this.right = node;
}else{
this.right.add(node);
}
}
}
//删除操作
//查找要删除节点的父结点
public Node searchParent(int value) {
//如果当前节点就是要删除的结点的父结点,就返回
if((this.left != null && value == this.left.value) || (this.right != null && value == this.right.value)) {
return this;
}else {
//如果查找的值小于当前节点的值,并且当前结点的左子结点不为空
if (value < this.value && this.left != null) {
return this.left.searchParent(value);
}else if(value >= this.value && this.right != null) {
return this.right.searchParent(value);
}else{
return null; //没有找到父结点
}
}
}
//查找操作
public Node search(int value) {
if(value == this.value) {
return this;
}else if(value < this.value) {
if(this.left == null) {
return null;
}
return this.left.search(value);
}else{
if(this.right == null) {
return null;
}
return this.right.search(value);
}
}
//遍历操作
//前序遍历
public void preOrder() {
System.out.println(this);
if(this.left != null) {
this.left.preOrder();
}
if(this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void infixOrder() {
if(this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null) {
this.right.infixOrder();
}
}
//后序遍历
public void postOrder() {
if(this.left != null) {
this.left.postOrder();
}
if(this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}