4.2 二叉树
前面学过了树的基本概念,和树的先序与后序遍历。现在要学二叉树。二叉树是一种受限制的树,也是一种非常有应用价值的数据结构。
(1)二叉树的基本概念
二叉树(binary tree):一棵树,其中每个节点的子节点不超过2.
二叉树的平均深度为:O(根号N),而二叉查找树的平均深度只有O(logN)
(2)二叉树的实现
因为二叉树已经限制了子节点的个数,因此除了使用树的左孩子右兄弟存储法,还可以直接定义两个子节点。
class BinaryNode{
Object element;
BinaryNode left;//左孩子
BinaryNode rigth;//右孩子
}
我们习惯只花不是null的节点,但是实际中每棵树都有N+1个null链。
二叉树除了常见的应用于搜索,还可以用于编译器的设计。
(3)一个二叉树的例子---表达式树
在前面学习栈与队列的时候,就提到了表达式的计算,以及中缀表达式与后缀表达式的转换,当时使用的是栈来计算,现在可以使用二叉树来计算,这样更加的直观易懂并且操作方便。
在表达式树中,我们将叶子节点看做操作数,而其他节点则是操作符。二叉树对应的表达树中的操作符都是二元的操作符。
在这里首先要介绍二叉树的几种遍历方式:
①先(根)序遍历: 节点,左节点,右节点
②中序遍历:左节点,节点,右节点
③后序遍历:左节点,右节点,节点
④层序遍历:需要通过队列来一层一层遍历
首先给出二叉树的结构:
package datastruct; public class BinaryTree{ private static class BinaryNode { E data; BinaryNode left;//左节点 BinaryNode right;//右节点 BinaryNode(E d,BinaryNode l,BinaryNode r) { data = d; left=l; right=r; } } private BinaryNode root; BinaryTree(){ root=null; } BinaryTree(BinaryNode r) { root=r; } public static void main(String[] args){ BinaryTree bt = new BinaryTree (new BinaryNode ("+",null,null)); String exp = "1+(2+3)"; } }
因为一步一步构建二叉树太过麻烦,这里直接使用栈将一个常见的中缀表达式转换为二叉树,然后再在二叉树的基础上得到他的后缀和前缀表达式。
大体思路:
①利用一个栈,S2,分别存储操作数;
②采用一个变量,从头遍历字符串,该变量存储该字符。并提供给一个判别程序判别式操作符还是操作数;
③如果是操作数存入操作数栈S2,若为操作符,则建立一个新的BinaryNode,并且从操作数栈取出两个分别为左节点和右节点,再存入S2中
1 package datastruct; 2 3 import java.util.Iterator; 4 5 public class BinaryTree{ 6 private static class BinaryNode 7 { 8 E data; 9 BinaryNode left;//左节点 10 BinaryNode right;//右节点 11 BinaryNode(E d,BinaryNode r,BinaryNode l) { 12 data = d; 13 left=l; 14 right=r; 15 } 16 } 17 private BinaryNode root; 18 BinaryTree(){ 19 root=null; 20 } 21 BinaryTree(BinaryNode r) { 22 root=r; 23 } 24 public void printInOrder(BinaryNode bn){ 25 if(bn==null) 26 return ; 27 else if(bn.left==null&&bn.right==null){ 28 System.out.println((String)bn.data); 29 } 30 else { 31 System.out.println("("); 32 printInOrder(bn.left); 33 System.out.println((String)bn.data); 34 printInOrder(bn.right); 35 System.out.println(")"); 36 } 37 } 38 public void printPostOrder(BinaryNode bn){ 39 if(bn==null) 40 return ; 41 else { 42 printPostOrder(bn.left); 43 printPostOrder(bn.right); 44 System.out.println((String)bn.data); 45 } 46 } 47 public static void main(String[] args){ 48 String exps = "123-+";//1+(2+3) 49 MyStack > S2 =new MyStack >(); 50 //遍历,并处理字符 51 for(int i=0;i ){ 52 char p=exps.charAt(i); 53 switch(p){ 54 case '0': S2.push(new BinaryNode (p+"",null,null));break; 55 case '1': S2.push(new BinaryNode (p+"",null,null));break; 56 case '2': S2.push(new BinaryNode (p+"",null,null));break; 57 case '3': S2.push(new BinaryNode (p+"",null,null));break; 58 case '4': S2.push(new BinaryNode (p+"",null,null));break; 59 case '5': S2.push(new BinaryNode (p+"",null,null));break; 60 case '6': S2.push(new BinaryNode (p+"",null,null));break; 61 case '7': S2.push(new BinaryNode (p+"",null,null));break; 62 case '8': S2.push(new BinaryNode (p+"",null,null));break; 63 case '9': S2.push(new BinaryNode (p+"",null,null));break; 64 case '+': S2.push(new BinaryNode (p+"",S2.pop(),S2.pop()));break; 65 case '-': S2.push(new BinaryNode (p+"",S2.pop(),S2.pop()));break; 66 case '*': S2.push(new BinaryNode (p+"",S2.pop(),S2.pop()));break; 67 case '/': S2.push(new BinaryNode (p+"",S2.pop(),S2.pop()));break; 68 default: System.out.println("input error");break; 69 } 70 }//end for 71 BinaryTree bt = new BinaryTree (S2.pop()); 72 System.out.println("中序遍历:"); 73 bt.printInOrder(bt.root); 74 System.out.println("后序遍历:"); 75 bt.printPostOrder(bt.root); 76 } 77 }
我的天,前序遍历和后序遍历使用递归固然方便,但是递归调用的时候一定要注意递归函数是否正确。。。。。。。。。。。。。。。。。。。
可以看出表达式的中序遍历输出的就是中缀表达式,而后序输出的则是后缀表达式。本次构建二叉树采用的是后缀的形式,因为这种方式只需要简单的栈操作就可以实现表达式的解析。
4.3 二叉查找树
本书对二叉查找树主要讲解了它的基本数据结构,还有几个重要的方法:contain(查找二叉查找树中是否存在该元素);findMin,findMax用于查找树的最小元和最大元;insert (插入操作);remove(删除操作);最后对二叉查找树的性能进行了分析。下面我就在上面的二叉树基础上实现二叉查找树。
二叉查找树的性质:对于树中的每个节点,它的左子树所有元素都小于该节点的值,而右子树则都大于该节点的值;
二叉查找树的平均深度为:O(log N);
二叉查找树要求所有节点都是可以比较的,因此要使用接口Comparable。
(1)insert函数
函数功能:往二叉查找树中插入一个元素;
传入参数:插入的元素;
返回元素:二叉查找树的根;
难点:二叉查找树对每个节点大小有要求,因此需要找到合适的插入位置;如果树中已经存在该元素则不操作。
public void insert(E d,BinaryNodep){ //二叉查找树因为元素都是有序的,因此可以不用遍历所有元素 if(d.compareTo(p.data)==0){ //节点元素值等于d return ; } else if(d.compareTo(p.data)>0){ if(p.right == null ) p.right=new BinaryNode (d); else insert(d,p.right); } else if(p.left == null ) p.left=new BinaryNode (d); else insert(d,p.left); return ; }
这里我采用的递归的方法查找插入位置。可以利用方法的重载再定义一个insert(E d),这样就不需要在调用的时候还要输入根节点。
(2)contain函数
函数功能:查找二叉查找树中是否存在指定元素;
传入参数:待查找的元素;
返回元素:若存在则返回Ture,否则返回False。
1 public boolean contain(E d){ 2 return contain(d,root); 3 } 4 public boolean contain(E d,BinaryNodep){ 5 //查找二叉查找树是否存在该元素,若存在返回true,否则返回false; 6 if(p==null) return false; 7 while(p!=null){ 8 if(d.compareTo(p.data)==0) 9 return true; 10 else if(d.compareTo(p.data)>0){ 11 p=p.right; 12 }else 13 p=p.left; 14 } 15 return false; 16 17 }
这里原本打算用递归的方式,当该节点不是要查找的节点时就递归的查找子树是否存在该节点,可是因为函数必须有个布尔类型的返回值,所以没有成功。我想自己对递归的认识还不够吧,以后得加深着方面的认识了。。。
其实不用递归也可以很简单的实现,只要用while就可以了。
(3)findMin和findMax
这两个函数比较简单,就是找出二叉查找树的最小值和最大值。
大体思路:findMin:因为节点的左子树都比该节点小,因此只要找出树的最左侧的节点就可以了。同理findMax就是找出二叉查找树的最右边的节点;
public E findMin(){ return findMin(root); } public E findMin(BinaryNodep){ while(p.left!=null) p=p.left; return p.data; } public E findMax(){ return findMax(root); } public E findMax(BinaryNode p){ while(p.right!=null) p=p.right; return p.data; }
这里没的说的,就是一直找最左边和最右边的自己点。
(4)remove
函数功能:删除指定的元素;
传入参数:待输出的元素值;
输出元素:替代删除的元素的值;
这里我一开始对照算法树敲的,但是每次删除后再次打印的结果显示没有删除。后来发现书上的代码在删除叶子节点的时候只是将栈中指向堆中对象的值改变了,但是这对堆中的树结构完全没有印象。所以我在最后删除叶子节点的环节另外提取出来,再遍历一次删除,这样每次删除就多了一次遍历。但是这种操作也可以不用,因为通常的二叉查找树的节点中会包含前驱节点和后继节点,参考链表的删除则不必再次遍历。但是怎么说呢。。。我这样节约了空间啊。。。。。。(也或许是我看的不透,有用书上代码实现的还望在评论中指点出来(假装有人看))