(四):树

这是动脑学院的"java版数据结构和算法"的学习笔记

1 树的基本定义

  • 树(Tree)是n(n>=0)个结点的有限集。
  • n = 0时为空树
  • 在任意一棵非空树中:
    1. 有且仅有一个特定的称为根(root)的结点
    2. 当n>1时候,其余结点可分为m(m>0)个互不相交的有限集T1、T2、… Tm,
    3. 其中每一个集合本身又是一棵树,并且称为根的子树(subTree)

2 树和结点的度

  • 结点拥有的子树数称为结点的度
  • 度为0的结点称为叶子结点(或终端结点),度不为0的结点称为分支结点(或非终端结点)
  • 树的度是树内各结点的度的最大值
    结点的度

3 树层次与深度

  • 结点的层次(Level)从根开始定义,根为第一层,根的孩子为第二层。
  • 双亲在同一层的结点互为堂兄弟结点,如下图D,E,D,F互为堂兄弟,G,J,H,J,I,J也互为堂兄弟。
  • 树中结点的最大层次称为树的深度(Depth)或高度,当前树的深度为4层次与深度

4 有序树和无序树

  • 如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称为有序树,否则称为无序树。

5 森林

森林是m棵互不相交的树的集合

6 树的存储结构

  • 简单的顺序存储不能满足树的实现
  • 结合顺序存储和链式存储来实现

树
对于上面的树有下面几种表示方法

6.1 双亲表示法

取一块连续的内存空间,在存储每个结点时,附加一个parent字段表明其双亲结点的位置
结点的结构
对于下面的树,双亲法顺序存储结构进行表示

顺序结构表示树
当算法中需要在树结构中频繁地查找某结点的父结点时,使用双亲表示法最合适。当频繁地访问结点的孩子结点时,双亲表示法就很麻烦,采用孩子表示法就很简单。

6.2 孩子表示法

将树中的每个结点的孩子结点排列成一个线性表,用链表存储起来。对于含有 n 个结点的树来说,就会有 n 个单链表,将 n 个单链表的头指针存储在一个线性表中,这样的表示方法就是孩子表示法。在这里插入图片描述
使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点

6.3 孩子双亲表示法

孩子表示法和双亲表示法的结合。
在这里插入图片描述

6.4 孩子兄弟表示法
  • 任意一棵树它的结点的第一个孩子如果存在,那么就是唯一的
  • 对于任意一个结点,它的右兄弟存在也是唯一的。
  • 因此对于结点的数据结构,除了数据外,我们设置两个指针,分别指向该结点的第一个孩子此结点的右兄弟
    node结构在这里插入图片描述

7 二叉树

  • 二叉树(Binary Tree)是n个结点的有限集合
  • 该集合或者为空集,则称为空二叉树
  • 或者由一个根结点和两棵互不相交,分别称为根结点的左子树和右子树的二叉树组成。
7.1 斜树
  • 所有的结点都只有左子树的二叉树叫左斜树。
  • 所有结点都是只有右子树的二叉树叫右斜树。
  • 这两者统称为斜树。
              A                            A
         B                                        B
    C                                                 C
 D                                                         D
 左斜树                                   右斜树 

线性表结构可以理解为树的斜树表达

7.2 满二叉树

在一棵二叉树,如果所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树满二叉树

7.3 完全二叉树

对一棵具有n个节点的二叉树按层序编号,如果编号为i(1<=i<=n)与同样深度的满二叉树编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
完全二叉树

7.4 二叉树的性质
  1. 在二叉树的第i层上至多有2i-1个结点(i>=1)
  2. 深度为k的二叉树至多有2k-1个结点(k>=1)
  3. 对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1
  4. 具有n个结点的完全二叉树深度为[log2n]+1 ([a]表示不 大于 a的最大整数)
  5. 如果对一颗有n个结点的完全二叉树(其深度为[log2n]+1) 的结点按层序编号(从第1层到第[log2n]+1层,每层从左到右),对任意一个结点i(1<=i<=n)有
    • 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]
    • 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。

证明性质3:
设n为总结点数,n1为度为1的结点数,n2为度为2的结点数,n0为终端结点数。

分支线总数=n-1= n1 + 2n2。 总结点数n = n0 + n1 + n2
所以: n0 = n2 + 1

7.5 二叉树的顺序存储结构
  • 完全二叉树
    完全二叉树
    其存储结构为
    在这里插入图片描述
    完全二叉树,顺序存储在线性表中,则从线性表中存储的结点,能反推出的原来的二叉树。

  • 一般二叉树

一般二叉树,如果直接存储在线性表中,则从线性表中的结点顺序,不能反推出原来的二叉树,所以要把一般二叉树补全为完全二叉树然后进行插入。

一般二叉树补全为完全二叉树
D, F, H, I为补全的结点,此时一般二叉树的顺序存储为
在这里插入图片描述

7.6 二叉链表

结点除了数据之外,有一个左孩子指针,一个右孩子指针。
在这里插入图片描述
整个链表的结构为
在这里插入图片描述

7.7 二叉树的遍历
7.7.1 前序遍历
  • 若二叉树为空,则空操作返回
  • 否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
    前序遍历
    前序遍历结果为: A,B,D,G,H,C,E,I,F

c代码实现

