文章收藏的好句子:坚持和放纵是永远对立的矛盾,好习惯的坚持就像春天随手丢下的种子,经历漫长的仲夏,终将会迎来收获的金秋。 而坏习惯的养成真的只能让人生的脚步停止在寒风凛冽的冬天,让你永远看不见下一个春天。
目录
1、数列的查询和添加问题
2、二叉排序树
2、1 二叉排序树的介绍
2、2 二叉排序树的创建和遍历
2、3 二叉排序树的删除
1、数列的查询和添加问题
讲二叉排序树之前,我们先丢出一个问题:假设给你一个数列 {7,3,10,12,5,2,8},你用什么办法如何实现它进行高效的查询和添加数据?以下有3种方案进行参考;
方案1:使用数组
(1)如果数组未排序,那么直接在数组尾添加,这样速度快,但是查找速度很慢。
(2)如果数组已经排序,那么可以直接使用二分查找,这样查找速度会更快,但是为了确保数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度就变慢。
方案2:使用链表
不管链表是否有序,查找速度都是很慢的,但是添加数据速度会比数组快,不需要数据移动。
方案3:使用二叉排序树
既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
2、二叉排序树
2、1 二叉排序树的介绍
对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大;注意:假设有相同的值,可以将该节点当作左子节点或右子节点。
好,我们现在先画一棵二叉排序树,这样会更好理解一点,二叉排序树如图1所示;
看,假设图1中的5节点是当前节点,那么左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
2、2 二叉排序树的创建和遍历
将一个数列 array = {7,3,10,12,5,2,8} 创建成对应的二叉排序树,并使用后序遍历(后续遍历相关的内容可看Java版的数据结构和算法(四)这篇文章)二叉排序树。
将数列 array = {7,3,10,12,5,2,8} 创建成二叉排序树的思路如下所示;
(1)用一个 for 循环遍历 array 数组,用 array 元素作为节点的值创建 Node 节点。
(2)将 array 的第一个元素作为一棵树的根节点 root ,刚开始 root 的左右子树都为空。
(3)将 array 的第二个元素以及后面的元素添加到 root 为根节点的树中。
(4)添加的元素如果小于当前节点的值并且当前节点的左子节点为空,那么就将添加的元素作为当前节点的左子节点。
(5)添加的元素如果小于当前节点的值并且当前节点的左子节点不为空,那么就将当前节点的左子节点作为新的当前节点,然后又走(4)步骤。
(6)添加的元素如果大于等于当前节点的值并且当前节点的右子节点为空,那么就将添加的元素作为当前节点的右子节点。
(7)添加的元素如果大于等于当前节点的值并且当前节点的右子节点不为空,那么就将当前节点的右子节点作为新的当前节点,然后又走(4)步骤。
我们上面这些思路,数列 array = {7,3,10,12,5,2,8} 创建的二叉排序树如图2所示;
我们将数列 array = {7,3,10,12,5,2,8} 创建成对应的二叉排序树并使用后序遍历,现在用 Java 代码实现一把;
(1)创建一个节点类 Node :
package com.xiaoer.binarysorttree;
public class Node {
private Node leftNode;
private Node rightNode;
private int value;
public Node(int value) {
super();
this.value = value;
}
public Node getLeftNode() {
return leftNode;
}
public void setLeftNode(Node leftNode) {
this.leftNode = leftNode;
}
public Node getRightNode() {
return rightNode;
}
public void setRightNode(Node rightNode) {
this.rightNode = rightNode;
}
//添加节点方法
public void addNode(Node node) {
if (node == null) {
System.out.println("该节点为空,不进行添加");
return;
}
//判断传入节点的值是否比当前节点的值小
if (node.value < value) {
//如果当前节点的左子节点为空,那么就把传入的节点作为当前节点的左子节点
if (leftNode == null) {
leftNode = node;
//递归遍历当前节点的左子树添加 node
} else {
leftNode.addNode(node);
}
//否则添加的节点的值大于等于当前节点的值
} else {
//如果当前节点的右子节点为空,那么就把传入的节点作为当前节点的右子节点
if (rightNode == null) {
rightNode = node;
//递归遍历当前节点的右子树添加 node
} else {
rightNode.addNode(node);
}
}
}
//后续遍历
public void postOrder() {
if (leftNode != null) {
leftNode.postOrder();
}
if (rightNode != null) {
rightNode.postOrder();
}
System.out.println(this);
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
}
(2)创建一个测试类 Test :
package com.xiaoer.binarysorttree;
public class Test {
private Node rootNode;
public static void main(String[] args) {
int[] array = {7,3,10,12,5,2,8};
Test test = new Test();
//for 循环将数组转化成二叉排序树
for (int i = 0;i < array.length;i++) {
test.addNode(new Node(array[i]));
}
//二叉排序树的后续遍历
test.postOrder();
}
private void addNode(Node node) {
if (rootNode == null) {
rootNode = node;
} else {
rootNode.addNode(node);
}
}
private void postOrder() {
if (rootNode == null) {
System.out.println("这是一棵空树");
} else {
rootNode.postOrder();
}
}
}
运行程序,日志打印如下所示;
根据日志打印的结果,确实我们用后序遍历的二叉树就是图2中的二叉树。
2、3 二叉排序树的删除
二叉排序树的删除有三种情况需要考虑;
(1)删除叶子节点(比如图2中的2、5、8、12)。
(2)删除只有一棵子树的节点。
(3)删除有两颗子树的节点(比如图2中的3、7、10)
好,我们现在写一下这三种情况的思路;
删除叶子节点的思路
(1)找到要删除的结点。
(2)找到要删除的节点的父结点。
(3)确定要删除的节点是父节点的左子结点还是右子结点,如果是左子节点,就将父节点的左子节点置空;如果是右子节点,就将父节点的右子节点置空。
删除只有一棵子树的节点的思路
(1)先去找到要删除的结点(假设为 deleteNode)
(2)找到 deleteNode 的父结点(假设为 parentNode)
(3)确定 deleteNode 的子结点是左子结点还是右子结点。
(4)确定 deleteNode是 parentNode 的左子结点还是右子结点。
(5)如果 deleteNode 有左子节点且 deleteNode是 parentNode 的左子结点,那么 parentNode 的左子节点就指向 targetNode 的左子节点。
(6)如果 deleteNode 有左子节点且 deleteNode是 parentNode 的右子结点,那么 parentNode 的右子节点就指向 targetNode 的左子节点。
(7)如果 deleteNode 有右子节点且 deleteNode是 parentNode 的左子结点,那么 parentNode 的左子节点就指向 targetNode 的右子节点。
(8)如果 deleteNode 有右子节点且 deleteNode是 parentNode 的右子结点,那么 parentNode 的右子节点就指向 targetNode 的右子节点。
删除有两颗子树的节点的思路
(1)先去找到要删除的结点(假设是 deleteNode) 。
(2)找到 deleteNode 的父结点(假设是 parentNode)。
(3)从 deleteNode 的右子树找到最小的结点。
(4)用一个临时变量(temp),将最小结点的值保存 temp 中,并删除该最小结点。
(5)将 deleteNode 的值设置为 temp 。
好,我们现在在图2的基础上添加一个值为9的节点,那么按照创建二叉排序树的规则,这个值为9的节点就成为了8节点的右子节点,那么就得到如图3所示的二叉排序树了;
现在我们要在图3的二叉排序树中分3次删除一个节点,第一次删除叶子节点12,第二次删除只有一棵子树的节点(也就是8 ),第三次删除有两棵子树的节点(就拿10节点来吧);好,我们现在代码实现一把,在上面创建二叉排序树的案例的代码上,我们只需要添加部分代码即可;
(1)在 Node 类中添加2个方法,那就是 setValue(int value) 和 getValue() 方法;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
(2)在 Test 类中添加 deleteNode(int value, Node rootNode)、deleteRightTreeMin(Node node)、deleteMinNode(int value, Node deleteNode) 、searchDeleteNode(int value, Node node) 和 searchDeleteParentNode(int value, Node node) 这几个方法;
/**
* 删除节点
*
* @param value
* 查找要删除的值
* @param rootNode
* 根节点
*/
private void deleteNode(int value, Node rootNode) {
if (rootNode == null) {
System.out.println("这是一棵空树,删除失败");
return;
}
// 查找要删除的节点
Node deleteNode = searchDeleteNode(value, rootNode);
if (deleteNode == null) {
System.out.println("没找到要删除的节点,删除失败");
return;
}
// 如果这棵树只有一个根节点,那么就将根节点置空
if (rootNode.getLeftNode() == null && rootNode.getRightNode() == null) {
rootNode = null;
System.out.println("这棵树只有一个根节点,删除成功");
return;
}
// 如果要删除的节点是根节点且只有一颗左子树
if (rootNode.getLeftNode() != null && rootNode.getRightNode() == null
&& rootNode.getValue() == value) {
rootNode = rootNode.getLeftNode();
return;
}
// 如果要删除的节点是根节点且只有一颗右子树
if (rootNode.getLeftNode() == null && rootNode.getRightNode() != null
&& rootNode.getValue() == value) {
rootNode = rootNode.getRightNode();
return;
}
// 查找要删除节点的父节点
Node parentNode = searchDeleteParentNode(value, rootNode);
// 如果删除的节点是叶子节点
if (deleteNode.getLeftNode() == null
&& deleteNode.getRightNode() == null) {
if (parentNode.getLeftNode() != null
&& parentNode.getLeftNode().getValue() == value) {
parentNode.setLeftNode(null);
} else if (parentNode.getRightNode() != null
&& parentNode.getRightNode().getValue() == value) {
parentNode.setRightNode(null);
}
// 要删除有2棵子树的节点
} else if (deleteNode.getLeftNode() != null
&& deleteNode.getRightNode() != null) {
int minValue = deleteRightTreeMin(deleteNode.getRightNode());
deleteNode.setValue(minValue);
// 要删除只有一颗子树的节点
} else {
// 要删除的节点只有左子节点
if (deleteNode.getLeftNode() != null) {
// deleteNode 是 parentNode 的左子树
if (parentNode.getLeftNode() != null
&& parentNode.getLeftNode().getValue() == value) {
parentNode.setLeftNode(deleteNode.getLeftNode());
// deleteNode 是 parentNode 的右子树
} else {
parentNode.setRightNode(deleteNode.getLeftNode());
}
// 要删除的节点只有右子节点
} else {
// deleteNode 是 parentNode 的左子树
if (parentNode.getLeftNode() != null
&& parentNode.getLeftNode().getValue() == value) {
parentNode.setLeftNode(deleteNode.getRightNode());
// deleteNode 是 parentNode 的右子树
} else {
parentNode.setRightNode(deleteNode.getRightNode());
}
}
}
}
/**
*
* @param node
* 以node为根节点
* @return 返回以node为根节点子树中最小的 value 值
*/
private int deleteRightTreeMin(Node node) {
Node t = node;
while (t.getLeftNode() != null) {
t.setLeftNode(t.getLeftNode());
}
int value = t.getValue();
// 删除最小节点
deleteMinNode(value, t);
return value;
}
private void deleteMinNode(int value, Node deleteNode) {
deleteNode(value, deleteNode);
}
// 查找要删除的节点(值为 value),node 为当前节点
private Node searchDeleteNode(int value, Node node) {
if (node == null) {
System.out.println("进行递归的节点不能为空");
return null;
}
// 找到该节点
if (value == node.getValue()) {
return node;
// 如果查找的值小于当前节点的值,那么就向左递归查找
} else if (value < node.getValue()) {
// 如果当前节点的左子节点为空,那么就不进行递归查找要删除的节点了
if (node.getLeftNode() == null) {
return null;
}
return searchDeleteNode(value, node.getLeftNode());
// 如果查找的值大于当前节点的值,那么就向右递归查找
} else {
// 如果当前节点的右子节点为空,那么就不进行递归查找要删除的节点了
if (node.getLeftNode() == null) {
return null;
}
return searchDeleteNode(value, node.getRightNode());
}
}
/**
*
* @param value
* 要查找的值
* @param node
* 要删除节点的父节点
* @return 返回当前节点的父节点
*/
private Node searchDeleteParentNode(int value, Node node) {
// 要删除节点的父节点如果为空
if (node == null) {
return null;
}
// 如果要删除节点的父节点的值等于要查找的值,则返回要删除节点的父节点
if ((node.getLeftNode() != null && node.getLeftNode().getValue() == value)
|| (node.getRightNode() != null && node.getRightNode()
.getValue() == value)) {
return node;
} else {
// 如果要查找的值小于当前节点的值且当前节点的左子节点不为空
if (value < node.getValue() && node.getLeftNode() != null) {
// 向左子树递归查找
return searchDeleteParentNode(value, node.getLeftNode());
// 如果要查找的值大于等于当前节点的值且当前节点的右子节点不为空
} else if (value >= node.getValue() && node.getRightNode() != null) {
// 向右子树递归查找
return searchDeleteParentNode(value, node.getRightNode());
} else {
// 没有找到父节点
return null;
}
}
}
删除叶子节点12,程序入口调用如下所示;
public static void main(String[] args) {
int[] array = { 7, 3, 10, 12, 5, 2, 8 ,9};
Test test = new Test();
// for 循环将数组转化成二叉排序树
for (int i = 0; i < array.length; i++) {
test.addNode(new Node(array[i]));
}
test.deleteNode(12, test.rootNode);
// 二叉排序树的后续遍历
test.postOrder();
}
删除叶子节点12,日志打印如下所示;
删除只有一颗子树的节点8,程序入口调用如下所示;
public static void main(String[] args) {
int[] array = { 7, 3, 10, 12, 5, 2, 8 ,9};
Test test = new Test();
// for 循环将数组转化成二叉排序树
for (int i = 0; i < array.length; i++) {
test.addNode(new Node(array[i]));
}
test.deleteNode(8, test.rootNode);
// 二叉排序树的后续遍历
test.postOrder();
}
删除只有一颗子树的节点8,日志打印如下所示;
删除有两棵子树的节点10,程序入口调用如下所示;