二叉树

1 递归写法:前序、中序、后序、90度旋转格式化打印二叉树用来熟悉递归算法与二叉树是怎么一回事,求叶子节点、求层数用来熟悉递归时候值传递(传递的值直接应用),值传递稍微有点不同的是用数组加空引用(比如String[] a = {"A","B","D",null,null,"E","G",null,null,null,"C","F"};)生成前序二叉树。

2 BST:排序二叉树,左最小根其次右最大,加入节点是一对一关系,所以每次从根节点开始循环就可生成,遍历时中序生成的就是排序好的。

3 二叉树非递归遍历:重点是保存本层现场和回退的时候要有标志指示代码从哪里开始继续执行。前序和中序因为访问根节点在右子树前,因此只要一个回退时是否访问左子树即可,也就是说进入右子树的时候没有保存根节点给本层的再次回退(保存也没有意义,因为访问本层之前做过了),并不是非常严格的栈实现递归算法。但后序一定要严格实现,同时保存根节点,同时得再加个标志位(之前访问了哪个节点)表示回退的时候是否访问右子树,也就是说2个标志,左是否进入(boolean型)和右是否进入(节点型存放之前访问过的一个节点)。

4吐槽的地方:java只能值传递,不能传地址,因此很多常规递归或者自实现递归遍历访问或者创建的时候,c++一般操作的是根节点,但java只能操作孩子节点,因为孩子节点的引用你传递下去是无意义的。由于值传递,不管下层孩子节点传到的引用怎么赋值,本层的孩子节点引用都是空。

代码:


package nuaa.ds;
//对于树来说,兄弟孩子表示法(还是二叉链表表示法)和二叉树的表示法一模一样
//左指针表示孩子,右指针表示兄弟
/**
 * 二叉树重要的是递归思路,自身发生一些行为,然后调用自身函数传递不同参数,意味着跑到
 *其他点上做相同的行为
 * 实际上二叉树遍历过程就是左节点一路到底,然后回退看是否存在右节点,访问代码位置的
 * 不同产生了所谓的pre inorder post
 */
public class BinaryTree<T> {
	
	private Node<T> root;

	public void traversal(VisitOrder order){
		switch(order){//switch语法和枚举是绝配
		case DLR:
			preTraversal(this.root);
			break;
		case LDR:
			inorderTraversal(this.root);
			break;
		case LRD:
			postTraversal(this.root);
		}
		
	}
	
	private void postTraversal(Node<T> root){
		if(root.left!=null){
			postTraversal(root.left);
		}
		if(null!=root.right){
			postTraversal(root.right);
		}
		visit(root);
	}
	private void inorderTraversal(Node<T> root){
		if(root.left!=null){
			inorderTraversal(root.left);
		}
		visit(root);
		if(root.right!=null){
			inorderTraversal(root.right);
		}
	}
	private void preTraversal(Node<T> root){
		visit(root);
		if(root.left!=null){
			preTraversal(root.left);
		}
		if(root.right!=null){
			preTraversal(root.right);
		}
	}
	
	/**
	 * visit函数从外部使用接口定义,
	 * 作为函子传递进来更合适。类似于排序的Comparator函子
	 * 这里简单起见
	 */
	private void visit(Node<T> root){
		System.out.println(root.data);
	}
	
	
	//求叶子节点个数
	public int getLeafNumber(){
		return getLeafNumber(this.root);
	}
	private int getLeafNumber(Node<T> root){
		if(root==null){
			return 0;
		}
		if(root.left==null&&root.right==null){
			return 1;
		}
		int leftNumber = getLeafNumber(root.left);
		int rightNumber = getLeafNumber(root.right);
		return leftNumber+rightNumber;
	}
	
	//求树的层数,层数从1开始表示
	public int getLayerNumber(){
		return this.getLayerNumber(this.root);
	}
	private int getLayerNumber(Node<T> root){
		if(root==null){
			return 0;
		}
		if(root.left==null&&root.right==null){
			return 1;
		}
		int leftLayerNumber = getLayerNumber(root.left);
		int rightLayerNumber = getLayerNumber(root.right);
		return (leftLayerNumber > rightLayerNumber?
									leftLayerNumber:rightLayerNumber)+1; 
	}
	
	//建立排序二叉树
	public void createBSTbyArray(T[] t,Comparator<T> c){
		if(t==null||t.length==0){
			throw new IllegalArgumentException("数组长度不能为0");
		}
		
		int index = 1;//数组的索引
		this.root = new Node<T>(t[0]);//保存根节点
		boolean left = true;//新插入的元素是否放左子树的标志位
		Node<T> iterator = root;//二叉树迭代元素标志
		while(index<t.length){
			while(true){
				if(c.compare(iterator.data, t[index])<0){
					if(iterator.left!=null){
						iterator = iterator.left;
					}
					else{
						break;
					}
				}else{
					if(iterator.right!=null){
						iterator = iterator.right;
					}
					else{
						left = false;
						break;
					}
				}
			}
			if(left){
				iterator.left = new Node<T>(t[index]);
			}else{
				iterator.right = new Node<T>(t[index]);
			}
			index++;
			iterator = root;
			left = true;
		}

			
	}
	
