自己动手实现一个简单的二叉树结构

自己动手实现一个简单的二叉树结构

二叉树(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这个中序后继结点替换了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉拌糖醋鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值