zoukankan      html  css  js  c++  java
  • 第四章节 树(2)

    目录

    一、预备知识

    二、二叉树

    三、查找树ADT-----二叉查找树

    四、AVL树

    五、伸展树

    六、树的遍历

    七、B树

    八、标准库中的集合与映射

    九、二叉树的其它方法补充 

    五、伸展树

    六、树的遍历

    中序遍历:二叉查找树可以很方便的按顺序输出项。将元素插入一个二叉树,再中序遍历输出。

    后序遍历:有时我们要先处理两个叶子节点再处理当前节点。如为了计算一个节点的高度,我们要先知道两个儿子的高度。

    先序遍历:当前节点在儿子处理前处理。如想标记每一个节点的深度。

    所有的有一个共同点:先处理null情况 ,再处理其它的。

     非递归 参考 http://blog.csdn.net/hairongtian/article/details/7930937 ,http://blog.csdn.net/kofsky/article/details/2886453/

    /**
    	 * 先序遍历,先访问根,再左子树,再右子树
    	 */
    	private void preOder(BinaryNode<AnyType> t){
    		if (t != null){
    			System.out.println(t.element+"");
    			preOder(t.left) ;
    			preOder(t.right) ;
    		}
    	}
    	public void preOder(){
    		preOder(root);
    	}
    	/**
    	 * 中序遍历
    	 * 先遍历左子树,再根结点,再右子树
    	 */
    	private void inOrder(BinaryNode<AnyType> t){
    		if (t!= null){
    			inOrder(t.left) ;
    			System.out.println(t.element+" ");
    			inOrder(t.right) ;
    		}
    	}
    	public void inOrder(){
    		inOrder(root) ;
    	}
    	/**
    	 * 后序遍历
    	 * 先左子树,再右子树,再根
    	 */
    	private void postOrder(BinaryNode<AnyType> t ){
    		if (t!= null){
    			postOrder(t.left) ;
    			postOrder(t.right);
    			System.out.println(t.element) ;
    		}
    	}
    	public void postOrder(){
    		postOrder(root) ;
    	}
    

    如果是非递归 

    • 前序遍历

    优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:

         对于任一结点P:

         1)访问结点P,并将结点P入栈;

         2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;

         3)直到P为NULL并且栈为空,则遍历结束。

    public void preOder2(BinaryNode<AnyType> t ){
    		Stack<BinaryNode<AnyType>> s = new Stack<BinaryNode<AnyType>>() ;
    		while(t!= null || !s.empty()){
    			while(t!= null){
    				System.out.println(t.element) ;
    				s.push(t) ;
    				t = t.left ;
    			}
    			if(!s.empty()){
    				t = s.pop() ;
    				t = t.right ;
    			}
    		}
    	}
    • 中序遍历

    对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:

       对于任一结点P,

      1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;

      2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;

      3)直到P为NULL并且栈为空则遍历结束

    public void inOder2(BinaryNode<AnyType> t ){
    		Stack<BinaryNode<AnyType>> s = new Stack<BinaryNode<AnyType>>();
    		while(t!= null || !s.empty()){
    			while(t!= null){
    				s.push(t);
    				t = t.left ;
    			}
    			if (!s.isEmpty()){
    				t = s.pop() ;
    				System.out.println(t.element) ;
    				t = t.right ;
    			}
    		}
    	}
    

      

    •   后序遍历

    的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。

          第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。

    第二种思路:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

    public void postOrder2(BinaryNode<AnyType> t){
    		Stack<BinaryNode<AnyType >> s = new Stack<BinaryNode<AnyType>>() ;
    		Stack<Integer> s2 = new Stack<Integer>() ;
    		Integer i = new Integer(1) ;
    		while(t!=null|| !s.empty()){
    			while(t!=null){
    				s.push(t) ;
    				s2.push(new Integer(0)) ;
    				t = t.left;
    			}
    			while(!s.empty() && s2.peek().equals(i)){
    				s2.pop() ;
    				System.out.println(s.pop().element) ;
    			}
    			if (!s.empty()){
    				s2.pop();
    				s2.push(new Integer(1)) ;
    				t = s.peek() ;
    				t = t.right ;
    			}
    		}
    	}
    	
    

      

     

    七、B树

    基于这样的现实:对磁盘的访问开销很大。所以要尽可能减少磁盘的访问。

    由于典型的AVL树接近最优的高度,所以应该清楚,二叉树是不能使访问次数少于logN的。我们考虑M叉树。

    原则上B树保证只有少数的磁盘访问。阶为M的B树有下面的性质:

    1.数据项存储在树叶上。

    2.非叶子节点 存储直到M-1个关键字以指示搜索方向。

    3.树的根或者是一个树叶,或者其儿子数在2 - M之前 。

    4.除根外,所有 非树叶节点 数在M/2 - M之前 。

    5.所有的树叶都在相同 的深度并有L/2 - L之间个数据项.

    八、标准库中的集合与映射

    第三章节中的List容器,如ArrayList/ LinkedList用于查找时的效率很低,因此Collection API提供了两个附加的容器 Set /Map ,它们对插入,

    删除,查找等基本操作效率比较高。

    • 关于Set接口

    不允许重复,由SortedSet给出保证所有 的项处于有序的状态。

    Set的基本操作:插入,删除 有基本的查找。

    保持各项有序的状态的Set的实现是TreeSet。其基本操作花费O(logN)的最坏情况。

    • 关于Map接口

    一个接口,代表由关键字以及它们的值组成的一个集合。

    在SortedMap接口中,关键字保持逻辑上的有序,SortedMap的一种实现是TreeMap类。

    注意:有一种支持Map的操作,但不保证有序的数据结构,HashMap。

    Map基本操作:isEmpty, clear, size, 

    boolean contains(KeyType key)
    ValueType get(KeyType key)
    ValueType put(Keytype key, ValueType value )
    

    Map的迭代比Collection复杂 ,因为不提供迭代器,而是提供三种方法,将Map对象的视图作为 Collection返回。这些视图可以被迭代 。 

    Set<KeyType> keySet()
    Collection <ValueType> values()
    Set<Map.Entry<KeyType, ValueType>> entrySet()
    

    EntryMap对象结构如下:

    KeyType getKey ()
    ValueType getValue()
    ValueType setValue (ValueType value )
    

    TreeSet/ TreeMap的实现

    他们支持基本的add remove ,contains操作以对数最坏时间完成。所以基本的实现方法是平衡二叉查找树。一般我们不用AVL树,而是用自己顶向下的红黑树。

    实现TreeMap和 TreeSet的一个重要问题是提供对迭代器的支持。困难的是到下一个节点的高效推进。

    九、二叉树的其它方法补充  

    /**
    	 * 叶子树
    	 * @param t
    	 * @return
    	 */
    	public  int leafNum (BinaryNode<AnyType> t ){
    		if (t != null){
    			if (t.left== null && t.right== null){
    				return 1 ;
    			}
    			return leafNum(t.left)+ leafNum(t.right) ;
    		}
    		return 0;
    	}
    	
    	/**
    	 * 非根非叶子节点的高度是从叶子节点到它, 深度则是从根到它
    	 * 下面的应该是高度,但, 对root来说都是一样的
    	 * @param t
    	 * @return
    	 */
    	public int height( BinaryNode<AnyType> t){
    		int h1, h2 ;
    		if (t == null){
    			return 0;
    		}else {
    			h1 = height(t.right );
    			h2 = height(t.left) ;
    			return (h1>h2? h1:h2)+1;
    		}
    	}
    	/**
    	 * 层次遍历
    	 * @param t
    	 */
    	public void levelOrder(BinaryNode<AnyType> t ){
    		if (t == null)
    			return ;
    		Queue<BinaryNode<AnyType>> queue  = new ArrayDeque<BinaryNode<AnyType>>();
    		queue.add( t ) ;
    		while(!queue.isEmpty()){
    			BinaryNode<AnyType> tmp = queue.poll() ;//移除 一个元素
    			System.out.println(tmp.element) ;
    			if (tmp.left!= null){
    				queue.add(tmp.left) ;
    			}
    			if (tmp.right!= null){
    				queue.add(tmp.right) ;
    			}
    		}
    		
    	}
    

      

  • 相关阅读:
    Java设计模式之责任链模式
    多线程几个常用方法的实例
    Activiti工作流
    Java线程常用方法汇总
    Java线程的几个概念
    多线程——实现Callable接口
    java对象在JVM堆中的数据结构
    对计算机世界的认知
    wait、notify为什么要放在同步代码块中
    java synchronized关键字的底层实现
  • 原文地址:https://www.cnblogs.com/chuiyuan/p/4498640.html
Copyright © 2011-2022 走看看