目录
树的基本概念
-
树的结点:存储数据项,结点中的边代表着实体与实体之间的逻辑关系
-
结点的路径:从根结点到该结点
-
路径的长度:该结点路径所包含的分支数目
-
结点的度:结点所拥有的子树数目
-
树的度:树中所有结点的度的最大值
-
结点的层次:根结点的层次为0,其余层次依次加1
-
树的深度:树中所有结点的层次最大值加1
-
有序树:树中各结点的所有子树之间从左到右有严格的次序关系,不能互换(左右子树不能互换)
二叉树
-
二叉树是有序树,左右结点不能互换
-
每一个结点最多有两个结点的树形结构
二叉树的分类
-
满二叉树:结点总数为(2^n-1),其中n为层数,等比数列求和
-
单分支树:都没有右子树或
-
完全二叉树:完全二叉树的全部结点(有n个),其逻辑结构与满二叉树的前n个结点的逻辑结构一致(次序相同)
一棵深度为k的有n个结点的 二叉树 ,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与 满二叉树 中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。.
二叉树的实现
package data_structure.tree; public class BinaryTree { Node head; public void f(Node head){ if(head == null){ return; } //1 f(head.left); //2 f(head.right); //3 } //递归思维:有两种对象——结点(输出值) 左右结点(继续左右递归遍历) //前序遍历 public void pre(Node head){ if(head == null){ return; } System.out.println(head.value); pre(head.left); pre(head.right); } //中序遍历 public void in(Node head){ if(head == null){ return; } in(head.left); System.out.println(head.value); in(head.right); } //后序遍历 public void pos(Node head){ if(head == null){ return; } pos(head.left); pos(head.right); System.out.println(head.value); } } class Node { int value; Node left; Node right; public Node(int value) { this.value = value; } }
二叉树的遍历
-
先序遍历:任何子树的处理顺序都是,先头结点,再左子树,再右子树
-
中序遍历:任何子树的处理顺序都是,先左子树,再头结点,再右子树
-
后序遍历:任何子树的处理顺序都是,先左子树,再右子树,再头结点
子树的概念需要注意:包含一个结点下的所有结点
有以下结论:
-
先序遍历从头结点开始,中序遍历与后序遍历从左下角开始——取决于 是否从左子树开始
深刻理解递归序
遍历的递归函数:
public void f(Node head){ if(head == null){ return; } //1 f(head.left); //2 f(head.right); //3 }
-
对于每一个结点:都会进入三次 f 函数
-
头结点:平均分散
-
第二层结点:中间隔3个
-
第三层结点(最后一层结点):三次挨着 f 位置中的1/2/3处挨着进行
-
前序遍历:在每个结点第一次出现的时刻打印
-
中序遍历:在每个结点第二次出现的时刻打印
-
后序遍历:在每个结点第三次出现的时刻打印
-
前序、后序、中序均是递归序加工处理的结果;
-
运用递归序——>可以使任何一个结点通过左子树再通过右子树最后回到结点本身再做处理
二叉树遍历的非递归实现
前序
使用栈来实现,有以下规则即可实现:
-
0)弹出即处理
-
1)如果有右子结点,压入右子结点
-
2)如果有左子结点,压入左子结点
/** * 思路:利用栈这个数据结构 * 先将树的根结点压入栈 * 利用while循环,结束条件是栈为空 * 1. 先序遍历:先从栈中将栈顶结点取出进行处理 * 2. 再判断取出的结点的右子树是否为空,加入栈 * 3. 再判断取出的结点的左子树是否为空,加入栈 * 如此循环1、2、3 */ public void preReverse2(){ LinkStack linkStack = new LinkStack(); linkStack.push(this.head); while(!linkStack.isEmpty()){ //取出随即打印 BinaryTreeNode head = (BinaryTreeNode)linkStack.pop(); System.out.println(head.value); //输出结点的值 //如果为叶子结点,这两步直接跳过,只会打印结点的存储的值 //关键在于:这是判断的从栈中取出的结点的子树,由于栈中结点本就存在一定规则的顺序,所以可以实现有序 if(head.right != null){ linkStack.push(head.right); } if(head.left != null){ linkStack.push(head.left); } } }
后序
/** * 非递归方式的后序遍历的实现 * 顺序是左、右、中 * 由于中在前较为简单(类似于前序遍历),用中、右、左的顺序进行遍历存入helpStack中 * 然后再逆序即可 */ public void posReverse2(){ if(head!=null) { LinkStack linkStack = new LinkStack(); LinkStack helpStack = new LinkStack(); linkStack.push(head); while(!linkStack.isEmpty()){ //取出随即打印 BinaryTreeNode pop = (BinaryTreeNode) linkStack.pop(); helpStack.push(pop.value); if (pop.left != null) { linkStack.push(pop.left); } if (pop.right != null) { linkStack.push(pop.right); } } helpStack.display(); } }
中序
-
-
当前子树(第一个是根结点)的整条左边界依次入栈(条件2:head结点不为空),将左边界入栈,可以保证先左再头
-
-
-
将head更新为栈顶结点(当前结点的左边界的左下结点),输出该结点的值(左)
-
-
-
将head更新为栈顶结点的右子结点,回到步骤1遍历左边界
-
/** * 非递归方式的中序遍历的实现 * 中序:先处理左子树,在处理结点的值,最后处理右子树 */ public void inReverse2(){ if(head!=null){ LinkStack linkStack = new LinkStack(); while(!linkStack.isEmpty() || head!=null){ if(head!=null){ //条件2 linkStack.push(head); head = head.left; }else { head = (BinaryTreeNode) linkStack.pop(); System.out.println(head.value); head = head.right; } } } }
二叉树遍历的应用
1. 查找结点
以下为错误的,直接搬用了先根遍历的操作
出现问题的原因:
-
先根遍历是没有返回值,只是将元素输出,调用的递归函数停止就停止了
-
而查找算法的函数是有返回值的
-
按照以下算法:就算向左向右查找到了,也没有任何的变量来接收结果,递归返回的结果就丢失了
//结点查找算法,错误的 public static BinaryTreeNode searchNodeWrong(BinaryTreeNode head, int x) { if (head != null) { if (head.value == x) { return head; }else { if (head.left != null) { searchNodeWrong(head.left, x); } if (head.right != null) { searchNodeWrong(head.right, x); } } } return null; }
改进方法,在递归函数前加 return :
public static BinaryTreeNode searchNodeWrong(BinaryTreeNode head, int x) { if (head != null) { if (head.value == x) { return head; }else { if (head.left != null) { return searchNodeWrong(head.left, x); //return } if (head.right != null) { return searchNodeWrong(head.right, x); //return } } } return null; }
但是仍然会出现问题:
-
如果要查找的值出现在右边,那么下面这部分代码只会返回null,对于同一级递归,就返回了null,阻碍了对右子树的搜索,出现了问题
if (head.left != null) { return searchNodeWrong(head.left, x); //return }
改正思路:
-
先不返回函数返回值,只有在左边没有搜索到时,才进入右边搜索(三元表达式)
/** * 搜索算法——针对一个元素只出现一次,元素具有唯一性 * @param head 要搜索的树的根结点 * @param x 匹配的元素 * @return 结点值 */ public static BinaryTreeNode searchNode(BinaryTreeNode head, int x) { if (head != null) { if (head.value == x) { return head; }else { BinaryTreeNode result = searchNode(head.left,x); return result!=null ? result : searchNode(head.right,x); } } return null; }
2. 统计结点个数
/** * 统计结点的个数,分治的思想 */ public static int countNode(BinaryTreeNode head) { int countLeft = 0; int countRight = 0; if (head != null) { if (head.left != null) { countLeft = countNode(head.left); } if (head.right != null) { countRight = countNode(head.right); } } return countLeft + countRight + 1; // //为节省空间 // int count = 0; // if (head != null) { // ++count; //根结点+1 // if (head.left != null) { // count += countNode(head.left); // } // if (head.right != null) { // count += countNode(head.right); // } // } // return count; }
采用递归模型的思路进行求解:
public static int countNode(BinaryTreeNode head) { if(head==null){ return 0; }else { return 1+countNode(head.right)+countNode(head.left); } }
3. 统计二叉树的深度
public static int getDepth(BinaryTreeNode head) { int depthLeft = 0; int depthRight = 0; if (head != null) { if (head.left != null) { depthLeft = getDepth(head.left); } if (head.right != null) { depthRight = getDepth(head.right); } return Math.max(depthLeft, depthRight) + 1; } else { return 0; } }
采用递归模型的思路进行求解:
public static int getDepth1(BinaryTreeNode head) {
if(head==null){
return 0;
}else if(head.right==null && head.left==null){
return 1;
}else {
return 1+Math.max(getDepth1(head.right),getDepth1(head.left));
}
}