自己动手实现一个简单的二叉树结构
二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个结点最多只能有两棵子树,且有左右之分。
在这篇文章中,尝试去实现一个二叉树结构,一些基本的概念在本文就不再赘述了,有兴趣的朋友可以去查找相关的资料来阅读。
在本例中,数据插入的规则是按照顺序大小插入,从根结点开始,将要插入的数据与当前结点比较,如果小于当前结点的数据,则前往左子树,如果大于当前结点的数据,则前往右子树; 小的数据在左孩子,大的数据在右孩子。
按照上面所定义的规则,将10,20,15,3,6,18,22,56,21,46,47,57
依次插入二叉树中,就可以得到一个如图所示的二叉树。那么如何去用代码定义这种插入规则呢,另外,二叉树的查找、前序遍历、中序遍历、后序遍历及结点的删除又如何实现呢,来代码中一探究竟吧!
数据结点部分代码:
package binarytree;
public class TreeNode {
private long data;
private String sDataString;
private TreeNode LeftChild;
private TreeNode rightChild;
public TreeNode(long data, String sDataString) {
this.data = data;
this.sDataString =sDataString;
}
public long getData() {
return data;
}
public String getsDataString() {
return sDataString;
}
public void setsDataString(String sDataString) {
this.sDataString = sDataString;
}
public void setData(long data) {
this.data = data;
}
public TreeNode getLeftChild() {
return LeftChild;
}
public void setLeftChild(TreeNode leftChild) {
LeftChild = leftChild;
}
public TreeNode getRightChild() {
return rightChild;
}
public void setRightChild(TreeNode rightChild) {
this.rightChild = rightChild;
}
}
二叉树主体部分代码:
package binarytree;
public class BinaryTree {
// 根结点
private TreeNode root;
/**
* 插入结点
*
* @param data
* @param sDataString
*/
public void insertNode(long data, String sDataString) {
// 封装结点
TreeNode newNode = new TreeNode(data, sDataString);
// 定义当前结点为根结点,也就是说,从根结点开始插入
TreeNode currentNode = root;
// 定义要插入结点的父结点,也就是插入位置的父结点
TreeNode parentNode;
// 如果,根结点为空,也就是说该二叉树还是个空二叉树
if (root == null) {
// 直接将要插入的结点引用为根结点
root = newNode;
// 结束插入
return;
} else {
// 如果根结点不为空,就要根据规则寻找插入位置了
while (true) {
// 先把当前结点引用为父结点,万一子结点有位置,就直接插入
parentNode = currentNode;
// 判断当前结点的数值与插入的数值谁大
if (currentNode.getData() > data) {
// 如果插入的值小于当前结点的数值,则该数应该插入到该二叉树的左子树
// 判断结束后,将当前结点的引用指向它的左子树,如果左子树有结点,则循环直到找到位置
currentNode = currentNode.getLeftChild();
// 如果当前结点为空,也就是父结点的左孩子为空,证明有位置
if (currentNode == null) {
// 插入结点,也就是将父结点左孩子的引用指向了新建结点
parentNode.setLeftChild(newNode);
// 插入结束
return;
}
} else {
// 如果要插入的数值大于或等于当前结点的数值,则插入到右孩子处
// 将当前结点的引用指向它的右孩子
currentNode = currentNode.getRightChild();
// 如果当前结点为空,也就是父结点的右孩子为空,证明有位置
if (currentNode == null) {
// 插入结点
parentNode.setRightChild(newNode);
// 插入结束
return;
}
}
}
}
}
/**
* 查找结点
*
* @param value
* @return
*/
public TreeNode searchNode(long value) {
// 从根结点root开始查找
TreeNode currentNode = root;
// 直到找到或者找到叶子结点为止
while (value != currentNode.getData()) {
// 如果要查找的数字小于当前结点,则表明该数在当前结点的左子树
if (currentNode.getData() > value) {
// 向左子树方向查找
currentNode = currentNode.getLeftChild();
} else {
// 否则,向右子树方向查找
currentNode = currentNode.getRightChild();
}
// 当查到叶子结点时依然没有找到,则返回null值
if (currentNode == null) {
return null;
}
}
// 找到匹配后会跳出循环,返回查找到的结点
return currentNode;
}
/**
* 获取根结点
*
* @return
*/
public TreeNode getRoot() {
return root;
}
/**
* 前序遍历, 二叉树的遍历,利用了递归的思想 顺序:先访问根结点 访问根结点 前序遍历左子树 前序遍历右子树
*/
public void preorderTraversal(TreeNode rootTreeNode) {
// 判断结点是否为空,如果不为空则向下遍历
if (rootTreeNode != null) {
// 先访问根结点
System.out.println(rootTreeNode.getData() + ": " + rootTreeNode.getsDataString());
// 前序遍历左子树
preorderTraversal(rootTreeNode.getLeftChild());
// 前序遍历右子树
preorderTraversal(rootTreeNode.getRightChild());
} else {
return;
}
}
/**
* 中序遍历 顺序:先访问左子树,根结点在中间访问 中序遍历左子树 访问根结点 中序遍历右子树
*/
public void inorderTraversal(TreeNode rootTreeNode) {
// 同前序遍历,就是顺序不同
if (rootTreeNode != null) {
// 先中序遍历左子树
inorderTraversal(rootTreeNode.getLeftChild());
// 访问根结点
System.out.println(rootTreeNode.getData() + ": " + rootTreeNode.getsDataString());
// 中序遍历右子树
inorderTraversal(rootTreeNode.getRightChild());
} else {
return;
}
}
/**
* 后序遍历 顺序:先访问左子树,再访问右子树,最后访问根结点 后序遍历左子树 后序遍历右子树 访问根结点
*/
public void postorderTraversal(TreeNode rootTreeNode) {
// 同前序遍历,中序遍历
if (rootTreeNode != null) {
// 后序遍历左子树
postorderTraversal(rootTreeNode.getLeftChild());
// 后序遍历
postorderTraversal(rootTreeNode.getRightChild());
// 访问根结点
System.out.println(rootTreeNode.getData() + ": " + rootTreeNode.getsDataString());
} else {
return;
}
}
/**
* 获取一个指定结点的中序后继结点
* 在树中的中序后继结点,就是比当前结点大,但是又是其中最接近该结点的数
* @param targetNode
* @return
*/
public TreeNode getSuccessor(TreeNode targetNode) {
// 定义一个中序后继结点的变量
TreeNode successorNode = targetNode;
// 定义一个中序后继结点的父结点变量
TreeNode successorParentNode = targetNode;
// 定义一个当前结点,中序后继结点都是该右子树的左子树,所以从指定结点的右孩子结点开始
TreeNode currentNode = targetNode.getRightChild();
// 当当前结点不为null的时候,一直往左子树走
while(currentNode != null) {
// 中序后继结点的父结点指向中序后继结点
successorParentNode = successorNode;
// 中序后继结点的引用指向当前的结点
successorNode = currentNode;
// 当前结点右指向自己的左孩子,也就是往左下走
currentNode = currentNode.getLeftChild();
}
// 如果中序后继结点不为指定结点的右孩子
if(successorNode != targetNode.getRightChild()) {
// 该中序后继结点的父结点的左孩子应该指向中序后继结点的右孩子
// 因为这个时候,中序后继结点只可能有右孩子,而我们的目的是用中序后继结点
// 来替代要被删除的结点
successorParentNode.setLeftChild(successorNode.getRightChild());
//将中序后继结点的右孩子引用指向要被删除的结点的右孩子,开始替换了
successorNode.setRightChild(targetNode.getRightChild());
}
// 返回中序后继结点,为后面的删除结点所用
return successorNode;
}
/**
* 删除结点,包括两步:1. 查找结点,2. 删除结点
* 在删除结点中,有三种情况:
* 1. 要删除的结点是个叶子结点,也就是说其没有左孩子也没有右孩子
* 2. 要删除的结点有一个子结点,可以是左孩子,也可以是右孩子
* 3. 要删除的结点有两个子结点,这个时候就比较复杂,要使用中序后继结点来替换该结点来达到删除的目的
* @param target
* @return
*/
public boolean delTreeNode(long target) {
// 定义一个当前查询的起始结点变量
TreeNode currentTreeNode = root;
// 定义一个要删除的结点父结点变量
TreeNode parrentTreeNode = root;
// 因为删除的时候,左子树和右子树的操作不一样,所以这里定义一个判断是否在左子树的boolean型变量
boolean isLeftChild = true;
// 循环,查找匹配的值
while (currentTreeNode.getData() != target) {
// 父结点引用指向当前结点
parrentTreeNode = currentTreeNode;
// 如果查找的值小于当前结点的值,往左子树查找
if (currentTreeNode.getData() > target) {
// 前往左子树
currentTreeNode = currentTreeNode.getLeftChild();
isLeftChild = true;
} else if (currentTreeNode.getData() < target) {
// 否则,前往右子树
currentTreeNode = currentTreeNode.getRightChild();
// 不在左子树
isLeftChild = false;
}
// 如果没有找到,返回false
if(currentTreeNode == null) {
return false;
}
}
// 如果查找到了匹配结点,开始删除操作
// 第一种情况,该结点是叶子结点,也就是左孩子和右孩子都为null
if(currentTreeNode.getLeftChild() == null && currentTreeNode.getRightChild() == null) {
// 如果该结点为root,则直接删除
if(currentTreeNode == root) {
root = null;
// 如果是在左子树中
}else if(isLeftChild) {
// 直接将父结点的左孩子引用设为null
parrentTreeNode.setLeftChild(null);
}else {
// 如果在右孩子中,直接将父结点的右孩子引用设为null
parrentTreeNode.setRightChild(null);
}
// 第二中情况,要删除的结点有一个子结点,左孩子或者右孩子
// 只有左孩子
}else if(currentTreeNode.getRightChild() == null) {
// 如果要删除的是root
if(currentTreeNode == root) {
// 将root引用直接指向要删除的结点的左孩子,因为这个时候它只有左孩子
root = currentTreeNode.getLeftChild();
// 如果在左子树中
}else if(isLeftChild) {
// 直接将父结点的左孩子引用指向要删除结点的左孩子
parrentTreeNode.setLeftChild(currentTreeNode.getLeftChild());
}else {
// 如果是在右子树中,直接将父结点的右孩子引用指向要删除的结点的右孩子
parrentTreeNode.setRightChild(currentTreeNode.getLeftChild());
}
// 只有右孩子
}else if(currentTreeNode.getLeftChild() == null) {
// 如果要删除的是root
if(currentTreeNode == root) {
// 直接将root的引用指向其右孩子
root = currentTreeNode.getRightChild();
}else if(isLeftChild) {
// 如果在左子树中,则直接将父结点的左孩子引用指向要删除结点的右孩子
parrentTreeNode.setLeftChild(currentTreeNode.getRightChild());
}else {
// 如果在右子树中,则直接将父结点的右孩子引用指向要删除结点的右孩子
parrentTreeNode.setRightChild(currentTreeNode.getRightChild());
}
}else {
// 第三种情况,要删除的结点有两个子结点
// 调用方法,获取要删除结点的中序后继结点
TreeNode successorTreeNode = getSuccessor(currentTreeNode);
// 如果要删除的是root
if(currentTreeNode == root) {
// 将root引用直接指向中序后继结点
root = successorTreeNode;
}else if(isLeftChild) {
// 如果在左子树中,将父结点的左孩子引用指向中序后继结点
parrentTreeNode.setLeftChild(successorTreeNode);
}else {
// 如果在右子树中,将父结点的右孩子引用指向中序后继结点
parrentTreeNode.setRightChild(successorTreeNode);
}
// 最后,将中序后继结点的左孩子引用指向要删除的结点的左孩子,这个时候
// 中序后继结点就完成了对要删除结点的彻底替换
successorTreeNode.setLeftChild(currentTreeNode.getLeftChild());
}
// 返回删除状态
return true;
}
}
测试部分代码:
package binarytree;
public class TestBinaryTree {
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();
tree.insertNode(10, "John Connor");
tree.insertNode(20, "Vincent Tian");
tree.insertNode(15, "Steve Cuper");
tree.insertNode(3, "Amily Yo");
tree.insertNode(6, "Zero Z");
tree.insertNode(18, "Sara Connor");
tree.insertNode(22, "Max Shannon");
tree.insertNode(56, "Ming Linx");
tree.insertNode(21, "Zash Cheng");
tree.insertNode(46, "Jams Pa");
tree.insertNode(47, "Maria Jams");
tree.insertNode(57, "Rean Grove");
tree.delTreeNode(22);
tree.preorderTraversal(tree.getRoot());
}
}
控制台输出结果:
10: John Connor
3: Amily Yo
6: Zero Z
20: Vincent Tian
15: Steve Cuper
18: Sara Connor
46: Jams Pa
21: Zash Cheng
56: Ming Linx
47: Maria Jams
57: Rean Grove
依照前序遍历的方法,可以发现22
这个结点被成功的删除了,而且也被46
这个中序后继结点替换了。