第四课 树和二叉树

1 树的定义和相关概念

树节点至多有一个前驱节点,可以有多个后继节点,线性表节点至多有一个前驱,
但是至多有一个后继节点,这是树和线性表的主要区别

1.1 定义

定义:树(Tree)是n(n>=0)个结点的有限集T,T为空时称为空树,否则它满足如下两个条件:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)其余的结点可分为m(m>=0)个互不相交的子集T1,T2,T3…Tm,其中每个子集又是一棵树,
并称其为子树(Subtree)。树是递归结构,在树的定义中又用到了树的概念

1.2 树的相关概念

(1)树结点:包含一个数据元素及若干指向子树的分支;
(2)孩子结点:结点的子树的根称为该结点的孩子;
(3)双亲结点:B结点是A结点的孩子,则A结点是B结点的双亲;
(4)兄弟结点:同一双亲的孩子结点
(5)堂兄结点:同一层上结点,并且父节点是兄弟节点;
(6)结点层次:根结点的层定义为1;根的孩子为第二层结点,依此类推;
(7)树的高(深)度:树中最大的结点层--形容的是数的高度
(8)结点的度:结点子树的个数
(9)树的度: 树中最大的结点度。--形容的是数的宽度
(10)叶子结点:也叫终端结点,是度为0的结点;
(11)分枝结点:度不为0的结点(非终端结点);
(12)森林:互不相交的树集合,俩刻树以上;
(13)有序树:树中任意节点的子结点之间有顺序关系,如:家族树;
(14)无序树:树中任意节点的子结点之间没有顺序关系;
(15)满二叉树:一棵深度为k且由2的k次幂减1(k是指数)个结点的二叉树称为满二叉树图是一棵满二叉树,对结点进行了顺序编号
(16)完全二叉树:如果深度为k、有n个结点的二叉树中每个结点能够与深度为k的顺序编号的满二叉树从1到n标号的结点相对应(后面可能没了几个叶子节点,但是不能省去满二叉树的分支节点),
    则称这样的二叉树为完全二叉树,满二叉树是完全二叉树的特例。

2 二叉树

二叉树在树结构的应用中起着非常重要的作用,因为对二叉树的许多操作算法简单,
而任何树都可以与二叉树相互转换,这样就解决了树的存储结构及其运算中存在的复杂性。

2.1 定义

(1)二叉树是由n(n>=0)个结点的有限集合构成,此集合或者为空集,或者由一个根结点及
两棵互不相交的左右子树组成,并且左右子树都是二叉树。这也是一个递归定义。二
叉树可以是空集合,根可以有空的左子树或空的右子树。二叉树不是树的特殊情况,
它们是两个概念。

(2)二叉树结点的子树要区分左子树和右子树,即使只有一棵子树也要进行区分,说明它是
左子树,还是右子树。这是二叉树与树的最主要的差别。下图列出二叉树的5种基本形态:
1)空二叉树
2)根和空的左右子树
3)根和左子树
4)根和右子树
5) 根和左右子树

2.2 二叉树的性质

(1)性质1
在二叉树的第 i 层上至多有2的i-1次幂 个结点(i≥1,i-1是指数)

(2)性质2
深度为 k 的二叉树上至多含 2的k次幂减1 个结点(k≥1,k是指数,1是常数)

(3)性质3
对任何一棵二叉树,若它含有n0个叶子结点、n2 个度为 2 的结点,
则必存在关系式:n0 = n2+1

证明:n1为二叉树T中度为1的结点数
因为:二叉树中所有结点的度均小于或等于2
所以:其结点总数n=n0+n1+n2
又二叉树中,除根结点外,其余结点都只有一个分枝进入
设B为分枝总数,则n=B+1
又:分枝由度为1和度为2的结点射出,B=n1+2n2
于是,n=B+1=n1+2n2+1=n0+n1+n2
=》n0=n2+1

(4)性质4
具有 n 个结点的完全二叉树的深度为 [log2n]+1(2是底数,n是对数,1是常数)

证明:设完全二叉树的深度为k 则根据第二条性质得2k-1(指数是k-1,当只有根节点的时候取等号)≤ n <= 2k-1 (指数是k)
所以 2k-1(指数是k-1)≤ n <2k(指数是k),即 k-1 ≤ log2 n < k , 因为 k 只能是整数,因此,k =[log2n]+1

