Java面向对象的特性,方便我们在生活中将任何一个事务都抽象成对象来处理,茶余饭后调侃最多的就是给自己new girlFriend(18, beautiful, tall, slim)等等特性,而且还可以自己创造一个generatorFactory(GirlFriend friend)工厂,批量创建多个。总之这些都是说明了Java面向对象的灵活方便性;其实这也是Java面向对象的特征来决定的:封装、继承、多态;
树,常见的数据结构,我们先来看下对于树的定义:树(Tree)是 n(n≥0) 个结点的有限集T;即我们可以想到树可能是只有0个节点,那么即是空树,本篇不做讨论。当 n>1 时,其余结点可以划分为 m(m>0) 个互不相交的有限集T1、T2 、…、Tm,每个集 Ti(1≤i≤m) 均为树,且称为树T的子树(SubTree)。
以 下图的二叉树为例,二叉树:二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有2^{i-1}个结点;深度为k的二叉树至多有2^k-1个结点;对任何一棵二叉树T,如果其终端结点数为n_0,度为2的结点数为n_2,则n_0=n_2+1。
使用面向对象的特性,我们可以大概抽象出3个Element:lnode(左节点),rnode(右节点),data(当前节点值),这个对象的值取决于开发自己的定义,自己也可以加上树的度字段等;
BinaryTree {
char data;
BinaryTree lnode;
BinaryTree rnode;
}
对于常见的遍历问题:常见的前序遍历:首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根节点,然后遍历左子树,最后遍历右子树。
中序遍历:首先遍历左子树,然后访问左子树上一级的根结点,最后遍历当前左子树的右子树,依次遍历。
后续遍历:首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。
那么这样一来,通过Java实现普通二叉树的遍历就容易很多:具体的遍历流程可以通过递归来实现二叉树的遍历
package com.bes.test;
public class MyOwnTree {
static class BinaryTree {
private char data;
private BinaryTree lnode;
private BinaryTree rnode;
public BinaryTree(){
}
public BinaryTree(char data, BinaryTree lnode, BinaryTree rnode) {
this.data = data;
this.lnode = lnode;
this.rnode = rnode;
}
}
static BinaryTree createBinaryTree() {
BinaryTree l3 = new BinaryTree('G', null, null);
BinaryTree r3 = new BinaryTree('F', null, null);
BinaryTree l2 = new BinaryTree('D', l3, null);
BinaryTree r2 = new BinaryTree('E', null,null);
BinaryTree l1 = new BinaryTree('B', l2, r2);
BinaryTree r1 = new BinaryTree('C', null, r3);
BinaryTree T = new BinaryTree('A', l1, r1);
return T;
}
public static void main(String[] args) {
BinaryTree b1 = createBinaryTree();
System.out.println("前序遍历结果:");
preorderTraversal(b1);
BinaryTree b2 = createBinaryTree();
System.out.println("");
System.out.println("中序遍历结果:");
midorderTraversal(b2);
BinaryTree b3 = createBinaryTree();
System.out.println("");
System.out.println("后序遍历结果:");
suforderTraversal(b3);
}
private static void suforderTraversal(BinaryTree b3) {
if (b3 != null) {
suforderTraversal(b3.lnode);
suforderTraversal(b3.rnode);
System.out.print(b3.data);
}
}
private static void midorderTraversal(BinaryTree b2) {
if (b2 != null) {
midorderTraversal(b2.lnode);
System.out.print(b2.data);
midorderTraversal(b2.rnode);
}
}
private static void preorderTraversal(BinaryTree b1) {
System.out.print(b1.data);
if (b1.lnode != null) {
preorderTraversal(b1.lnode);
}
if (b1.rnode != null) {
preorderTraversal(b1.rnode);
}
}
}
最后很显然树的前序遍历结果:ABDGECF
...中序遍历结果:GDBEACF
...后序遍历结果:GDEBFCA
补充说明:
对于树的概念:优先了解下面几个常用的术语:
根节点(root): 树顶端的节点称之为根节点,也叫树根。
子树(subTree): 除根节点之外,其他节点可以分为多个树的集合,叫做子树,如图1中,B这个节点可以称之为一颗子树,而B、D、E、G四个节点组合起来也可以叫做一颗子树。
结点(node): 存储数据元素和指向子树的链接,由数据元素和构造数据元素之间关系的引用组成。
子结点: 树中一个结点的子树的根结点称为这个结点的子结点,如图1中的A的子结点有B、C。
双亲结点: 树中某个结点有孩子结点(即该结点的度不为0),该结点称为它孩子结点的双亲结点,也叫前驱结点。双亲结点和孩子结点是相互的,如图1中,A的孩子结点是B、C,B、C的双亲结点是A。
兄弟结点: 具有相同双亲结点(即同一个前驱)的结点称为兄弟结点,如图1中B、C为兄弟结点。
堂兄弟节点: 同一层,父节点不同,或者说双亲节点在同一层的节点称之为堂兄弟节点。如图1的E、F、D。
叶子结点: 度为0的结点称为叶子结点,也叫终端结点。如图1的E、F、G。
分支结点: 度不为0的结点称为分支结点,也叫非终端结点。如图1的A、B、C、D。
节点的祖先: 从根节点到某一节点一路顺下来的除了该节点的所有节点都是该节点的祖先节点。
节点的子孙: 以某节点为根的子树中,任何一个节点都是其子孙,也就是说这个节点下面与这个节点有关的节点都是这个节点的子孙。
结点的度: 结点所有子树的个数称为该结点的度,如图1,A的度为4,B的度为3。
树的度: 树中所有结点的度的最大值称为树的度,如图1的度为4。
结点的层次: 从根结点到树中某结点所经路径的分支数称为该结点的层次。根结点的层次一般为1(也可以自己定义为0),这样,其它结点的层次是其双亲结点的层次加1。
树的深度: 从根节点开始、自顶向下逐层累加(根节点的高度是1)助记:深度从上到下。
树的高度: 从叶节点开始、自底向上逐层累加(叶节点的高度是1)助记:高度由下向上。
有序树和无序树: 树中任意一个结点的各子树按从左到右是有序的,称为有序树,否则称为无序树。
森林: 由m棵不相交的树组成的集合,叫做森林。
数据元素: 具有相同特性的数据元素的集合。
结构关系: 树中数据元素间的结构关系由树的定义确定。