二叉查找树
特点
- 所有节点最多拥有两个子节点,即度不大于2
- 左子树的键值小于根的键值,右子树的键值大于根的键值
- 二叉树的左右子树都是二叉树
- 没有键值相同的节点
缺点
- 只限制了节点的基本顺序,所以存在多种深度,如上图,同一组数据生成的这两种不同的树,第二种树查询效率很低,对于值 4 的查询,几乎要遍历全部的节点才能得到结果。
插入及删除原理
插入是在叶子节点上新增内容
删除分为三种情况:
-
待删除的节点没有子节点
如上图 ② 中的 值 4,此时删除该节点不影响二叉树的整体结构,直接删除即可 -
待删除的节点有一个子节点
如上图 ② 中的值 6,此时删除节点 6 ,会导致 4 与树断开链接,因此我们需要进行调整, 6 作为 8 的左子节点,因此 6 的子节点中的所有值都小于 8 ,即 6 的子节点 一定是 10 的左子节点,而 6 只有一个子节点,因此子节点树的节点顺序不需要改变,即将节点 8 的左子节点直接替换为 4 即可。右子节点同理 -
待删除的节点有两个子节点
-
先了解一下后继节点的概念,后继节点为在中序遍历中紧跟在目标节点后面一个的节点,因为中序遍历时按照左中右的顺序,因此后继节点一定是目标节点右子节点中的最小左节点。
-
如图③节点 9,拥有两个子节点,此时删除该节点影响二叉树的整体结构,我们需要对其进行重新排序。
-
图③的中序排序顺序为 【3】【9】【10】【14】【15】【18】【20】【30】
-
按照中序排列获得 9 的后继节点为 10 ,因为 10 是 9 的后继节点,所以 9 的左子节点都小于 10 ,除了 10 之外的右子节点都大于 10 ,所以我们将 10 移动到 9 的位置,将节点 9 的左子节点和右子节点都赋值给 10,就可以保持二叉树的结构。
-
但是还有一种情况,即图④,后继节点拥有一个右子节点,这个时候我们不能直接将后继节点移动到待删除的节点位置上。
-
图④的中序排序顺序为 【3】【9】【10】【12】【14】【15】【18】【20】【30】
-
但是同理,按照中序排列获得 9 的后继节点为 10 ,因为 10 是 9 的后继节点,所以 9 的左子节点都小于 10 ,而除 10 节点树【10】【12】之外的所有节点,都大于 10 节点树中的最大即最右节点 【12】.因此,我们需要将节点 9 的左子节点赋值给 10,将 9 的右子节点赋值给【12】,然后将 10 移动到 9 的位置即可。
Java代码实现
节点实体类,记录节点信息
- 包含当前节点的值、左子节点信息、右子节点信息
- 提供先序遍历、中序遍历、后序遍历方法
public class Node {
private Integer value;
private Node parentNode;
private Node leftNode;
private Node rightNode;
public Node(){
}
private void setParentNode(Node node){
this.parentNode = node;
}
public Node getParentNode(){
return this.parentNode;
}
public Integer getValue(){
return this.value;
}
public void setValue(Integer value){
this.value = value;
}
public Node getLeftNode(){
return this.leftNode;
}
public void setLeftNode(Node node){
//自动设置父节点
if(null != node ) node.setParentNode(this);
this.leftNode = node;
}
public Node getRightNode(){
return this.rightNode;
}
public void setRightNode(Node node){
//自动设置父节点
if(null != node ) node.setParentNode(this);
this.rightNode = node;
}
/**
* 前序遍历
*/
public void DLR(){
if(value!=null) {
System.out.print(String.format("【%d】", value));
if (this.leftNode != null) {
this.leftNode.DLR();
}
if (this.rightNode != null) {
this.rightNode.DLR();
}
}
}
/**
* 中序遍历
*/
public void LDR(){
if(value!=null) {
if (this.leftNode != null) {
this.leftNode.LDR();
}
System.out.print(String.format("【%d】", value));
if (this.rightNode != null) {
this.rightNode.LDR();
}
}
}
/**
* 后续遍历
*/
public void LRD() {
if (value != null) {
if (this.leftNode != null) {
this.leftNode.LRD();
}
if (this.rightNode != null) {
this.rightNode.LRD();
}
System.out.print(String.format("【%d】", value));
}
}
}
二叉树操作类
public class BstTree {
private Node root;
public Node getTree(){
return this.root;
}
/**
* 插入元素
* @param i
*/
public void insert(int i){
Node newNode = new Node();
newNode.setValue(i);
if(root==null){
//没有根结点时 将当前节点作为根节点
root=newNode;
}else{
Node currentNode = root;
Node parentNode;
while (null != currentNode) {
// 当前节点视为父节点
parentNode = currentNode;
if (i > currentNode.getValue()) {
// 大于父节点的值 且父节点没有右子节点 则作为右节点插入
currentNode = currentNode.getRightNode();
if (null == currentNode) {
parentNode.setRightNode(newNode);
}
} else if(i < currentNode.getValue()) {
// 小于父节点的值 且父节点没有左子节点 则作为左节点插入
currentNode = currentNode.getLeftNode();
if (null == currentNode) {
parentNode.setLeftNode(newNode);
}
}else{
//值已存在 则跳出
System.out.println(String.format("值【%d】已存在",i));
return;
}
}
}
}
/**
* 查找元素节点
* @param key
* @return
*/
public Node find(int key) {
Node currentNode = root;
if (null != currentNode) {
// 判断是否是当前节点
while (key != currentNode.getValue()) {
//值大于当前节点 向右遍历
if (key > currentNode.getValue()) {
currentNode = currentNode.getRightNode();
} else {
//值小于当前节点 向左遍历
currentNode = currentNode.getLeftNode();
}
if (null == currentNode) {
return null;
}
}
}
return currentNode;
}
/**
* 查找 node 的前驱节点
* @param node
* @return
*/
public Node precursor(Node node) {
//前驱节点即 左子树的最大节点
Node maxNode = null;
if(node.getLeftNode()!=null){
maxNode=node.getLeftNode();
while(maxNode!=null){
Node currentChild = maxNode.getRightNode();
if(currentChild!=null){
maxNode = maxNode.getLeftNode();
}else{
break;
}
}
}
return maxNode;
}
/**
* 查找 node 的后继节点
* @param node
* @return
*/
public Node successor(Node node) {
//后继节点 即 右节点的最小值
Node minNode = null;
if(node.getRightNode()!=null){
minNode=node.getRightNode();
while(minNode!=null){
Node currentChild = minNode.getLeftNode();
if(currentChild!=null){
minNode = minNode.getLeftNode();
}else{
break;
}
}
}
return minNode;
}
/**
* 用新的节点替换当前节点
* @param oldNode
* @param newNode
*/
private void updateNode(Node oldNode,Node newNode){
if (oldNode.getParentNode().getLeftNode() == oldNode){
oldNode.getParentNode().setLeftNode(newNode);
}else if (oldNode.getParentNode().getRightNode() == oldNode){
oldNode.getParentNode().setRightNode(newNode);
}
}
/**
* 删除节点 分三种情况
* 叶子节点 直接删除
* 有一个子节点 将子节点的父节点设置为被删除节点的父节点
* 左右节点都存在时 可以先找到需要删除的节点的后继节点或前驱节点替换当前节点
* @param key
*/
public void delete(int key) {
Node deleteNode = find(key);
if(deleteNode!=null){
if(deleteNode.getLeftNode()!=null && deleteNode.getRightNode()!=null){
//两个子节点,查找后继节点替换当前节点
Node backContinueNode = successor(deleteNode);
//将后继节点 替换为 NULL
this.updateNode(backContinueNode,null);
//左子节点赋值
backContinueNode.setLeftNode(deleteNode.getLeftNode());
//后继节点有右子节点
Node rightNode = backContinueNode.getRightNode();
if(rightNode!=null){
//获得最后一个右子节点 赋值
while(rightNode!=null){
if(rightNode.getRightNode()!=null){
rightNode=rightNode.getRightNode();
}else{
//最后一个右子节点
rightNode.setRightNode(deleteNode.getRightNode());
break;
}
}
}else{
//将待删除节点的子节点内容赋值给 后继节点
backContinueNode.setRightNode(deleteNode.getRightNode());
}
//将待删除节点 替换为 后继节点
//需要删除的节点没有被引用的地方,会自动回收
this.updateNode(deleteNode,backContinueNode);
}else if(deleteNode.getLeftNode()!=null){
//只有左子节点 将左子节点和父节点直接相连
this.updateNode(deleteNode,deleteNode.getLeftNode());
}else if(deleteNode.getRightNode()!=null){
//只有右子节点 将右子节点和父节点直接相连
this.updateNode(deleteNode,deleteNode.getRightNode());
}else{
//没有子节点
this.updateNode(deleteNode,null);
}
}else{
throw new RuntimeException("没有该节点");
}
}
}