(5)性质5
如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点i(1<=i<=n),有:
(1)如果i=1,则结点i无双亲,是二叉树的根;如果i>1,则其双亲的编号是 i/2(整除)。
(2)n一定是右孩子节点的编号,如果双亲节点编号是i,那么,n=2i+1
(3)如果2i>n,无左孩子(此时i一定是爷爷的右孩子);否则,其左孩子是结点2i。
(4)如果2i+1>n,则结点i无右孩子;否则,其右孩子是结点2i+1。

2.3 二叉树的存储结构(重点)

2.3.1 顺序存储结构

(1)特点
【1】数组存储
【2】方便查询
【3】完全二叉树适合使用该结构
【4】对于数组索引i,满足:左子节点:2i+1,右子节点:2i+2,父节点:(i-1)/2,从上到
下从左到右节点的位置刚好是数组索引的位置,
所以完全二叉树比较适合,否则造成空间浪费
(2)缺点
对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在
一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只
有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺
序存储。显然,这种存储对于需增加许多空结点才能将一棵二叉树改造成为一棵完全
二叉树的存储时,会造成空间的大量浪费,不宜用顺序存储结构。最坏的情况是右单支树
,一棵深度为k的右单支树,只有k个结点,却需分配2k-1个存储单元。数组元素
可以只是存储数据,或者加上双亲和孩子的数组下标。

2.3.2 链式存储结构

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关
系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用
来给出该结点左孩子和右孩子所在的链结点的存储地址,这就是二叉链表;还可以存多一个
双亲节点的指针,此时称为三叉链表。尽管在二叉链表中无法由结点直接找到其双亲,但
由于二叉链表结构灵活,操作方便,对于一般情况的二叉树,甚至比顺序存储结构还节省
空间。因此,二叉链表是最常用的二叉树存储方式。对于二叉树链式存储结构,遍历的时候,
可以用递归的方法和非递归的方法。

(1)二叉链表节点(常用)
class TreeNode {
Object data;
TreeNode lchild, rchild;
(2) 三叉链表节点

二叉树的三叉链表存储表示
class TreeNode {
Object data;
TreeNode lchild,rchild,parent;
}

3 遍历二叉树和线索二叉树

3.1 遍历二叉树

遍历二叉树:使得每一个结点均被访问一次,而且仅被访问一次。遍历对线性结构是
容易解决的,而二叉树是非线性的,因而需要寻找一种规律, 以便使二叉树上的结点
能排列在一个线性队列上,从而便于遍历。假如以L、D、R分别表示代表遍历左子树、
遍历根结点和遍历右子树,遍历整个二叉树则有DLR、LDR、LRD、DRL、RDL、RLD六种
遍历方案。若规定先左后右,则只有前三种情况,分别规定为:DLR(data leftchild 
rightchild)——先(根)序遍历,LDR——中(根)序遍历,LRD——后(根)序遍历。

(1)先序遍历二叉树的操作定义为:
若二叉树不空,则:
1)访问根结点;
2)先序遍历左子树;
3)先序遍历右子树。

(2)中序遍历二叉树的操作定义为:
若二叉树不空,则:
1)中序遍历左子树;
2)访问根结点;
3)中序遍历右子树。

(3)后序遍历二叉树的操作定义为:
若二叉树不空,则:
1)后序遍历左子树;
2)后序遍历右子树;
3)访问根结点。

3.2 线索二叉树

(1)遍历是二叉树最基本最常用的操作
1)对二叉树所有结点做某种处理可在遍历过程中实现;
2)检索(查找)二叉树某个结点,可通过遍历实现;

(2)与线性表相比,对二叉树的遍历存在如下问题:
1)遍历算法要复杂的多,费时的多;
2)为检索或查找二叉树中某结点在某种遍历下的后继,必须从根开始遍历,
直到找到该结点及后继;如果能将二叉树线索化,就可以简化遍历算法,
提高遍历速度

(3)如何线索化
1)以中序遍历为例,若能将中序序列中每个结点前趋、后继信息保存起来,以后再遍历
二叉树时就可以根据所保存的结点前趋、后继信息对二叉树进行遍历。加上结点前趋
后继信息(结索)的二叉树称为线索二叉树

