20162316刘诚昊 2017-2018-2 《Java程序设计》第二次实验 树
实验链接:
实验二 树-1-实现二叉树:
实验要求:
参考教材p375,完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder)
用JUnit或自己编写驱动类对自己实现的LinkedBinaryTree进行测试,提交测试代码运行截图,要全屏,包含自己的学号信息
课下把代码推送到代码托管平台
实验过程:
构建的树:
实验一中完成简单的步骤我就不在此加以赘述。
- getRight:
public BinaryTree<T> getRight() {
if (root == null)
throw new EmptyCollectionException ("Get left operation "
+ "failed. The tree is empty.");
LinkedBinaryTree<T> result = new LinkedBinaryTree<T>();
result.root = root.getRight();
return result;
}
- contains:
public boolean contains(T target) throws ElementNotFoundException {
if (root == null)
throw new ElementNotFoundException("Find operation failed. "
+ "No such element in tree.");
boolean a = false;
BTNode<T> node = root.find(target);
if (node != null ){
a = true;
}
return a;
- isEmpty:
public boolean isEmpty() {
return (root == null);
}
- preorder:
public Iterator<T> preorder() {
ArrayIterator<T> iter = new ArrayIterator<T>();
if (root != null)
root.preorder (iter);
return iter;
}
- postorder:
public Iterator<T> postorder() {
ArrayIterator<T> iter = new ArrayIterator<T>();
if (root != null)
root.postorder (iter);
return iter;
}
我认为最难的地方在toString方法,一开始我调用levelorder方法,直接打印出后得到 [1,2,3,4,5,6,7,8]。后来问王老师后得知应当打印成就像书上这样:
也即当遇到空的地方应当留一个位置出来而非略过。
于是我采用这样的方法:
public String toString(){
BTNode<T> To = root; //将所要打印的树赋给一个新的节点,以避免后面的操作影响到原来的树
int B = 0;
String result = "";
ArrayList<BTNode> list = new ArrayList();
list.add(To); //将根加入到list队列中。
//假如这个树为空,则直接返回一个空串;否则将根的元素转入String类型,加入到将要返回的结果中。
if (To == null){
return result;
}
else {
result = To.getElement().toString();
}
//一个循环结构,每次循环遍历树的新一层节点,遍历list队列中的内容并在每次循环开始时创建一个新的数组L。
//每次循环开始在打印结果中加入一个“ | ”,以区分不同层数,更易理解。
while(B == 0){
ArrayList<BTNode> L = new ArrayList();
result = result + " |";
String qaz = "";
//当遍历遇到空节点,在数组L中加入两个空节点。
//当遍历遇到非空节点,则将这个节点的左节点、右节点依次加入到数组L中,假若这个节点没有左(右)节点,便创建一个空的节点null,加入数组L中。
//每次在打印空的节点时,用“口”代替。
//把数组list的下一层节点的元素转化为String类型,再在下面判断是否加入打印的结果中。
for(int c = 0; c<list.size();c++){
if (list.get(c) == null){
qaz = qaz + " 口" + " 口";
L.add(null);
L.add(null);
}
else {
if (list.get(c).getLeft() == null) {
qaz = qaz + " 口";
L.add(null);
} else {
L.add(list.get(c).getLeft());
qaz = qaz + " " + list.get(c).getLeft().getElement();
}
if (list.get(c).getRight() == null) {
qaz = qaz + " 口";
L.add(null);
} else {
L.add(list.get(c).getRight());
qaz = qaz + " " + list.get(c).getRight().getElement();
}
}
}
//遍历完一层以后得到下一层的节点,全部都在L中,此时把L赋给list,假若while循环未结束,则再次遍历list得到新的L。
list = L;
//创建一个等于零的int类型,检查在最新的这个数组中,是否全部元素都是null。
//假若不是,则把得到的新一层String类型加入到打印结果中,继续while循环。
//假若是,则循环结束,得到结果result。
int NUM = 0;
for(int num = 0; num<list.size(); num++){
if(list.get(num) == null)
NUM++;
if (NUM==list.size())
B =1;
}
if (B==0)
result = result + qaz;
}
return result;
}
以下是我的toString方法打印出的结果:
1 | 2 3 | 口 4 5 6 | 口 口 口 口 口 7 8 口 |
1-实现二叉树测试截图:
1-实现二叉树代码链接:
实验二 树-2-中序先序序列构造二叉树:
实验要求:
基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二㕚树的功能,比如教材P372,给出HDIBEMJNAFCKGL和ABDHIEJMNCFGKL,构造出附图中的树
用JUnit或自己编写驱动类对自己实现的功能进行测试,提交测试代码运行截图,要全屏,包含自己的学号信息
课下把代码推送到代码托管平台
实验过程:
这个实验我独自完成还有些力不足,于是参考了网上的代码:http://blog.youkuaiyun.com/leiflyy/article/details/51100687
在此我先引用先该代码的大方向思路:
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}。
首先,根节点 是{ 1 };
左子树是:前序{ 2,4,7 } ,中序{ 4,7,2 };
右子树是:前序{ 3,5,6,8 } ,中序{ 5,3,8,6 };
这时,如果我们把左子树和右子树分别作为新的二叉树,则可以求出其根节点,左子树和右子树。
这样,一直用这个方式,就可以实现重建二叉树。
再是我再码云中提交的实现功能的代码并加以阐释:
public class reConstructBinaryTree {
//在此方法内输入先序、中序的数组。
//如果两者任意数组为空,返回为空。
//例如我现在输入的先序数组为{1,2,4,7,3,5,6,8},中序为{4,7,2,1,5,3,8,6}。
public BTNode reConstructBinaryTr(int [] pre, int [] in) {
if(pre == null || in == null){
return null;
}
BTNode qw = reCons(pre, in, 0, pre.length-1, 0, in.length-1);
return qw;
}
//主函数,用来构建树。
public BTNode reCons(int[] pre, int[] in, int preStart, int preEnd, int inStart, int inEnd) {
//建立一个节点,使用先序排列的第零个作为其元素。
BTNode tree = new BTNode(pre[preStart]);
tree.left = null;
tree.right = null;
//假若此时先序排列和中序排列中元素仅有一个,便就此返回,上面创立的节点便为叶。
if (preStart == preEnd && inStart == inEnd) {
return tree;
}
//这里的root用来计算子树的节点数。
//比如:
//以上我输入的先序数组的第零个为“1”,与中序的第四个元素相同,那么此时root为3.
//也即,接下来的左子树有3个节点,右子树则有4个节点(中序遍历的数组中,“1”后面有4个元素。
int root = 0;
for(root= inStart; root < inEnd; root++){
if (pre[preStart] == in[root]) {
break;
}
}
int leifLength = root - inStart;
int rightLength = inEnd - root;
//如果左子树的节点数大于0,开始递归构建左子树。右子树同样如此。
if (leifLength > 0) {
tree.left = reCons(pre, in, preStart+1, preStart+leifLength, inStart, root-1);
}
if (rightLength > 0) {
tree.right = reCons(pre, in, preStart+1+leifLength, preEnd, root+1, inEnd);
}
return tree;
}
2-中序先序序列构造二叉树测试:
测试中仍然使用这棵树:
调用类中再中序遍历的操作,看是否与输入一致:
中序遍历代码:
public void preTraverseBinTree(BTNode node){
ArrayIterator iter = new ArrayIterator();
if (node != null)
node.preorder (iter);
System.out.println(iter);
}
测试截图:
2-中序先序序列构造二叉树代码链接:
实验二 树-3-决策树:
实验要求:
完成PP16.6:在称为20问的游戏中,一个人先想好一个物体,另一个人通过 yes-or-no 问题尝试判定这个物体是什么。目标是用尽可能少的问题判定出物体。设计并实现程序,利用决策树来玩20问游戏,基于一组预设的可能的物体。
提交测试代码运行截图,要全屏,包含自己的学号信息
课下把代码推送到代码托管平台
实验过程:
我设计的问题是关于猜测最喜欢的早餐主食:
这个实验很简单跟着书上走就行了。
树-3-决策树测试截图:
树-3-决策树代码链接:
实验二 树-4-表达式树:
实验要求:
完成PP16.8:第二章介绍过,表达式树可用来表示算术表达式。设计并实现程序,使用二叉树来表示表达式树。提供方法对数进行计算,得到表达式的结果。
提交测试代码运行截图,要全屏,包含自己的学号信息
课下把代码推送到代码托管平台
实验过程:
构建一棵表达式树:
构建以后用中序遍历得出中缀表达式,再用上个学期四则运算的代码转化为后缀并得出结果:
4-表达式树代码链接:
实验二 树-5-二叉查找树:
实验要求:
完成PP17.1:完成本章中LinkedBinarySearchTree类的实现。特别是实现findMin和findMax两个操作。
提交测试代码运行截图,要全屏,包含自己的学号信息
课下把代码推送到代码托管平台
实验过程:
- findMin:
先创一个节点node赋值为root;
当root为null,直接返回为null;
当root不为null时,进入while循环结构;
只要当前节点有左枝,便该左枝取代原node称为新的node;
直到没有左节点时,返回node的元素。
public T findMin() {
if(root == null){
return null;
}
BSTNode<T> miner = (BSTNode<T>) root;
while(miner.getLeft() != null) {
miner = (BSTNode<T>) miner.getLeft();
}
return miner.getElement();
}
- findMax类似。
实验二 树-5-二叉查找树测试:
实验二 树-5-二叉查找树代码链接:
实验二 树-6-红黑树分析:
实验要求:
参考 http://www.cnblogs.com/rocedu/p/7483915.html 对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果.
实验过程:
首先要了解红黑树是什么,这里引用360百科中有关红黑树的部分:
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的"红黑树"。
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
它在除了二叉查找树的性质外,还有:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。
在红黑树上只读操作不需要对用于二叉查找树的操作做出修改,因为它也是二叉查找树。但是,在插入和删除之后,红黑属性可能变得违规。恢复红黑属性需要少量(O(log n))的颜色变更(这在实践中是非常快速的)并且不超过三次树旋转(对于插入是两次)。这允许插入和删除保持为 O(log n) 次,但是它导致了非常复杂的操作。
如果插入根节点,那么性质2便会被破坏,这时便需要进行旋转:
- 左旋:
private void leftRotate(RBTNode<t> x) {
// 设置x的右孩子为y
RBTNode<t> y = x.right;
// 将 “y的左孩子” 设为 “x的右孩子”;
// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
x.right = y.left;
if (y.left != null)
y.left.parent = x;
// 将 “x的父亲” 设为 “y的父亲”
y.parent = x.parent;
if (x.parent == null) {
this.mRoot = y; // 如果 “x的父亲” 是空节点,则将y设为根节点
} else {
if (x.parent.left == x)
x.parent.left = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
else
x.parent.right = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
}
// 将 “x” 设为 “y的左孩子”
y.left = x;
// 将 “x的父节点” 设为 “y”
x.parent = y;
}
- 右旋:
private void rightRotate(RBTNode<t> y) {
// 设置x是当前节点的左孩子。
RBTNode<t> x = y.left;
// 将 “x的右孩子” 设为 “y的左孩子”;
// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
y.left = x.right;
if (x.right != null)
x.right.parent = y;
// 将 “y的父亲” 设为 “x的父亲”
x.parent = y.parent;
if (y.parent == null) {
this.mRoot = x; // 如果 “y的父亲” 是空节点,则将x设为根节点
} else {
if (y == y.parent.right)
y.parent.right = x; // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
else
y.parent.left = x; // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
}
// 将 “y” 设为 “x的右孩子”
x.right = y;
// 将 “y的父节点” 设为 “x”
y.parent = x;
}
参考资料:
360百科——红黑树:https://baike.so.com/doc/616923-653087.html
博客园——杭州.Mark:http://www.cnblogs.com/hzmark/archive/2012/12/31/Tree.html
红黑联盟——java红黑树实现原理:https://www.2cto.com/kf/201710/689628.html