详细图文——树与二叉树

       树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空的树中:(1)有且仅有有个特定的称为根(root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,……,Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
在这里插入图片描述
在这里插入图片描述
强调:

  • 一棵树至多一个根,不可能存在多个根结点。
  • 子树的个数没有限制,但是,子树之间一定是互不相交的。

树的相关概念

结点的度和树的度

  • 结点拥有子树数称为结点的度(数分叉);
  • 树的度是树内各结点的度的最大值。如上面的树的度为3。

结点类别

  • 度为0的结点称为叶子结点或者终端结点;
  • 度不为0的结点称为非终端结点或分支结点。除根结点外,分支结点也称为内部结点。

结点关系

在这里插入图片描述

结点层次与树的深度

       结点的层次(Level)从根开始定义器,根为第一层(不过有的从0开始,这不影响),根的孩子为第二层。树中结点的最大层次称为数的深度(depth)或高度。
在这里插入图片描述

其他

       如果将树中结点的各子树看成从左至右是有序的,不能互换的则该树为有序树,否则为无序树。问:3个结点组成的二叉树有几种形式?
在这里插入图片描述
       森林(forest)是m(m>=0)棵互不相交的树的集合。

二叉树

       二叉树(binary tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

特殊二叉树

  • 斜树
           顾名思义,斜树一定是倾斜的,分为左斜和右斜。斜树有很明显的特定,每一层只有一个结点,结点的个数和二叉树的深度相同。
  • 满二叉树
           在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
    在这里插入图片描述
  • 完全二叉树
           对一棵具有n个结点的二叉树按层编号,如果编号i的结点与同样深度的满二叉树中的编号为i的结点在二叉树中未知完全相同,则这棵二叉树称为完全二叉树。就是满二叉树的从右至左删除叶子结点生成的二叉树,并且如果右边有结点没删除不能删除左边的结点。满二叉树是完全二叉树,但是完全二叉树不一定是满二叉树。
    在这里插入图片描述

二叉树性质

  • 性质1:在二叉树的第i层至多有 2 i 1 2^{i-1} 个结点(i>=1)。
  • 性质2:深度为k的二叉树至多有 2 k 1 2^k-1 个结点(k>=1)。
  • 性质3:对任何一棵二叉树T,如果其终端结点数为 n 0 n_0 ,度为2的结点数为 n 2 n_2 ,则 n 0 = n 2 + 1 n_0=n_2+1
  • 性质4:具有n个结点的完全二叉树的深度为 l o g 2 n + 1 \lfloor log_2 n \rfloor+1
  • 性质5:如果对一棵有n个结点的完全二叉树(其深度为 l o g 2 n + 1 \lfloor log_2 n \rfloor+1 )的结点按层序编号(从第1层到第 l o g 2 n + 1 \lfloor log_2 n \rfloor+1 层,每层从左至右),对任一结点i有:
    • 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点 i / 2 \lfloor i/2 \rfloor
    • 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
    • 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

性质5用于创建完全二叉树。

二叉树遍历

先序遍历

       若树为空,则直接返回,否则从根结点开始,然后前序遍历左子树,在前序遍历右子树。如下图的前序遍历结果为:ABDGHCEIF。
在这里插入图片描述

中序遍历

       若树为空,则直接返回,否则首先从根结点开始(但是不访问),然后中序遍历左子树,再访问根结点,最后中序遍历右子树。如下图的中序遍历结果为:GDHBAEICF。
在这里插入图片描述

后序遍历

       若树为空,则直接返回,否则首先从根结点开始(但是不访问),然后后序遍历左子树,再后序遍历右子树,最后访问根结点。如下图的后序遍历结果为:GHDBIEFCA。
在这里插入图片描述

层序遍历

       若树为空,则直接返回,否则从树的第一层,也就是根结点开始访问,从上至下逐层遍历,在同一层中,从左至右对结点逐一访问。如下图的层序遍历结果为:ABCDEFGHI。
在这里插入图片描述

二叉树的创建

       为了后序的遍历操作我们先创建一个二叉树。因为现在我的二叉树是无规律的,所以只能自己指定左孩子,右孩子和父结点。
定义结点:

public class Node {
	public Node leftChild;//左孩子
	public Node rightChild;//右孩子
	public Node parentNode;//为了便于删除,我们也保存父节点的引用
	public int data;//数据域
	
	public Node(int i){
		this.data = i;
	}
	
	public void displayNode(){
		System.out.print(data+" ");
	}
}

二叉树类:

public class BinaryTree {
	public Node root=null;
	
	public BinaryTree(){}
	public BinaryTree(Node node){
		root = node;
	}
}

由于我们现在的这棵树没有规律,所以手动创建树。

public static void main(String[] args) {
	Node node1 = new Node('A');
	Node node2 = new Node('B');
	Node node3 = new Node('C');
	Node node4 = new Node('D');
	Node node5 = new Node('E');
	Node node6 = new Node('F');
	Node node7 = new Node('G');
	Node node8 = new Node('H');
	Node node9 = new Node('I');
	//由于我们现在的这棵树没有规律,所以手动创建
	node1.leftChild = node2;
	node2.parentNode = node1;
	node1.rightChild = node3;
	node3.parentNode = node1;
	node2.leftChild = node4;
	node4.parentNode = node2;
	node4.leftChild = node7;
	node7.parentNode = node4;
	node4.rightChild = node8;
	node8.parentNode = node4;
	node3.leftChild = node5;
	node5.parentNode = node3;
	node3.rightChild = node6;
	node6.parentNode = node3;
	node5.rightChild = node9;
	node9.parentNode = node5;
	
	BinaryTree tree = new BinaryTree(node1);
}

前序遍历代码

递归实现

public void preOrderTraverse(){
	System.out.println("先序遍历:");
	preOrderTraverse(root);
	System.out.println();
}
private void preOrderTraverse(Node root){
	if(root==null)
		return;
	root.displayNode();
	preOrderTraverse(root.leftChild);
	preOrderTraverse(root.rightChild);
}

非递归实现

public void preOrderTraverseByStack(){
	System.out.println("先序遍历:");
	preOrderTraverseByStack(root);
	System.out.println();
}
private void preOrderTraverseByStack(Node root){
	Stack<Node> s = new Stack<Node>();
	s.push(root);//首先将根结点入栈
	Node current = root;
	while (!s.empty()) {
		current = s.pop();
		if (current != null) {
			current.displayNode();
			//由于栈的FILO原则,先序遍历需要先左子树后右子树,所以这里需要将右子树先入栈
			s.push(current.rightChild);
			s.push(current.leftChild);
		}
	}
}

中序遍历代码

递归实现

public void inOrderTraverse(){
	System.out.println("中序遍历:");
	inOrderTraverse(root);
	System.out.println();
}
private void inOrderTraverse(Node root){
	if(root==null)
		return;
	inOrderTraverse(root.leftChild);
	root.displayNode();
	inOrderTraverse(root.rightChild);
}

非递归实现

public void inOrderTraverseByStack(){
	System.out.println("中序遍历:");
	inOrderTraverseByStack(root);
	System.out.println();
}
private void inOrderTraverseByStack(Node root){
	Stack<Node> s = new Stack<Node>();
	Node current = root;
	while(current!=null||!s.isEmpty()){
		//将左子树放入栈
		while(current!=null){
			s.push(current);
			current = current.leftChild;
		}
		current = s.pop();
		current.displayNode();//访问根结点
		current = current.rightChild;//遍历右子树,将右子树入栈
	}	
}

后序遍历代码

递归实现

public void postOrderTraverse(){
	System.out.println("后序遍历:");
	postOrderTraverse(root);
	System.out.println();
}
private void postOrderTraverse(Node root){
	if(root==null)
		return;
	postOrderTraverse(root.leftChild);//后序遍历左子树
	postOrderTraverse(root.rightChild);//后序遍历右子树
	root.displayNode();//最后访问根结点
}

非递归实现

public void postOrderTraverseByStack(){
	System.out.println("后序遍历:");
	postOrderTraverseByStack(root);
	System.out.println();
}
private void postOrderTraverseByStack(Node root){
	Stack<Node> s = new Stack<Node>();
	Node curr = root;
	Node last = null;//前一个已经访问的节点
	while(curr != null || !s.empty()){ // 栈空时结束    
        while(curr != null){// 一直向左走直到为空     
            s.push(curr);    
            curr = curr.leftChild;    
        }    
        curr = s.peek();  
        // 当前节点的右孩子如果为空或者已经被访问,则访问当前节点  
        if(curr.rightChild == null || curr.rightChild == last){     
            curr.displayNode();    
            last = curr;    
            s.pop();    
            curr = null;    
        }    
        else  
            curr = curr.rightChild;      // 否则访问右孩子  
    } 
}

层序遍历代码

层序遍历使用队列实现,实现过程如下图所示:
在这里插入图片描述

public void levelOrderTraverse(){
	System.out.println("层序遍历:");
	levelOrderTraverse(root);
	System.out.println();
}
private void levelOrderTraverse(Node root){
	Queue<Node> queue = new ArrayDeque<>();
	Node curr = root;
	while(curr!=null||!queue.isEmpty()){
		while(curr!=null){
			if(queue.isEmpty())//第一次需要放入根结点
				queue.add(curr);
			if(curr.leftChild!=null)
				queue.add(curr.leftChild);
			if(curr.rightChild!=null)
				queue.add(curr.rightChild);
			curr=null;
		}
		curr = queue.poll();
		curr.displayNode();
		curr = queue.peek();
	}
}

树是否为空

/**
* 判断树是否为空
 */
public boolean isEmpty(){
	return root==null;
}

获取树的深度

如果这棵树只有一个根结点,深度为1。否则树的深度为左子树深度和右子树深度的较大值+1。采用递归实现。

/**
* 获取树的深度
 */
public int getDeepth(){
	return getDeepth(root);
}
private int getDeepth(Node root){
	if(root==null)
		return 0;
	int left = getDeepth(root.leftChild);
	int right = getDeepth(root.rightChild);
	return left>=right?left+1:right+1;//左右子树深度较大值+1
}

获取树的结点数

/**
* 结点数目
 */
public int size(){
	return this.size(root);
}

private int size(Node root){
	if(root==null)
		return 0;
	int nl = size(root.leftChild);
	int nr = size(root.rightChild);
	return nl+nr+1;//树的总结点数为左子树结点数+右子树结点数+1(根结点)
}

获取叶子结点数

/**
* 获取叶子结点数
 */
public int numberOfLeafs(){
	return this.numberOfLeafs(root);
}
private int numberOfLeafs(Node root){
	if(root==null)
		return 0;//结点为null直接返回0
	if(root.leftChild==null&&root.rightChild==null)
		return 1;//如果结点没有左右结点,那么该结点就是叶子结点,返回1
	return numberOfLeafs(root.leftChild)+numberOfLeafs(root.rightChild);//该节点不是叶子结点,计算它的左右子树的叶子结点之和
}

获取度为2的结点数

/**
* 计算满结点(度为2)的个数
 */
public int numberOfFulls(){
	return this.numberOfFulls(root);
}
private int numberOfFulls(Node root){
	if(root==null)
		return 0;
	else if(root.leftChild==null&&root.rightChild==null)
		return 0;//叶子结点,不是满结点,直接返回0
	else if(root.leftChild!=null&&root.rightChild==null)
		return  numberOfFulls(root.leftChild);//左孩子不为空,右孩子为空,满结点可以存在左子树中
	else if(root.leftChild==null&&root.rightChild!=null)
		return numberOfFulls(root.rightChild);//左孩子为空,右孩子不为空,满结点可以存在右子树中
	else
		return 1+numberOfFulls(root.leftChild)+numberOfFulls(root.rightChild);//左右孩子都不为空,当前结点是满结点,并且它的左右子树中都可能存在满结点
}

根据二叉树性质3,代码也可以写为:

public int numberOfFulls(){
	return this.numberOfLeafs(root)-1;
}

查找指定结点

/**
* 查找指定值的结点
 */
public Node findValue(char value){
	return findValue(root,value);
}
private Node findValue(Node node,char value){
	if(node==null)
		return null;
	else if(node!=null&&node.data==value)
		return node;
	else{
		Node nodeL = this.findValue(node.leftChild,value);//未找到则从左子树查找
		Node nodeR = this.findValue(node.rightChild,value);//从右子树查找
		if(nodeL!=null&&nodeL.data == value)
			return nodeL;
		else if(nodeR!=null&&nodeR.data == value )
			return nodeR;
		else
			return null;
	}
}

二叉树镜像

在这里插入图片描述
       首先交换根结点的左右孩子(BC→CB),再分别以左右孩子为根结点执行同样的交换操作。

递归方法

/**
* 递归获取镜像
 */
public void getMirror(){
	this.getMirror(root);
}

private void getMirror(Node node){
	if(node==null)
		return;
	//交换左右孩子
	Node temp = node.leftChild;
	node.leftChild = node.rightChild;
	node.rightChild = temp;
	if(node.leftChild!=null)//以左孩子为根,翻转左子树
		getMirror(node.leftChild);
	if(node.rightChild!=null)
		getMirror(node.rightChild);
}

非递归方法

       按照先序遍历二叉树的顺序,每遇到一个节点,判断当前节点是否有孩子,如果有孩子,我们交换其左右孩子,然后把非空孩子入栈。直到栈为空。

/**
 * 非递归获取镜像
 */
public void getMirrorByStack(){
	this.getMirrorByStack(root);
}
private void getMirrorByStack(Node root) {
	Stack<Node> stack = new Stack<>();
	stack.push(root);
	while(!stack.isEmpty()){
		Node top = stack.pop();
		Node temp = top.leftChild;
		top.leftChild = top.rightChild;
		top.rightChild = temp;
		if(top.leftChild!=null)
			stack.push(top.leftChild);
		if(top.rightChild!=null)
			stack.push(top.rightChild);
	}
}
原创文章 234 获赞 1294 访问量 23万+

猜你喜欢

转载自blog.csdn.net/qq_25343557/article/details/83750319