2)线索二叉树的存贮方法:
为每个结点增加二个指针域。缺点:要用较多的内存空间。

3)n个结点的二叉链表表示的二叉树,有n+1个空指针域(左或者右孩子为null的节点),
故可利用这些的空指针域存放结点的前趋和后继指针(A.J. Perlis,L.Thornton)以这种
结点的构成二叉链表称为线索链表

4)线索二叉树结构:
ltag|data|rtag
lchild|rchild
标志域tag为0时,表示指针域存储的是孩子的指针,标志域为1时,表示指针域存储的是
前趋/后继结点的指针

5)线索树下结点x的前驱与后继查找:
设结点x相应的左(右)标志是线索标志,则lchild(rchild)就是前驱(后继),否则:
【1】LDR
前驱:左子树中最靠右边的结点;
后继:右子树中最靠左边的结点

【2】LRD
前驱:右子树的根,若无右子树,为左子树根。
后继:
1】x是根,后继是空
2】x是双亲的右孩子、x是双亲的左孩子,但双亲无右孩子,双亲是后继
3】x是双亲的左孩子,双亲有右孩子双亲右子树中最左的叶子是后继

【3】DLR:对称于LRD线索树—将LRD中所有左右互换,前驱与后继互换,得到DLR的方法。

6)为简化线索链表的遍历算法,仿照线性链表,为线索链表加上一头结点约定:
【1】头结点的lchild域:存放线索链表的根结点指针;
【2】头结点的rchild域: 中序序列最后一个结点的指针;
【3】中序序列第一结点lchild域指向头结点中序序列,最后一个结点的rchild域指向头结点;

7)线索二叉树的遍历
在二叉树上加上结点前趋、后继线索后,可利用线索对二叉树进行遍历,此时,不需栈,也不需递归。
下面是线索链表的遍历算法。基本步骤:
【1】 p=T->lchild; p指向线索链表的根结点;
【2】若线索链表非空,循环:
1】循环,顺着p左孩子指针找到最左下结点;访问之;
2】若p所指结点的右孩子域为线索, p的右孩子结点即为后继结点循环: p=p->rchild;
并访问p所指结点;(在此循环中,顺着后继线索访问二叉树中的结点)
3】一旦线索“中断”,p所指结点的右孩子域为右孩子指针,p=p->rchild,使 p指向右孩子结点;

4 树和森林

4.1 树的存储结构

4.1.1 树节点的存储结构

比较常用的有双亲和孩子和兄弟表示方法,这写表示方式,不能死用,看具体的需求,
结合使用,比如,一个节点可以存储孩子和双亲的指针

(1)双亲表示法
实现:定义结构数组存放树的结点,每个结点含两个域:
数据域:存放结点本身信息
双亲域:指示本结点的双亲结点在数组中位置
特点:找双亲容易,找孩子难

(2)孩子表示法
多重链表:每个结点有多个指针域,分别指向其子树的根
结点同构:结点的指针个数相等,为树的度
结点不同构:结点指针个数不等,为该结点的度

(3)利用图表示树

(4)孩子兄弟表示法(二叉树表示法)
实现:用二叉链表作树的存储结构,链表中每个结点的两指针域分别指向其第一个孩子
结点和下一个兄弟结点
特点:操作容易,破坏了树的层次

4.1.2 数的存储结构

此部分主要看二叉树,二叉树做了详细的讲解

(1)顺序存储结构
用数组来存储节点,节点存的是数据和索引,是一个静态链表。放在一个固定大小的连续
空间,所以称为静态;通过节点的索引来访问后续节点,所以叫静态链表

(2)链式存储结构
用链表来存储,节点存的是数据和指针,是一个动态链表。放在一个个分散的空间中,并且
整个链表的空间大小不是固定的,根据需求和系统内存来分配,所以称为动态;通过节点的
指针来访问后续节点,所以称为动态链表

4.2 将树转换成二叉树

加线:在兄弟之间加一连线
抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系(兄弟的不可以去掉)
旋转:以树的根结点为轴心,将整树顺时针转45°

4.3.森林转换成二叉树

将各棵树分别转换成二叉树
将每棵树的根结点用线相连
以第一棵树根结点为二叉树的根

4.4 树的遍历

4.4.1 遍历

按一定规律走遍树的各个顶点,且使每一顶点仅被访问一次,即找一个完整
而有规律的走法,以得到树中所有结点的一个线性排列

