前两篇博客介绍了两种设计模式的实现,本来按照预期,今天应该介绍第三种,可是我买的书还没到,想着等书到后再一并写完,那么今天就来说说树有关的内容吧。
学习树之前我们首先要知道什么是树,根据《数据结构与算法分析》里的解释,一棵树是一些节点的集合。这个集合可以为空集,如果不为空集,则树由称作根的节点root以及0或者多个非空子树组成。
因为是从算法入手,所以我们直接从二叉树开始。
定义:二叉树是一棵每个节点都不能有多于两个子节点的树。
如上图所示,如果忽略掉我的灵魂画手属性,这是一棵标准的二叉树。下面附上二叉树的节点创建定义:
public class RBTree {
//二叉树的值
private Object value;
//二叉树的键
private String key;
//左子节点
private RBTree LeftNode = null;
//右子节点
private RBTree RightNode = null;
//父节点
private RBTree parentNode = null;
//定义一个构造方法,给节点初始化
RBTree(Object value,String key){
this.value = value;
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public RBTree getLeftNode() {
return LeftNode;
}
public void setLeftNode(RBTree leftNode) {
LeftNode = leftNode;
}
public RBTree getRightNode() {
return RightNode;
}
public void setRightNode(RBTree rightNode) {
RightNode = rightNode;
}
public RBTree getParentNode() {
return parentNode;
}
public void setParentNode(RBTree parentNode) {
this.parentNode = parentNode;
}
}
好了,现在我们就可以通过重复创建上面的节点类来得到一棵二叉树,那么有了一个二叉树我们又能干什么,首先肯定是遍历这个二叉树,二叉树的遍历有先序,中序,和后续遍历三种遍历方式。
考虑到我的灵魂画手属性,这张图我是从网上找的,下面我会用这张图来分别演示二叉树的三种遍历方式。
先做准备工作,如图创建一个二叉树:
public class RTBtreeTest {
public static void main(String[] args) {
// 创建二叉树
RBTree I = new RBTree(8, "I");
RBTree H = new RBTree(4, "H");
RBTree G = new RBTree(2, "G");
RBTree F = new RBTree(7, "F");
RBTree E = new RBTree(5, "E");
RBTree D = new RBTree(1, "D");
RBTree C = new RBTree(9, "C");
RBTree B = new RBTree(3, "B");
RBTree root = new RBTree(6, "A");
root.setLeftNode(B);root.setRightNode(C);
B.setLeftNode(D);B.setRightNode(E);B.setParentNode(root);
C.setLeftNode(F);C.setParentNode(root);
D.setRightNode(G);D.setParentNode(B);
E.setLeftNode(H);E.setParentNode(B);
F.setRightNode(I);F.setParentNode(C);
G.setParentNode(D);
H.setParentNode(E);
I.setParentNode(F);
}
1.先序遍历
先序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
若二叉树为空则结束返回,否则:
(1)访问根结点;
(2)先序遍历左子树;
(3)先序遍历右子树 ;
//二叉树的先序遍历
public static void preTraversal(RBTree root) {
if(root == null) return;
System.out.print(root.getKey() + root.getValue().toString()+"..");
if(root.getLeftNode() != null) {
preTraversal(root.getLeftNode());
}
if(root.getRightNode() != null) {
preTraversal(root.getRightNode());
}
}
需要注意的是:遍历左右子树时仍然采用前序遍历方法。可以看出前序遍历后,遍历结果为:631254978
2.中序遍历
中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树。即:
若二叉树为空则结束返回,否则:
(1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树;
//二叉树的中序遍历
public static void inorderTraversal(RBTree root) {
if(root == null) return;
if(root.getLeftNode() != null) {
inorderTraversal(root.getLeftNode());
}
System.out.print(root.getKey() + root.getValue().toString()+"..");
if(root.getRightNode() != null) {
inorderTraversal(root.getRightNode());
}
}
注意的是:遍历左右子树时仍然采用中序遍历方法。最上图的二叉树用中序遍历的结果是:123456789
3.后续遍历
后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。即:
若二叉树为空则结束返回,否则:
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根结点;
//二叉树的后续遍历
public static void afterTraversal(RBTree root) {
if(root == null) return;
if(root.getLeftNode() != null) {
afterTraversal(root.getLeftNode());
}
if(root.getRightNode() != null) {
afterTraversal(root.getRightNode());
}
System.out.print(root.getKey() + root.getValue().toString()+"..");
}
如图所示的二叉树,用后序遍历的结果是:214538796
二叉树的遍历方法总结:其实二叉树的遍历就是一个简单的递归函数,从根节点开始不断的递归查询子节点。当然如果觉得递归难以理解也可以使用栈的形式来进行遍历,这里不加以赘述,因为本质上栈的先进后出属性就是递归。
好了,下面再介绍一个比较简单的二叉树ADT,即二叉查找树,我突然发现在介绍二叉树的三种遍历的时候用的图还是一棵完美的二叉查找树,那就再用一次吧,如下图:
从图片显而易见,二叉查找树的左节点永远比右节点小,嗯,二叉查找树还真就这一个性质!
定义
二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:
若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
若其右子树存在,则其右子树中每个节点的值都不小于该节点值。
今天时候不早了,就暂时附上二叉查找树的删除步骤吧,剩下的插入,和查找比较简单,以后有时间再补充,下面代码测试可行,有兴趣的小伙伴可以看看,详细解释和插入,查找一起有时间补上。
public class ADT {
public static void delete(RBTree node) {
//如果被删除节点没有子节点
if(node.getLeftNode() == null && node.getRightNode() == null) {
if(node.getParentNode().getLeftNode() == node) {
node.getParentNode().setLeftNode(null);
release(node);
}else {
node.getParentNode().setRightNode(null);
release(node);
}
}
//如果被删除字节只有左子节点
if(node.getLeftNode() != null && node.getRightNode() == null) {
//如果被删除节点是其父节点的左子节点
if(node.getParentNode().getLeftNode() == node) {
node.getParentNode().setLeftNode(node.getLeftNode());
node.getLeftNode().setParentNode(node.getParentNode());
release(node);
}else {
//如果被删除节点是其父节点的右子节点
node.getParentNode().setRightNode(node.getLeftNode());
node.getLeftNode().setParentNode(node.getParentNode());
release(node);
}
}
//如果被删除节点只有右子节点
if(node.getRightNode() != null && node.getLeftNode() == null) {
if(node.getParentNode().getLeftNode() == node) {
//如果被删除节点是其父节点的左子节点
node.getParentNode().setLeftNode(node.getRightNode());
node.getRightNode().setParentNode(node.getParentNode());
release(node);
}else {
//如果被删除节点是其父节点的右子节点
node.getParentNode().setRightNode(node.getRightNode());
node.getRightNode().setParentNode(node.getParentNode());
release(node);
}
}
//如果被删除节点有左子节点也有右子节点
if(node.getLeftNode() != null && node.getRightNode() != null) {
RBTree l = node.getLeftNode();
while(l.getRightNode() != null) {
l = l.getRightNode();
}
if(l.getLeftNode() == null) {
l.getParentNode().setRightNode(null);
}else if(l.getLeftNode() != null) {
l.getParentNode().setRightNode(l.getLeftNode());
l.getLeftNode().setParentNode(l.getParentNode());
}
l.setParentNode(node.getParentNode());
l.setLeftNode(node.getLeftNode());
l.setRightNode(node.getRightNode());
if(node.getParentNode().getLeftNode() == node) {
node.getParentNode().setLeftNode(l);
}else {
node.getParentNode().setRightNode(l);
}
node.getLeftNode().setParentNode(l);
node.getRightNode().setParentNode(l);
release(node);
}
}
//释放节点
private static void release(RBTree node) {
node.setLeftNode(null);
node.setParentNode(null);
node.setRightNode(null);
}
}