void ProOrderTraverse(Tree T){
	if(T == null){
		return;
	}
	printf(%c”,T->data);
	ProOrderTraverse(T->lchild);
	ProOrderTraverse(T->rchild);
}
7.7.2 中序遍历
  • 若树为空,则空操作返回
  • 否则从根结点开始(注意并不是先访问根结点), 中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

中序遍历
中序遍历结果为: G,D,H,B,A,E,I,C,F

c代码实现

void ProOrderTraverse(Tree T){
	if(T == null){
		return;
	}
	ProOrderTraverse(T->lchild);
	printf(%c”,T-data);
	ProOrderTraverse(T->rchild);
}
7.7.3 后序遍历
  • 若树为空,则空操作返回
  • 否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点
    后续遍历
    后序遍历结果为: G,H,D,B,I,E,F,C,A

c代码实现

void ProOrderTraverse(Tree T){
	if(T == null){
		return;
	}
	ProOrderTraverse(T->lchild);
	ProOrderTraverse(T->rchild);
	printf(%c”,T-data);
}
7.7.4 层序遍历
  • 规则是若树为空,则空操作返回
  • 否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层,按从左到右的顺序对结点逐个访问

层序遍历
层序遍历结果: ABCDEFGHI

使用队列进行实现:

  1. 先将A入队,然后把A出队,然后把B,C入队
  2. 然后B出队,D入队。C出队,E,F入队
  3. D出队,G,H入队,E出队,I入队。
  4. F,G,H,I依次出队

出队顺序即为层序遍历结果: A,B,C,D,E,F,G,H

7.7.5 java实现前序,中序,后序遍历
  1. 定义结点
public class TreeNode{
		private int index;
		private String data;
		private TreeNode leftChild;
		private TreeNode rightChild;
		
	
		public int getIndex() {
			return index;
		}
		public void setIndex(int index) {
			this.index = index;
		}
		public String getData() {
			return data;
		}


		public void setData(String data) {
			this.data = data;
		}
 		public TreeNode(int index,String data){
			this.index = index;
			this.data = data;
			this.leftChild = null;
			this.rightChild = null;
		}
	}
  1. 创建BinaryTree
public class BinaryTree {
	private TreeNode  root = null;
	public BinaryTree(){
		root = new TreeNode(1, "A");
	}
	
	/**
	 * 构建二叉树
	 *         A
	 *     B       C
	 * D      E        F
	 */
	public void createBinaryTree(){
		TreeNode nodeB = new TreeNode(2, "B");
		TreeNode nodeC = new TreeNode(3, "C");
		TreeNode nodeD = new TreeNode(4, "D");
		TreeNode nodeE = new TreeNode(5, "E");
		TreeNode nodeF = new TreeNode(6, "F");
		root.leftChild = nodeB;
		root.rightChild = nodeC;
		nodeB.leftChild = nodeD;
		nodeB.rightChild = nodeE;
		nodeC.rightChild = nodeF;
	}
  1. 获取二叉树的高度
/**
	 * 求二叉树的高度
	 * @author Administrator
	 *
	 */
	public int getHeight(){
		return getHeight(root);
	}
	
	private int getHeight(TreeNode node) {
		if(node == null){
			return 0;
		}else{
			int i = getHeight(node.leftChild);
			int j = getHeight(node.rightChild);
			return (i<j)?j+1:i+1;
		}
	}

二叉树的高度=Max(左子树高度,右子树高度) + 1

  1. 获取二叉树结点数
/**
	 * 获取二叉树的结点数
	 * @author Administrator
	 *
	 */
	public int getSize(){
		return getSize(root);
	}	
	private int getSize(TreeNode node) {
		if(node == null){
			return 0;
		}else{
			return 1+getSize(node.leftChild)+getSize(node.rightChild);
		}
	}

树的总的结点数=左子树的结点数+右子树的结点数+1(根结点)

  1. 前序遍历二叉树
    /**
	 * 前序遍历——迭代
	 */
	public void preOrder(TreeNode node){
		if(node == null){
			return;
		}else{
			System.out.println("preOrder data:"+node.getData());
			preOrder(node.leftChild);
			preOrder(node.rightChild);
		}
	}
 
    /**
	 * 前序遍历——非迭代
	 */
	public void nonRecOrder(TreeNode node){
		if(node == null){
			return;
		}
		
		Stack<TreeNode> stack = new Stack<TreeNode>();
		stack.push(node);
		while(!stack.isEmpty()){
		    //弹出栈顶结点
			TreeNode n = stack.pop();
			//打印出栈顶结点
			System.out.println("nonRecOrder data"+n.getData());
			//先将右子结点入栈
			if(n.rightChild!=null){
				stack.push(n.rightChild);
			}
			//再将左边结点入栈,此时左边结点在栈顶,下次循环时,先左子节点出栈
			if(n.leftChild!=null){
				stack.push(n.leftChild);
			}
		}
	}
  1. 中序遍历
/**
	 * 中序遍历——迭代
	 * @author Administrator
	 *
	 */
	public void midOrder(TreeNode node){
		if(node == null){
			return;
		}else{
			midOrder(node.leftChild);
			System.out.println("midOrder data:"+node.getData());
			midOrder(node.rightChild);
		}
	}
  1. 后序遍历
/**
	 * 后序遍历——迭代
	 * @author Administrator
	 *
	 */
	public void postOrder(TreeNode node){
		if(node == null){
			return;
		}else{
			postOrder(node.leftChild);
			postOrder(node.rightChild);
			System.out.println("postOrder data:"+node.getData());
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值