4.4.2 常用方法

先根(序)遍历:若树不空,则先根访问树的根结点,然后依次先根遍历根的每棵子树
后根(序)遍历:若树不空,则先依次后根遍历每棵子树,然后访问根结点

4.4.3 结论

(1)转换后的二叉树的先序 对应树的先序遍历
(2)转换后的二叉树的中序 对应树的后序遍历
所以:树的后序遍历也称为树的中序遍历

4.5 二叉树的代码实现

4.5.1 二叉树的顺序存储结构

public class ArrayBinaryTree {
    int[] elements;
    public ArrayBinaryTree(int[] elements){
        this.elements = elements;
    }
    public void frontShow(){
        frontShow(0);
    }
    // 前序遍历
    public void frontShow(int index){
        if (elements == null || elements.length == 0){
            return;
        }
        // 先遍历当前节点的内容
        System.out.print(elements[index] + " ");
        // 处理左子树
        if (2*index+1 < elements.length){
            frontShow(2*index+1);
        }
        //处理右子树
        if (2*index+2 < elements.length){
            frontShow(2*index+2);
        }
    }
    // 中序遍历
    public void midShow(int index){
        if (elements == null || elements.length == 0){
            return;
        }
        // 左
        if (2*index+1 < elements.length){
            midShow(2*index+1);
        }
        // 根
        System.out.print(elements[index] + " ");
        // 右
        if (2*index+2 < elements.length){
            midShow(2*index+2);
        }
    }
    // 后序遍历
    public void afterShow(int index){
        if (elements == null || elements.length == 0){
            return;
        }
        // 左
        if (2*index+1 < elements.length){
            afterShow(2*index+1);
        }
        // 右
         if (2*index+2 < elements.length){
             afterShow(2*index+2);
         }
        // 根
        System.out.print(elements[index] + " ");
    }
}

4.5.2 二叉树的链式存储结构

package chapter6.binaryTree;

public class LinkedBinaryTree {
/**
 * 1.定义节点
 * 
 */
	private class  TreeNode{  
        private int  data;  //存储数据,这里假设是字符串,方便测试
        private TreeNode leftChild;  //存储左孩子
        private TreeNode rightChild;  //存储右孩子
          
        public TreeNode(){}  

        public TreeNode(int  data){  
            this.data=data;  
            this.leftChild=null;  
            this.rightChild=null;  
        }

		public int getData() {
			return data;
		}

		public void setData(int data) {
			this.data = data;
		}

		public TreeNode getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(TreeNode leftChild) {
			this.leftChild = leftChild;
		}

		public TreeNode getRightChild() {
			return rightChild;
		}

		public void setRightChild(TreeNode rightChild) {
			this.rightChild = rightChild;
		}  
     
    }  
/**
 * 2、定义成员变量      
 */
	 private TreeNode root;  //表示二叉树的根
/**
 * 3、定义构造方法
 */
	 public LinkedBinaryTree(){//创建空树
		 root = null ;
	 }
	 public LinkedBinaryTree(int data){  //只有根节点
	        root=new TreeNode(data);  
	    }  
       
	  /*判断是否是空树*/
	 private boolean isEmpty(){  
	        return root==null;  
	    }  
	 /**
	  * 4、查找
	  * 根据节点内元素的key查找,这里为方便书写,存的元素是一个整数,当做key
	  *   二叉树或者二叉子树中的数据的大小关系是:左孩子<根<右孩子
	  */
	 public TreeNode find(int key)   //find node with given key   
	    {  
	        TreeNode current = root; //代表当前要比较的节点   
	        while(current.data != key) {
	            if(key < current.data){
	                current = current.leftChild;  
	            }
	            if(key >current.data){
	                current = current.rightChild;  
	            }
	            if(current==null){//此时的current是左子数的根或者右子数的根
	            	return null ;
	            }
	        }  //从此循环出来有2钟可能,要么null,结束方法,要么data相等
	        return current;  
	    }
	 /**
	  * 5、插入
	  * 插入一个元素,是节点的数据区
	  *     二叉树或者二叉子树中的数据的大小关系是:左孩子<根<右孩子
	  */
	 public void insert(int  element)  
	    {  
	        TreeNode newNode = new TreeNode();    
	        newNode.data = element;  
	        if( root == null ){//如果是空树
	        	root = newNode;  
	        } else{
	        	TreeNode current = root;      
		         TreeNode parent;
		         while(true){
		        	 parent = current;  //current是变化的
		                if(element< current.data){//这个分支不执行的话执行下一次循环 
		                    current = current.leftChild;  
		                    if(current == null){  //这个分支执行的话,结束方法,插入成功,
		                    	//否则执行下一次循环
		                        parent.leftChild = newNode;  
		                        return ;  
		                    }  
		                } else {                
		                        current = current.rightChild;  
		                       if(current == null){ 
		                        parent.rightChild = newNode;  
		                        return ;  
		                        }  
		                } 
		         }
	        }                
	    }
	          