	/**
	 * 格式化打印二叉树,递归特性导致竖的直接打印不了,
	 * 必须存个数组保存对应序号再打印数组才能实现。
	 * 所以就打印个旋转90度的,采用逆中序
	 * 其实打印下来还是很丑。。只能看出个大概,根据层数判断谁连谁。。。
	 */
	public void printTree(){
		printTree(this.root,1);
		
	}
	private void printTree(Node<T> root,int layerNumber){
		if(null!=root.right){
			this.printTree(root.right, layerNumber+1);
		}
		
		for(int i=1;i<layerNumber;i++){
			System.out.print(" ");
		}
		this.visit(root);
		if(null!=root.left){
			this.printTree(root.left, layerNumber+1);
		}
		
	}
	
	//自建堆栈遍历二叉树
	public void traversalUsingStack(VisitOrder order){
		switch(order){
		case DLR:
			preTraversalUsingStack();
			break;
		case LRD:
			postTraversalUsingStack();
			break;
		case LDR:
			inorderTraversalUsingStack();
		}
	}
	
	//思路大于代码,想清楚递归过程更重要。
	private void preTraversalUsingStack(){
		if(this.root==null){
			System.out.println("二叉树为空");
			return;
		}	
		Stack<Node<T>> stack = new Stack<Node<T>>();
		Node<T> iterator  = this.root;//迭代因子
		stack.push(iterator);//这个只是为了满足循环条件
		boolean left = true;//是否访问左子树(标记位,表示从上层的哪里继续访问)
		
		while(!stack.isEmpty()){//遍历到最后重复放置的根时正好为空跳出循环
			while(left){//当左子树存在的时候并且应该访问左子树
				visit(iterator);//首先访问根节点
				if(iterator.left!=null){//左子树存在的话
					stack.push(iterator);//保留现场也就是存放根
					iterator = iterator.left;//等于递归调用自身,左子树的根作为新的参数
				}else{
					break;//左子树访问到底了等于第一个递归调用自身结束了就跳出来
				}
			}
			
			if(iterator.right!=null){//当前节点存在右子树
				iterator = iterator.right;//由右子树开始进入新一层的递归调用
				left = true;
			}else{
				iterator = stack.pop();//一直回退,直到找到
				left = false;//回退过程中不访问左子树,一直查找对应的根节点
			}                //是否存在右子树
		}
	}

	private void postTraversalUsingStack(){
		if(this.root==null){
			System.out.println("二叉树为空");
			return;
		}
		Node<T> iterator = this.root;
		Node<T> previous = null;
		Stack<Node<T>> stack = new Stack<Node<T>>();
		stack.push(iterator);
		boolean left = true;
		while(!stack.isEmpty()){
			while(iterator.left!=null&&left){
				stack.push(iterator);
				iterator = iterator.left;
			}
			if(iterator.right!=null&&(previous==null||!previous.equals(iterator.right))){
				stack.push(iterator);
				iterator = iterator.right;
				left = true;
			}else{
				
				visit(iterator);
				previous = iterator;
				iterator = stack.pop();
				left = false;
				
			}
		}
	}
	private void inorderTraversalUsingStack(){
		if(this.root==null){
			System.out.println("二叉树为空");
			return;
		}
		Node<T> iterator = this.root;
		Stack<Node<T>> stack = new Stack<Node<T>>();
		stack.push(iterator);//实际编程的trick,正好满足循环条件
		boolean left = true;
		while(!stack.isEmpty()){//多入栈的根节点出栈时候正好循环终止
			while(iterator.left!=null&&left){
				stack.push(iterator);
				iterator = iterator.left;
			}
			visit(iterator);
			if(iterator.right!=null){//右子树存在
				iterator = iterator.right;//右孩子作为根进入下层递归
				left = true;
			}else{//右孩子不存在
				iterator = stack.pop();//返回上层
				left = false;
			}
		}
	}
	
	//这函数意义其实不大,数组本身可以根据下标当成二叉树,练习而已
	//c在本层创建根节点,java在本层创建孩子节点
	public void createBTbyArray(T[] t,VisitOrder order){
		if(t.length==0){
			throw new IllegalArgumentException("数组为空");
		}
		switch(order){
		case DLR:
			this.root = new Node<T>(t[0]);//保留根
			preCreateByArray(t,1,this.root);
			break;
		case LDR:	
		case LRD:
			throw new IllegalArgumentException("中序和后序无法根据数组确定二叉树");
		}
	}
	private int preCreateByArray(T[] t,int index,Node<T> root){
		
		if(index<t.length&&null!=t[index]){
			root.left = new Node<T>(t[index]);
			index = preCreateByArray(t,index+1,root.left);
		}
		index = index+1;
		if(index<t.length&&null!=t[index]){
			root.right = new Node<T>(t[index]);
			index = preCreateByArray(t,index+1,root.right);
		}
		return index;	
	}
	
	
	private class Node<U>{
		private U data;
		private Node<U> left;
		private Node<U> right;
		
		//构造函数初始化内存,无需泛型
		//只有类泛型和函数泛型
		public Node(U t){
			this.data=t;
		}
		
	}
}

盲点: BST的平衡、加权二叉树典型应用比如哈夫曼,用数组表示,长的不像二叉树了,还没写。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值