一、树的基本概念
什么是树?
非线性的数据结构,由n(>=0)个有限节点组成的一个具有层次关系的集合。 空树:n==0 非空树n>0 特点:
a) 有一个特殊的点称为根节点,根节点没有前驱节点 b) 在树中除了根节点每个节点都有唯一的双亲节点,
c) 如果有n个节点,一定有n-1条边
**
- 节点的度:一个节点含有子树的个数,如上图A节点的度为6
- 树的度:一个树中最大的节点的度,如上图树的度为6
- 叶子节点或终端节点:度为零的节点(没有孩子)。如上图树的节点HIPQKLMN
- 双亲结点或父节点:如果一个节点有孩子,则这个节点为父节点。
- 子节点或孩子节点:一个节点含有子树的根节点 根节点:没有双亲的节点
- 节点的层次:从根开始,一般情况下根为第一层 书的高度或深度:树节点中的最大层次。如上图树的高度为4
- 非终端节点或分支节点:度不为零的节点
- 森林:由m(>=0)颗互不相交的树构成了森林
二、特殊的树——二叉树
基本概念:
**
二叉树是一个有限集合,可以为空,由根节点加上左右两颗分别称为左子树和右子树的二叉树组成
特殊的二叉树
满二叉树:每一层的节点都达到最大值,也就是如果由i层,则节点总数为2^(i-1)
完全二叉树:完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树
二叉树的性质
若规定根节点层数为一。则一棵非空二叉树的第i层上最多有 (i>0)个结点
若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0)
对于任何一颗二叉树,如果其叶节点的个数为n0,度为二的非叶节点的个数为n2,则n0=n2+1
具有n个结点的完全二叉树的深度k为 上取整
对于有n个结点的完全二叉树如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
i>0,双亲序号:(i-1)/2,
i=0,i为根节点,无双亲
2i+1<n, 左孩子序号2i+1
2i+2<n,右孩子序号2i+2
二叉树的存储
二叉树的基本操作
class TreeNode {
char value;//数据值
TreeNode left;//左孩子的引用
TreeNode right;//右孩子的引用
public TreeNode(char value) {
this.value = value;
this.left = null;
this.right = null;
}
}
前序遍历递归和非递归
前序遍历:根左右
递归
// 前序遍历
void preOrderTraversal(TreeNode root) {
//根左右
if(root==null) {
return;
}
System.out.print(root.value+" ");
preOrderTraversal(root.left);
preOrderTraversal(root.right);
}
非递归
//非递归前序遍历
void preOrderTraversal1(TreeNode root) {
if(root==null) {
return;
}
//不为空,让根入栈
Stack<TreeNode> s=new Stack<>();
s.push(root);
while(!s.empty()){
TreeNode node=s.peek();//取栈顶元素
//遍历,删除栈顶元素
System.out.print(node.value+" ");
s.pop();
//如果该右子树不为空,右子树入栈
if(node.right!=null) {
s.push(node.right);
}
//如果左子树不为空,左子树入栈
if(node.left!=null) {
s.push(node.left);
}
}
}
中序遍历递归和非递归
递归
//中序遍历
void inOrderTraversal(TreeNode root) {
//左根右
if (root==null) {
return;
}
inOrderTraversal(root.left);
System.out.print(root.value+" ");
inOrderTraversal(root.right);
}
//非递归中序
void inOrderTraversal1(TreeNode root) {
if(root==null) {
return;
}
Stack<TreeNode> s=new Stack<>();
TreeNode cur=root;
while(!s.empty()||cur!=null) {
//先以root为根找二叉树中最左侧的节点
while (cur != null) {
s.push(cur);
cur = cur.left;
}
//cur为空说明左子树已经遍历完,
//栈顶元素就是最左侧的节点
cur = s.peek();
System.out.print(cur.value + " ");
//返回最左边节点的上一层节点
s.pop();
//遍历该节点的右子树部分,
//不论右子树是否存在,将其当作一棵二叉树重复上面的步骤进行遍历
cur = cur.right;
}
}
后序遍历递归和非递归
递归
void postOrderTraversal(TreeNode root) {
//左右根
if (root==null) {
return;
}
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.print(root.value+" ");
}
非递归
//后续遍历非递归
void postOrderTraversal1(TreeNode root) {
if(root==null) {
return;
}
Stack<TreeNode> s=new Stack<>();
TreeNode cur=root;
TreeNode last=null;//标记根节点的右子树是否被遍历过,也就是刚刚遍历过的节点
while(cur!=null||!s.empty()) {
while (cur != null) { s.push(cur);
cur = cur.left;
}
TreeNode temp= s.peek();
if(temp.right==null||temp.right==last) {
s.pop();
System.out.print(temp.value + " ");
last=temp;
}else {
cur = temp.right;
}
}
}
其他操作
//求节点个数
int getSize(TreeNode root) {
if (root==null) {
return 0;
}
return getSize(root.left)+getSize(root.right)+1;
}
//叶子节点个数
int getleafSize(TreeNode root) {
if (root==null) {
return 0;
}
if(root.left==null&&root.right==null) {
return 1;
}
return getleafSize(root.left)+getleafSize(root.right);
}
// 子问题思路-求第 k 层结点个数
int getKLevelSize(TreeNode root,int k) {
if (root==null) {
return 0;
}
if (k==1) {
return 1;
}
return getKLevelSize(root.left,k-1)+getKLevelSize(root.right,k-1);
}
int height(TreeNode root) {
if (root==null) {
return 0;
}else {
int left=height(root.left);
int right=height(root.right);
return (right>left?right:left)+1;
}
}
// 查找 val 所在结点,没有找到返回 null
// 按照 根 -> 左子树 -> 右子树的顺序进行查找
// 一旦找到,立即返回,不需要继续在其他位置查找
TreeNode find(TreeNode root, char val) {
if (root==null) {
return null;
}
if (root.value==val) {
return root;
}
TreeNode r=find(root.left,val);
if (r!=null) {
return r;
}
r=find(root.right,val);
if (r!=null) {
return r;
}
return null;
}
//层序遍历
void levelOrderTraversal(TreeNode root) {
//如果是空树,直接返回
if (root==null) {
return;
}
//不为空,先让根入队
Queue<TreeNode> q=new LinkedList<>();
q.offer(root);
while(!q.isEmpty()) {
TreeNode node=q.poll();
System.out.print(node.value);
if(node.left!=null) {
q.offer(node.left);
}
if (node.right!=null) {
q.offer(node.right);
}
}
}
//判断一颗二叉树是不是完全二叉树
boolean isCompleteTree(TreeNode root) {
//如果为空,一定是完全二叉树
if(root==null) {
return true;
}
//不为空,找到第一个不饱和节点
//不饱和节点要么是叶子节点,要么他后面的节点没有孩子节点
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
boolean isLeafOrLeft=false;
while(!queue.isEmpty()) {
TreeNode cur=queue.poll();
if(isLeafOrLeft) {
//找到饱和节点后其后面所有节点不能有孩子
if (cur.left!=null||cur.right!=null) {
return false;
}
}else {
// 1. 按照层序遍历的方式找第一个不饱和节点(叶子,只有一个孩子的节点)
// cur节点的左右孩子均存在
if(null != cur.left && null != cur.right){
queue.offer(cur.left);
queue.offer(cur.right);
}
else if(null != cur.left){
// cur只有左孩子
queue.offer(cur.left);
isLeafOrLeft = true;
}
else if(null != cur.right){
// cur只有右孩子
return false;
}
else {
// cur是叶子节点
isLeafOrLeft = true;
}
}
}
return true;
}
//
// preorder: 前序遍历结果
// inorder: 中序遍历结果
// [left, right): 标记树中节点在inorder中的范围
int index = 0;
private TreeNode reBuilidTree(int[] preorder, int[] inorder, int left, int right){
if(index >= preorder.length && left >= right){
return null;
}
// 前序中找根节点
// 创建根节点
TreeNode root = new TreeNode((char) preorder[index]);
// 在中序遍历结果中找根的位置
// 确认根节点的左右子树的区间
int inrootIdx = left;
while(inrootIdx < right)
{
if(inorder[inrootIdx] == preorder[index])
break;
inrootIdx++;
}
++index;
// 递归创建根节点的左子树
// [left, inrootidx)
root.left = reBuilidTree(preorder, inorder, left, inrootIdx);
// 递归创建根节点的右子树
root.right = reBuilidTree(preorder, inorder, inrootIdx+1, right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
return reBuilidTree(preorder, inorder, 0, inorder.length);
}