	/**
	 * 6、删除
	 * 此部分比较复杂,所以没时间的话可以先不看。
	 * 
	 * 根据节点内元素的key来删除,为方便书写,节点内的元素存的都是整数,所以直
	 * 接当做key;
	 * 
	 * 当要删除的节点有左右子树的时候,应该先找继任者
	 */
	 
	 /**
	  *找继任者来替代删除节点,因为继任者要比待删除的节点的左子树上的所有节点都大,
	  *比右子树上的节点都小才满足,所以可以有两种方案:
	  *(1)在左子树上找最大的
	  *(2)在右子树上找最小的(待删除节点的右子树中最左下角的才是最小的)
	  *这里采用第2种方案,并且找回来的继任者已经连好右子树,只是没有连好左子树
	  *
	  */
	 private TreeNode getSuccessor(TreeNode delNode) {  
	        TreeNode successorParent = null;  //表示继任者的双亲节点
	        TreeNode successor = null;  //表示继任者
	        TreeNode current = delNode.rightChild; //采用方案2,去待删除节点的右子树
	                                                                                        //找最小的
	        while(current != null) {  //此循环是找继任者                   
	            successorParent = successor;  //把上一次的继任者当双亲(下面要用到)
	            successor = current;  //把当前节点当继任者
	            current = current.leftChild; //待删除节点的右子树中最左下角的才是最小的,
	                                                                   //一直循环,如果没有左孩子了,那他就是最左下角
	                                                                   //就是最小的
	        }  
	      //继任者一定没有左子树,但是,继任者如果不是待删除节点的右孩子,
            //那么继任者可能还有右子树,继任者和右子树切断后,把该继任者的右子树
             //作为继任者的双亲的左孩子,代替继任者,因为继任者和他的孩子都比双亲小,
	         //所以应该作为双亲的左孩子。并且该继任者代替原来待删除节点。
	        //继任者也可能是待删除节点的右孩子,那么就不用做连接了
	        if (successor != delNode.rightChild)  { 
	            successorParent.leftChild = successor.rightChild;  
	            successor.rightChild = delNode.rightChild;  
	        }  //
	        return successor;  
	    }  
	 /*删除*/
	 public boolean delete(int key)      //delete node with given key   
	    {  
	       TreeNode current = root;  //保存的是待删除节点
	        TreeNode parent = null;  //保存的是待删除节点的父亲
	        boolean isLeftChild = true;//下面要用到,表示待删除节点是否是左孩子
	        //此循环是为了找到待删除的节点,其实可以利用查找方法find
	        while(current.data != key){  
	            parent = current;  
	            if(key < current.data)  
	            {  
	                isLeftChild = true;  
	                current = current.leftChild;  
	            }  
	            else  {    
	                isLeftChild = false;  
	                current = current.rightChild;  
	            }  
	            if(current == null){// 不相等,然后左边或者右边孩子都为空,那就是找不到 
	                return false;  
	            }
	        } 
	        //找到后,如果待删除节点没有左右孩子
	        if(current.leftChild == null && current.rightChild == null){  
	            if (current == root)  
	                root = null;  
	            else if(isLeftChild)  
	                parent.leftChild = null;  
	            else  
	                parent.rightChild = null;  
	        }  
	        //如果待删除的节点有左孩子,没有右孩子  
	        else if(current.rightChild == null) { 
	            if (current == root)  
	                root = current.leftChild;  
	            else if (isLeftChild)  
	                parent.leftChild = current.leftChild;  
	            else  
	                parent.rightChild = current.leftChild;  
	        }  
	        //如果待删除的节点有右孩子,没有左孩子  
	        else if (current.leftChild == null) { 
	            if (current == root)  
	                root = current.rightChild;  
	            else if(isLeftChild)  
	                parent.leftChild = current.rightChild;  
	            else  
	                parent.rightChild = current.rightChild;  
	        }  
	        //如果待删除的节点有左孩子和右孩子,那么就要在他的左孩子或者右孩子树
	        //找继任者,都可以,这里是去他的右子树找,找回来的继任者没有作者数,
	        //具体的实现去看方法getSuccessor()
	        else  
	        {  
	           TreeNode successor = getSuccessor(current);  
	            if(current == root)  
	                root = successor;  
	            else if (isLeftChild)  
	                parent.leftChild = successor;  
	            else   
	                parent.rightChild = successor;  

              //最后要把待删除节点的左孩子作为继任者的左孩子
	            successor.leftChild = current.leftChild;  
	        }
	        return true;  
	    }
     /**
      * 7.修改:比较简单,利用查找的方法,这里不讲
      */
	        
/**
 * 8、遍历树:
 * 前序遍历、中序遍历、后续遍历
 * 这里只是讲递归的,非递归的比较难理解,先不看
 */
	 /*8.1 前序遍历*/
	 public void preOrder(TreeNode subTree){  
	      if(subTree!=null){
	    	  System.out.print(subTree.data+",");
	    	  preOrder(subTree.leftChild);
	    	  preOrder(subTree.rightChild);
	      }
	    }  
	 /*8.2 中序遍历*/
	 public void inOrder(TreeNode subTree){  
		      if(subTree!=null){
		    	  inOrder(subTree.leftChild);
		    	  System.out.print(subTree.data+",");
		    	  inOrder(subTree.rightChild);
		      }
	    }  
	 /*8.3 后序遍历*/
	 public void postOrder(TreeNode subTree){  
		      if(subTree!=null){
		    	  postOrder(subTree.leftChild);
		    	  postOrder(subTree.rightChild);
		    	  System.out.print(subTree.data+",");
		      }
	    }  
	 /**
	  * 9.求树的高度
	  * 将问题一般化,用递归:
	  * (1)求左子树的高度
	  * (2)求右子树的高度
	  *   (3)比较左子树的高度和右子树的高度,取大者+1作为数的高度,算上数根
	  *   递归结束条件:
	  *   子树的根为空,子树的高度就是0
	  */
	 public  int height(TreeNode subTree){  
	        if(subTree==null)  
	            return 0;//递归结束:空树高度为0  
	        else{  
	            int i=height(subTree.leftChild);  
	            int j=height(subTree.rightChild);  
	            return (i<j)?(j+1):(i+1);  
	        }  
	    }  
	 /**
	  * 10.求数的总节点数:
	  *  将问题一般化,用递归:
	  *        (1)求左子数的总结点
	  *        (2)求右子数的总结点
	  *        (3) 树的总节点数=左子树的总节点+右子数的总结点+1,加1表示树根
	  * 递归结束条件:
	  *   子树的根为空,子树的总结点就是0
	  */
	public  int size(TreeNode subTree){  
	        if(subTree==null){  
	            return 0;  
	        }else{  
	            return 1+size(subTree.leftChild)  +size(subTree.rightChild);  
	        }  
	    }  
	/**
	 *11.找某个节点的双亲节点
	 * 将问题一般化,用递归:
	 *     在树的左子树搜索,找不到的话就去右子树搜索,把树本身也是当做子树
	 * 递归结束条件:
	 * (1)如果是待查找的节点是root,那么就返回null,表示没有双亲
	 * (2)如果子数是空,那么就返回null,表示没有双亲
	 * (3)如果待查找的节点是子树的左节点或者右节点,那么,子树的根就是待查找
	 *       的节点的双亲,返回子树的根
	 */
	 public TreeNode parent(TreeNode subTree,TreeNode element){ 
		 //如果是待查找的节点是root,那么就返回null,表示没有双亲
		    if(root==element)
		    	return null ;
		  //如果子数是空,那么就返回null,表示找不到
	        if(subTree==null)  
	            return null;  
	        //如果待查找的节点是子树的左节点或者右节点,那么,子树的根就是待查找 
	        //的节点的双亲,返回子树的根
	        if(subTree.leftChild==element||subTree.rightChild==element)
	            return subTree;          
	        TreeNode p;  
	        //现在左子树中找,如果左子树中没有找到,才到右子树去找  
	        if((p=parent(subTree.leftChild, element))!=null)  
	            //递归在左子树中搜索  
	            return p;  
	        else  
	            //递归在右子树中搜索  
	            return parent(subTree.rightChild, element);  
	    }  
	      
