diff --git a/note/README.md b/note/README.md index f2fe03e..04fe585 100644 --- a/note/README.md +++ b/note/README.md @@ -38,6 +38,7 @@ - [🔖 向量与数组](datastructureAlgorithm/book/datastructure/VectorOrArrayList.md) - [🔖 列表](datastructureAlgorithm/book/datastructure/List.md) - [🔖 树](datastructureAlgorithm/book/datastructure/Tree.md) +- [🔖 二叉排序树](datastructureAlgorithm/book/datastructure/BinarySortTree.md) - [🔖 跳表](datastructureAlgorithm/book/datastructure/skipList.md) #### [🔖 2.2 算法篇](datastructureAlgorithm/README.md) ##### [🔖 排序算法](datastructureAlgorithm/README.md) diff --git a/note/datastructureAlgorithm/README.md b/note/datastructureAlgorithm/README.md index fda5286..969c3cf 100644 --- a/note/datastructureAlgorithm/README.md +++ b/note/datastructureAlgorithm/README.md @@ -10,6 +10,7 @@ - [🔖 向量与数组](book/datastructure/VectorOrArrayList.md) - [🔖 列表](book/datastructure/List.md) - [🔖 树](book/datastructure/Tree.md) +- [🔖 二叉排序树](book/datastructure/BinarySortTree.md) - [🔖 跳表](book/datastructure/skipList.md) #### 算法 ##### [排序算法](README.md) diff --git a/note/datastructureAlgorithm/book/datastructure/BinarySortTree.md b/note/datastructureAlgorithm/book/datastructure/BinarySortTree.md new file mode 100644 index 0000000..b52ed16 --- /dev/null +++ b/note/datastructureAlgorithm/book/datastructure/BinarySortTree.md @@ -0,0 +1,329 @@ +## 二叉排序树(BST) + +### 介绍 + +二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。 +特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点 + +比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为: + +![bst](img/bst/bst01.jpg) + +### 二叉排序树创建和遍历 +一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创建成对应的二叉排序树为 : + +```java + 7 + / \ + 3 10 + / \ / \ + 1 5 9 12 +``` +### 二叉排序树的删除 +二叉排序树的删除情况比较复杂,有下面三种情况需要考虑 +1) 删除叶子节点 (比如:2, 5, 9, 12) +2) 删除只有一颗子树的节点 (比如:1) +3) 删除有两颗子树的节点. (比如:7, 3,10 ) +4) 操作的思路分析 + +```java +//对删除结点的各种情况的思路分析: + +第一种情况: +删除叶子节点 (比如:2, 5, 9, 12) +思路 +(1) 需求先去找到要删除的结点 targetNode +(2) 找到 targetNode 的 父结点 parent +(3) 确定 targetNode 是 parent 的左子结点 还是右子结点 +(4) 根据前面的情况来对应删除左子结点 parent.left = null +右子结点 parent.right = null; +第二种情况: 删除只有一颗子树的节点 比如 1 +思路 +(1) 需求先去找到要删除的结点 targetNode +(2) 找到 targetNode 的 父结点 parent +(3) 确定 targetNode 的子结点是左子结点还是右子结点 +(4) targetNode 是 parent 的左子结点还是右子结点 +(5) 如果 targetNode 有左子结点 +5. 1 如果 targetNode 是 parent 的左子结点 +parent.left = targetNode.left; +5.2 如果 targetNode 是 parent 的右子结点 +parent.right = targetNode.left; +(6) 如果 targetNode 有右子结点 +6.1 如果 targetNode 是 parent 的左子结点 +parent.left = targetNode.right; +6.2 如果 targetNode 是 parent 的右子结点 +parent.right = targetNode.right + +情况三 : 删除有两颗子树的节点. (比如:7, 3,10 ) +思路 +(1) 需求先去找到要删除的结点 targetNode +(2) 找到 targetNode 的 父结点 parent +(3) 从 targetNode 的右子树找到最小的结点 +(4) 用一个临时变量,将 最小结点的值保存 temp = 11 +(5) 删除该最小结点 +(6) targetNode.value = temp + +``` +### 代码实现 +```java +package com.javayh.advanced.datastructure.tree.sort; + +/** + *

+ * 二叉排序树 + *

