文章目录
基本概念
二叉查找树
也叫 二叉搜索树,二叉排序树.它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
二叉查找树期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n)),在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)
叶子节点
一棵树当中没有子结点(即度为0)的结点称为叶子结点,简称“叶子”。 叶子是指度为0的结点,又称为终端结点。
根结点
根结点(root)是树的一个组成部分,也叫树根。所有非空的二叉树中,都有且仅有一个根结点。它是同一棵树中除本身外所有结点的祖先,没有父结点。
根节点一般作为一个树遍历的最初起点.
子节点
子节点是父节点的下一层节点.当子节点没有子节点时,其也是叶子节点.
先序遍历二叉树
从一棵树的根节点开始,每一个节点都按照下面的顺序遍历:本节点、本节点左子树、本节点右子树
中序遍历二叉树
从一棵树根节点开始,每一个节点都按照下面的顺序遍历,本节点左子树、本节点、本节点右子树
按照中序遍历的结果正好满足将树的节点的值按照 从小 到 大 升序排列
后序遍历二叉树
从一棵树根节点开始,每一个节点按照下面的顺序遍历,本节点左子树,本节点右子树,本节点
Java 实现二叉查找树
注意,本文涉及到的代码不包含包路径,如自己测试请自己补充完整
定义一个节点数据结构 MyNode
/**
* 自定义一个节点
* @author jie.wu
*/
public class MyNode {
/**索引值*/
private int id;
/**数据项*/
private int value;
/**左节点*/
private MyNode leftNode;
/**右节点*/
private MyNode rightNode;
/**父节点
* 平时不用,为了标记被查找节点,为删除节点调整树做准备
*/
private MyNode parentNode;
public MyNode() {
}
public MyNode(int id,int value) {
this.id=id;
this.value=value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public MyNode getLeftNode() {
return leftNode;
}
public void setLeftNode(MyNode leftNode) {
this.leftNode = leftNode;
}
public MyNode getRightNode() {
return rightNode;
}
public void setRightNode(MyNode rightNode) {
this.rightNode = rightNode;
}
public MyNode getParentNode() {
return parentNode;
}
public void setParentNode(MyNode parentNode) {
this.parentNode = parentNode;
}
@Override
public String toString() {
return "MyNode [id=" + id + ", value=" + value + ", parentNode=" + parentNode + "]";
}
}
对节点的操作 BinaryTree
本类提供了对二叉查找树做添加子节点、删除子节点、前中后序遍历方法、最小节点、查找指定节点的方法
根节点一直是比较重要的
遍历二叉树的方式有前序,中序,后序,中序即从小到大对节点进行升序排序
再一个,删除节点分为删除叶子节点,删除包含一个字树的节点,删除包含左右子树的节点,每一种情况都需要把删除节点是根节点单独进行考虑
删除节点的逻辑是最复杂,也是最有意思的部分,需要确保删除后树依旧是二叉查找树,本文的逻辑是:先找到被删除的节点,然后将该节点的父节点和左右节点的关系从新进行组织,根据二叉树的性质,其右子树的任何一个节点值都大于左子树的节点,所在在删除的节点含左右节点时,本文的逻辑是将被删除节点的左子树整体放到被删除节点的右子树的最小节点的左节点。
/*
* 假设一个小的二叉查找树有三个节点,父节点、左子节点、右子节点,
* 中序遍历是左子节点、父节点、右子节点;
* 前序是父节点、左子节点、右子节点;
* 后序是左子节点、右子节点、父节点。
*/
public class BinaryTree {
/**被查找节点*/
public MyNode searchNode=new MyNode();
/**根节点*/
public MyNode root;
/**
* 向二叉树新加入节点
* @param relativeRootNode 相对根节点
* @param newNode 新加入节点
* @return
*/
boolean insertNewNode(MyNode relativeRootNode,MyNode newNode) {
if(newNode==null) {
return false;
}
if(relativeRootNode.getId()==newNode.getId()) {
return false;
}
if(relativeRootNode.getId()>newNode.getId()) {
if(relativeRootNode.getLeftNode()==null) {
relativeRootNode.setLeftNode(newNode);
}else {
insertNewNode(relativeRootNode.getLeftNode(),newNode);
}
}else {
if(relativeRootNode.getRightNode()==null) {
relativeRootNode.setRightNode(newNode);
}else {
insertNewNode(relativeRootNode.getRightNode(),newNode);
}
}
return true;
}
/**
* 二叉树查找算法
* @param root
* @param id
* @return
*/
MyNode searchByid(MyNode currentNode,int id) {
if(currentNode!=null) {
//如果跟节点就是所查节点
if(currentNode.getId()==id) {
return currentNode;
}
//如果本节点索引值>索引值-查找左节点
if(currentNode.getId()>id) {
return searchByid(currentNode.getLeftNode(),id);
}
//如果本节点索引值<索引值-查找右节点
if(currentNode.getId()<id) {
return searchByid(currentNode.getRightNode(),id);
}
}
return null;
}
/**
* 中序遍历查询二叉树:左、父、右
* @param currentRoot
*/
public void inOrder(MyNode currentRoot) {
if(currentRoot!=null) {
inOrder(currentRoot.getLeftNode());
System.out.print(currentRoot.getId()+"-");
inOrder(currentRoot.getRightNode());
}
}
/**
* 遍历方式 查询指定子节点的父节点和两个子节点
* @param currentRoot
* @param search
* @param parentNode
*/
public MyNode inOrderFindParentAndNext(MyNode currentRoot,int search,MyNode parentNode) {
if(currentRoot!=null) {
if(currentRoot.getId()==search) {
//确定父节点
currentRoot.setParentNode(parentNode);
System.out.println("找到指定节点了"+search);
//没有孩子节点
if(currentRoot.getLeftNode()==null&¤tRoot.getRightNode()==null) {
System.out.println("没有孩子节点;parentNode="+(parentNode==null?null:parentNode.getId()));
//currentToSearchNode(currentRoot);
return currentRoot;
}
//只有左孩子节点
if(currentRoot.getLeftNode()!=null&¤tRoot.getRightNode()==null) {
System.out.println("只有左孩子节点;parentNode="+(parentNode==null?null:parentNode.getId())+";左孩子:"+currentRoot.getLeftNode()+";右孩子=null");
//currentToSearchNode(currentRoot);
return currentRoot;
}
//只有右孩子节点
if(currentRoot.getLeftNode()==null&¤tRoot.getRightNode()!=null) {
System.out.println("只有左孩子节点;parentNode="+(parentNode==null?null:parentNode.getId())+";左孩子=null;右孩子:"+currentRoot.getRightNode().getId());
//currentToSearchNode(currentRoot);
return currentRoot;
}
//具有两个节点
if(currentRoot.getLeftNode()!=null&¤tRoot.getRightNode()!=null) {
System.out.println("左右孩子都有;parentNode="+
(parentNode==null?null:parentNode.getId())+
";左孩子:"+currentRoot.getLeftNode().getId()+
";右孩子:"+currentRoot.getRightNode().getId());
//currentToSearchNode(currentRoot);
return currentRoot;
}
}
MyNode temNode;
//继续遍历左节点
temNode=inOrderFindParentAndNext(currentRoot.getLeftNode(),search,currentRoot);
if(temNode!=null) {
return temNode;
}
//继续遍历右节点
temNode=inOrderFindParentAndNext(currentRoot.getRightNode(),search,currentRoot);
if(temNode!=null) {
return temNode;
}
}
return null;
}
/**
* 前序:父、左、右
* @param currentRoot
*/
public void preOrder(MyNode currentRoot) {
if(currentRoot!=null) {
System.out.println(currentRoot.getId()+";"+currentRoot.getValue());
preOrder(currentRoot.getLeftNode());
preOrder(currentRoot.getRightNode());
}
}
/**
* 后序:左、右、父
* @param currentRoot
*/
public void afterOrder(MyNode currentRoot) {
if(currentRoot!=null) {
afterOrder(currentRoot.getLeftNode());
afterOrder(currentRoot.getRightNode());
System.out.println(currentRoot.getId()+";"+currentRoot.getValue());
}
}
/**
* 寻找指定节点下的最小子节点
* @param currentRootNode
* @return
*/
public MyNode getMinNode(MyNode currentRootNode) {
if(currentRootNode!=null) {
if(currentRootNode.getLeftNode()==null) {
return currentRootNode;
}else {
return getMinNode(currentRootNode.getLeftNode());
}
}
return null;
}
/**初始化二叉树的方法
5
/ \
3 8
/ \ / \
1 4 7 10
* */
public void initRootNode(BinaryTree b,MyNode root) {
MyNode[] nodeArray=new MyNode[6];
MyNode n0=new MyNode(3,3);
MyNode n1=new MyNode(8,8);
MyNode n2=new MyNode(4,4);
MyNode n3=new MyNode(1,1);
MyNode n4=new MyNode(7,7);
MyNode n5=new MyNode(10,10);
nodeArray[0]=n0;
nodeArray[1]=n1;
nodeArray[2]=n2;
nodeArray[3]=n3;
nodeArray[4]=n4;
nodeArray[5]=n5;
//向树中添加
for(int i=0;i<nodeArray.length;i++) {
if(nodeArray[i]!=null) {
b.insertNewNode(root,nodeArray[i]);
}
}
}
/**
* 将形参对象的值赋值给 全局对象 searchNode
* @param currentNode
*/
private void currentToSearchNode(MyNode currentNode) {
searchNode.setId(currentNode.getId());
searchNode.setLeftNode(currentNode.getLeftNode());
searchNode.setRightNode(currentNode.getRightNode());
searchNode.setParentNode(currentNode.getParentNode());
}
//删除节点
//第一种情况:删除末尾的一个节点
//第二种情况:被删除的节点有一个子节点
//第三种情况:被删除的节点有两个子节点
public boolean deleteById(MyNode currentRoot,int id) {
//新进行查找-查找结果为 searchNode
searchNode=inOrderFindParentAndNext(currentRoot,id,null);
//如果查询成功
if(searchNode.getId()==id) {
//第一种情况,删除叶子节点
if(searchNode.getLeftNode()==null&&searchNode.getRightNode()==null) {
//如果是根节点
if(searchNode.getParentNode()==null) {
System.out.println("只有根节点");
root=null;
return true;
}
//本节点是父节点的左节点吗
if(searchNode.getParentNode().getLeftNode()!=null&&searchNode.getParentNode().getLeftNode().getId()==searchNode.getId()) {
System.out.println("是左节点");
searchNode.getParentNode().setLeftNode(null);
}else if(searchNode.getParentNode().getRightNode()!=null&&searchNode.getParentNode().getRightNode().getId()==searchNode.getId()) {
System.out.println("是右节点");
searchNode.getParentNode().setRightNode(null);
}
return true;
}
//第二种情况,被删除节点有一个子节点
//2.1、左节点为null,右节点不为 null
if(searchNode.getLeftNode()==null&&searchNode.getRightNode()!=null) {
//如果是根节点
if(searchNode.getParentNode()==null) {
//更新根节点
root=searchNode.getRightNode();
}else {
//如果是父节点的左节点
if(searchNode.getParentNode().getLeftNode().getId()==searchNode.getId()) {
searchNode.getParentNode().setLeftNode(searchNode.getRightNode());
}else {//如果是父节点的右节点
searchNode.getParentNode().setRightNode(searchNode.getRightNode());
}
}
return true;
}
//2.2、左节点不为 null,右节点为null
if(searchNode.getLeftNode()!=null&&searchNode.getRightNode()==null) {
//如果是根节点
if(searchNode.getParentNode()==null) {
searchNode.getLeftNode().setParentNode(null);
//更新根节点
root=searchNode.getLeftNode();
}else {
//如果是父节点的左节点
if(searchNode.getParentNode().getLeftNode().getId()==searchNode.getId()) {
searchNode.getParentNode().setLeftNode(searchNode.getLeftNode());
}else {//如果是父节点的右节点
searchNode.getParentNode().setRightNode(searchNode.getLeftNode());
}
}
return true;
}
//第三种情况,被删除节点有两个子节点
if(searchNode.getLeftNode()!=null&&searchNode.getRightNode()!=null) {
//如果是根节点
if(searchNode.getParentNode()==null) {
//左子树总是小于右子树
//将根节点左子树 安置 到 根节点右子树的最小值的叶子节点的左节点
MyNode rightMinNode=getMinNode(root.getRightNode());
rightMinNode.setLeftNode(searchNode.getLeftNode());
//右节点变为新的根节点
root=searchNode.getRightNode();
}else {//如果不是根节点
//当前节点左子节点作为当前节点右子树最小子节点的左子节点
MyNode rightMinNode=getMinNode(searchNode.getRightNode());
rightMinNode.setLeftNode(searchNode.getLeftNode());
//当前节点右子节点取代当前节点-当前节点的父节点的孩子节点就是当前节点的右子节点
//需要判断当前节点是左节点还是右节点
if(searchNode.getParentNode().getLeftNode().getId()==searchNode.getId()) {
searchNode.getParentNode().setLeftNode(searchNode.getRightNode());
}else {
searchNode.getParentNode().setRightNode(searchNode.getRightNode());
}
}
return true;
}
//没有匹配的模式
return false;
}
//没有找到匹配的节点
return false;
}
}
/**
* 获取以节点为根节点的树的高度
* 基本思路是
* 如果本层节点不为null,那么本层高度就是1+最高子树长度
* @param currentNode
* @return
*/
public int getHeight(MyNode currentNode) {
int height=0;
if(currentNode==null) {
height=0;
return height;
}else {
height=1;
}
int left=getHeight(currentNode.getLeftNode());
int right=getHeight(currentNode.getRightNode());
if(left>=right) {
height+=left;
}else {
height+=right;
}
return height;
}
测试:支持自由添加删除节点-使用中序遍历观察结果
import org.junit.Test;
public class BinaryTreeTest {
/**先序、中序、后序遍历二叉树*/
@Test
public void test1() {
//初始化查询二叉树
BinaryTree b=new BinaryTree();
b.root=new MyNode(5,5);
b.initRootNode(b,b.root);
//中序 遍历二叉树
System.out.println("中序begin:");
b.inOrder(b.root);
System.out.println("中序end:");
//前序
System.out.println("前序begin:");
b.preOrder(b.root);
System.out.println("前序end:");
//后序
System.out.println("后序begin:");
b.afterOrder(b.root);
System.out.println("后序end:");
}
/**遍历的方式打印出被查询的节点的父节点和两个子节点*/
/*
5
/ \
3 8
/ \ / \
1 4 7 10
*/
@Test
public void test2() {
//初始化查询二叉树
BinaryTree b=new BinaryTree();
b.root=new MyNode(5,5);
b.initRootNode(b,b.root);
//查找结果
b.inOrderFindParentAndNext(b.root,1,null);
//检查一下获取到的节点
System.out.println("值:"+b.searchNode.getId()+";"
+"左:"+(b.searchNode.getLeftNode()==null?null:b.searchNode.getLeftNode().getId())
+";"
+"右:"+(b.searchNode.getRightNode()==null?null:b.searchNode.getRightNode().getId())
+";"
+"父:"+(b.searchNode.getParentNode()==null?null:b.searchNode.getParentNode().getId())
);
}
/**
* 动态添加和删除
*/
@Test
public void test3() {
int deleteId=5;
//动态添加节点
//初始化查询二叉树
BinaryTree b=new BinaryTree();
b.root=new MyNode(5,5);
b.initRootNode(b,b.root);
/* 5
/ \
3 8
/ \ / \
1 4 7 10 */
//中序打印
b.inOrder(b.root);
//动态删除节点
b.deleteById(b.root, 3);
b.inOrder(b.root);
b.deleteById(b.root,8);
b.inOrder(b.root);
}
}
/**树的高度*/
@Test
public void test4() {
//动态添加节点
//初始化查询二叉树
BinaryTree b=new BinaryTree();
b.root=new MyNode(5,5);
b.initRootNode(b,b.root);
int height=b.getHeight(b.root);
System.out.println("高度:"+height);
}
参考链接
[1]、https://baike.baidu.com/item/二叉搜索树/7077855?fr=aladdin
[2]、https://baike.baidu.com/item/叶子结点/3620239?fr=aladdin
[3]、https://blog.youkuaiyun.com/u013486414/article/details/92759838
[4]、https://www.cnblogs.com/mojxtang/p/10122587.html
[5]、https://mp.weixin.qq.com/s?__biz=Mzg2NzA4MTkxNQ==&mid=2247485220&idx=1&sn=7bca40ca5a3e703f6c9f89eb0f9f2be4&scene=21#wechat_redirec
[6]、https://blog.youkuaiyun.com/isea533/article/details/80345507
1489

被折叠的 条评论
为什么被折叠?