	 /**
	  * 12.树的销毁;让每个节点都为null,释放空间
	  * 在释放某个结点时,该结点的左右子树都已经释放,
	  * 所以应该采用后续遍历,当访问某个结点时将该结点的存储空间释放 
	  * 将问题一般化,采用递归
	  * 1)销毁树的左子树
	  * 2)销毁树的右子树
	  * 3)销毁树的根
	  * 递归的结束条件:
	  *    子树为空
	  * @param args
	  */
	 public void destroy(TreeNode subTree){  
	        //删除根为subTree的子树  
	        if(subTree!=null){  
	            //删除左子树  
	            destroy(subTree.leftChild);  
	            //删除右子树  
	            destroy(subTree.rightChild);  
	            //删除根结点  
	            subTree=null;  
	        }  
	    }  
	 //测试	 
     public static void main(String[] args){
    	 /*按照自己的方式构建二叉树*/
    	 System.out.println("创建二叉树(1,2,3,4,5,6),按照自己的方式创建二叉树:");
    	 LinkedBinaryTree t1 = new LinkedBinaryTree() ;
    	 TreeNode  n1= t1.new TreeNode();
    	 TreeNode  n2= t1.new TreeNode();
    	 TreeNode  n3= t1.new TreeNode();
    	 TreeNode  n4= t1.new TreeNode();
    	 TreeNode  n5= t1.new TreeNode();
    	 TreeNode  n6= t1.new TreeNode();
    	 n1.data = 1;
    	 n2.data = 2;
    	 n3.data = 3;
    	 n4.data = 4;
    	 n5.data = 5;
    	 n6.data = 6;
        n1.leftChild = n2 ;
        n1.rightChild = n3 ;
        n2.leftChild  = n4 ;
        n2.rightChild = n5 ;
        n3.rightChild = n6 ;
        t1.root = n1 ;
        System.out.println("*******(前序遍历)[1,2,4,5,3,6,]遍历*****************");  
        t1.preOrder(t1.root);  
        System.out.println("\n*******(中序遍历)[4,2,5,1,3,6,]遍历*****************");  
        t1.inOrder(t1.root);  
        System.out.println("\n*******(后序遍历)[4,5,2,6,3,1,]遍历*****************");  
        t1.postOrder(t1.root);  
        System.out.println("\n树的高度:"+t1.height(t1.root));
        System.out.println("树的总节点:"+t1.size(t1.root));
        TreeNode n = t1.new TreeNode();//测试找双亲用
        System.out.println("查找节点n4的双亲:"+((n =t1.parent(t1.root, n4))==null?null:n.data) );
        /*按照二叉树的插入方式构建二叉树*/
       System.out.println("销毁t1!");
        t1.destroy(t1.root);
   	 System.out.println("\n创建二叉树(10,8,11,7,9,12),按照自己的方式创建二叉树:");
   	 LinkedBinaryTree t2 = new LinkedBinaryTree() ;
   	 t2.insert(10);
     t2.insert(8);
     t2.insert(7);
     t2.insert(9);
     t2.insert(11) ;
     t2.insert(12);
       System.out.println("*******(前序遍历)[10,8,7,9,11,12,]遍历*****************");  
       t2.preOrder(t2.root);  
       System.out.println("\n*******(中序遍历)[7,8,9,10,11,12,]遍历*****************");  
       t2.inOrder(t2.root);  
       System.out.println("\n*******(后序遍历)[7,9,8,12,11,10,]遍历*****************");  
       t2.postOrder(t2.root);  
       System.out.println("\n是否包含10:"+t2.find(10).data) ;
       System.out.println("删除8:"+t2.delete(8));
       System.out.println("*******(前序遍历)[10,9,7,11,12,]遍历*****************");  
       t2.preOrder(t2.root);  
       System.out.println("\n销毁t2!");
       t2.destroy(t2.root);
     }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java封神之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值