+ * + * @author Dylan + * @version 1.0.0 + * @since 2020-12-31 8:04 PM + */ +public class BinarySortTreeDemo { + public static void main(String[] args) { + int[] arr = {7, 3, 10, 12, 5, 1, 9, 2}; + BinarySortTree binarySortTree = new BinarySortTree(); + //循环的添加结点到二叉排序树 + for (int value : arr) { + binarySortTree.add(new Node(value)); + } + //中序遍历二叉排序树 + System.out.println("中序遍历二叉排序树~"); + binarySortTree.infixOrder(); // 1, 3, 5, 7, 9, 10, 12 + //测试一下删除叶子结点 + binarySortTree.delNode(12); + binarySortTree.delNode(5); + binarySortTree.delNode(10); + binarySortTree.delNode(2); + binarySortTree.delNode(3); + binarySortTree.delNode(9); + System.out.println("del后中序遍历二叉排序树~"); + binarySortTree.infixOrder(); + } +} + +class BinarySortTree { + private Node root; + + //查找要删除的结点 + 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); + } + } + + //编写方法: + //1. 返回的 以 node 为根结点的二叉排序树的最小结点的值 + //2. 删除 node 为根结点的二叉排序树的最小结点 + + /** + * @param node 传入的结点(当做二叉排序树的根结点) + * @return 返回的 以 node 为根结点的二叉排序树的最小结点的值 + */ + public int delRightTreeMin(Node node) { + Node target = node; + //循环的查找左子节点,就会找到最小值 + while (target.left != null) { + target = target.left; + + } + //这时 target 就指向了最小结点 + //删除最小结点 + delNode(target.value); + return target.value; + } + + //删除结点 + public void delNode(int value) { + if (root == null) { + return; + } else { + //1.需求先去找到要删除的结点 targetNode + Node targetNode = search(value); + //如果没有找到要删除的结点 + if (targetNode == null) { + return; + } + //如果我们发现当前这颗二叉排序树只有一个结点 + if (root.left == null && root.right == null) { + root = null; + return; + } + //去找到 targetNode 的父结点 + Node parent = searchParent(value); + //如果要删除的结点是叶子结点 + if (targetNode.left == null && targetNode.right == null) { + //判断 targetNode 是父结点的左子结点,还是右子结点 + if (parent.left != null && parent.left.value == value) { //是左子结点 + parent.left = null; + } else if (parent.right != null && parent.right.value == value) {//是由子结点 + parent.right = null; + } + } else if (targetNode.left != null && targetNode.right != null) { //删除有两颗子树的节点 + int minVal = delRightTreeMin(targetNode.right); + targetNode.value = minVal; + } else { // 删除只有一颗子树的结点 + //如果要删除的结点有左子结点 + if (targetNode.left != null) { + if (parent != null) { + //如果 targetNode 是 parent 的左子结点 + if (parent.left.value == value) { + parent.left = targetNode.left; + } else { // targetNode 是 parent 的右子结点 + parent.right = targetNode.left; + } + } else { + root = targetNode.left; + } + } else { //如果要删除的结点有右子结点 + if (parent != null) { + //如果 targetNode 是 parent 的左子结点 + if (parent.left.value == value) { + parent.left = targetNode.right; + } else { //如果 targetNode 是 parent 的右子结点 + parent.right = targetNode.right; + } + } else { + root = targetNode.right; + } + } + } + } + } + + //添加结点的方法 + public void add(Node node) { + if (root == null) { + root = node;//如果 root 为空则直接让 root 指向 node + } else { + root.add(node); + } + } + + //中序遍历 + public void infixOrder() { + if (root != null) { + root.infixOrder(); + } else { + System.out.println("二叉排序树为空,不能遍历"); + } + } + + +} + +class Node { + int value; + Node left; + Node right; + + public Node(int value) { + this.value = value; + } + + /** + * 查找要删除的结点 + * + * @param value 希望删除的结点的值 + * @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 Node searchParent(int value) { + //如果当前结点就是要删除的结点的父结点,就返回 + if ((this.left != null && this.left.value == value) || + (this.right != null && this.right.value == 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; + } + } + } + + @Override + public String toString() { + return "Node [value=" + value + "]"; + + } + + /** + * 添加结点的方法,递归的形式添加结点,注意需要满足二叉排序树的要求 + * + * @param node + */ + public void add(Node node) { + if (node == null) { + return; + + } + //判断传入的结点的值,和当前子树的根结点的值关系 + if (node.value < this.value) { + //如果当前结点左子结点为 null + 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 infixOrder() { + if (this.left != null) { + this.left.infixOrder(); + } + System.out.println(this); + if (this.right != null) { + this.right.infixOrder(); + } + } + +} +``` + + + + diff --git a/note/datastructureAlgorithm/book/datastructure/img/bst/bst01.jpg b/note/datastructureAlgorithm/book/datastructure/img/bst/bst01.jpg new file mode 100644 index 0000000..8c00721 Binary files /dev/null and b/note/datastructureAlgorithm/book/datastructure/img/bst/bst01.jpg differ diff --git a/note/datastructureAlgorithm/book/datastructure/img/bst/bst02.jpg b/note/datastructureAlgorithm/book/datastructure/img/bst/bst02.jpg new file mode 100644 index 0000000..27d3ef1 Binary files /dev/null and b/note/datastructureAlgorithm/book/datastructure/img/bst/bst02.jpg differ diff --git a/source-code/src/main/java/com/javayh/advanced/datastructure/tree/sort/BinarySortTreeDemo.java b/source-code/src/main/java/com/javayh/advanced/datastructure/tree/sort/BinarySortTreeDemo.java new file mode 100644 index 0000000..414158f --- /dev/null +++ b/source-code/src/main/java/com/javayh/advanced/datastructure/tree/sort/BinarySortTreeDemo.java @@ -0,0 +1,256 @@ +package com.javayh.advanced.datastructure.tree.sort; + +/** + *

+ * 二叉排序树 + *

+ * + * @author Dylan + * @version 1.0.0 + * @since 2020-12-31 8:04 PM + */ +public class BinarySortTreeDemo { + public static void main(String[] args) { + int[] arr = {7, 3, 10, 12, 5, 1, 9, 2}; + BinarySortTree binarySortTree = new BinarySortTree(); + //循环的添加结点到二叉排序树 + for (int value : arr) { + binarySortTree.add(new Node(value)); + } + //中序遍历二叉排序树 + System.out.println("中序遍历二叉排序树~"); + binarySortTree.infixOrder(); // 1, 3, 5, 7, 9, 10, 12 + //测试一下删除叶子结点 + binarySortTree.delNode(12); + binarySortTree.delNode(5); + binarySortTree.delNode(10); + binarySortTree.delNode(2); + binarySortTree.delNode(3); + binarySortTree.delNode(9); + System.out.println("del后中序遍历二叉排序树~"); + binarySortTree.infixOrder(); + } +} + +class BinarySortTree { + private Node root; + + //查找要删除的结点 + 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); + } + } + + //编写方法: + //1. 返回的 以 node 为根结点的二叉排序树的最小结点的值 + //2. 删除 node 为根结点的二叉排序树的最小结点 + + /** + * @param node 传入的结点(当做二叉排序树的根结点) + * @return 返回的 以 node 为根结点的二叉排序树的最小结点的值 + */ + public int delRightTreeMin(Node node) { + Node target = node; + //循环的查找左子节点,就会找到最小值 + while (target.left != null) { + target = target.left; + + } + //这时 target 就指向了最小结点 + //删除最小结点 + delNode(target.value); + return target.value; + } + + //删除结点 + public void delNode(int value) { + if (root == null) { + return; + } else { + //1.需求先去找到要删除的结点 targetNode + Node targetNode = search(value); + //如果没有找到要删除的结点 + if (targetNode == null) { + return; + } + //如果我们发现当前这颗二叉排序树只有一个结点 + if (root.left == null && root.right == null) { + root = null; + return; + } + //去找到 targetNode 的父结点 + Node parent = searchParent(value); + //如果要删除的结点是叶子结点 + if (targetNode.left == null && targetNode.right == null) { + //判断 targetNode 是父结点的左子结点,还是右子结点 + if (parent.left != null && parent.left.value == value) { //是左子结点 + parent.left = null; + } else if (parent.right != null && parent.right.value == value) {//是由子结点 + parent.right = null; + } + } else if (targetNode.left != null && targetNode.right != null) { //删除有两颗子树的节点 + int minVal = delRightTreeMin(targetNode.right); + targetNode.value = minVal; + } else { // 删除只有一颗子树的结点 + //如果要删除的结点有左子结点 + if (targetNode.left != null) { + if (parent != null) { + //如果 targetNode 是 parent 的左子结点 + if (parent.left.value == value) { + parent.left = targetNode.left; + } else { // targetNode 是 parent 的右子结点 + parent.right = targetNode.left; + } + } else { + root = targetNode.left; + } + } else { //如果要删除的结点有右子结点 + if (parent != null) { + //如果 targetNode 是 parent 的左子结点 + if (parent.left.value == value) { + parent.left = targetNode.right; + } else { //如果 targetNode 是 parent 的右子结点 + parent.right = targetNode.right; + } + } else { + root = targetNode.right; + } + } + } + } + } + + //添加结点的方法 + public void add(Node node) { + if (root == null) { + root = node;//如果 root 为空则直接让 root 指向 node + } else { + root.add(node); + } + } + + //中序遍历 + public void infixOrder() { + if (root != null) { + root.infixOrder(); + } else { + System.out.println("二叉排序树为空,不能遍历"); + } + } + + +} + +class Node { + int value; + Node left; + Node right; + + public Node(int value) { + this.value = value; + } + + /** + * 查找要删除的结点 + * + * @param value 希望删除的结点的值 + * @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 Node searchParent(int value) { + //如果当前结点就是要删除的结点的父结点,就返回 + if ((this.left != null && this.left.value == value) || + (this.right != null && this.right.value == 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; + } + } + } + + @Override + public String toString() { + return "Node [value=" + value + "]"; + + } + + /** + * 添加结点的方法,递归的形式添加结点,注意需要满足二叉排序树的要求 + * + * @param node + */ + public void add(Node node) { + if (node == null) { + return; + + } + //判断传入的结点的值,和当前子树的根结点的值关系 + if (node.value < this.value) { + //如果当前结点左子结点为 null + 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 infixOrder() { + if (this.left != null) { + this.left.infixOrder(); + } + System.out.println(this); + if (this.right != null) { + this.right.infixOrder(); + } + } + +} \ No newline at end of file