二叉树
今天我们来说一下二叉树,先提一点,hash里面的红黑树也是从二叉树演变而来的,这个我们以后再说,这就涉及到动态查找。
1.二叉树的几种形式
1.二叉树
2.满二叉树
一棵二叉树的结点要么是叶子结点,要么它有两个子结点(如果一个二叉树的层数为K,且结点总数是(2^k) -1,则它就是满二叉树。)
3.完全二叉树
若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。就是结点树为n的完全二叉树的结构要和满二叉树前你个结点的结构要一样。
4.哈夫曼树
树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
结合哈夫曼编码去理解:https://www.cnblogs.com/-citywall123/p/11297523.html
5.平衡二叉树
它或者是一颗空树,或它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。
2.二叉树的性质
-
二叉树结点的层次:规定根节点的层次为0,则其他结点的层次再从双亲父母结点层次+1;
-
二叉树的深度: 树中所有结点的层次树的最大值+1;
-
结点的度:该结点拥有子结点的个数;
-
二叉树中第i层(i>=0)层上的结点数最多为2的i次方;
-
深度为h(h>=1)的二叉树最多有2的h次方减1。
3.二叉树的遍历
经过二叉树的遍历,可以得到而二叉树结点的不同线性序列,将非线性结构的二叉树变为线性序列,从而可以确定二叉树中某个结点再某种遍历序列的前驱和后继。
1.先根遍历(DLR)
-
递归法
public void preRootTraverse(TreeNode root){ if(root!=null){ System.out.println(root.data); preRootTraverse(root.left); preRootTraverse(root.right); } }
-
非递归法
将递归法转换为为非递归法一般有两种,一种是直接转换法,不需要回溯,递归结果通过循环结构来代替,通过一些变量来保存中间结果,比如说双指针、斐波那契。还有一种就是间接转换法,需要回溯,可以利用队列、栈来保存中间结果。
递归法虽然写起来很方便,结构简洁,不过在时间和空间的开销比较大,所以我们借助栈来报存中间结果。
这里我们使用栈来保存右子树。过程如下:
(1)创建一个栈对象,让根结点入栈。
(2)当栈不为空的时候,将栈顶结点抛出栈并访问该结点。
(3)对当前访问结点的非空左孩子结点相继依次的访问,并将当前访问该结点的非空右孩子结点压入栈中。
(4)重复(2)、(3),直到栈为空。
public void preRootTraverse(TreeNode root){ if(root!=null){ LinkStack stack = new LinkStack;//构造栈 stack.push(root);//根结点入栈 while(!stack.isEmpty()){//判断栈是否为空 TreeNode node = stack.pop();//抛出栈顶元素,并返回其值 System.out.print(node.data);//访问上一步返回的结点 while(node!=null){ if(node.left!=null){//访问左孩子 System.out.print(node.left.data);//访问结点 } if(noded.right!=null){//访问右孩子,如果非空就入栈, stack.push(node.right); } node = node.right;//依次访问下一个左结点 } } } }
2.中根遍历(LDR)
-
递归法
public void inRootTraverse(TreeNode root){ if(root!=null){ preRootTraverse(root.left); System.out.println(root.data); preRootTraverse(root.right); } }
-
非递归
(1)创建一个栈对象,将根结点入栈。
(2)若栈非空,将栈顶元素的非空左孩子结点相继入栈。
(3)栈顶结点出栈,并访问非空栈顶接地那的非空右孩子结点入栈。
(4)重复(2)、(3)直到栈为空。
public void inRootTraverse(TreeNode root){ if(root!=null){ LinkStack stack = new LinkStack;//构造栈 stack.push(root);//根结点入栈 while(!stack.isEmpty()){//判断栈是否为空 while(stack.peek()!=null){//将栈顶结点的左孩子结点相继入栈 stack.push(((TreeNode)stack.peek()).left); } stack.pop();//空结点退栈 if(!stack.isEmpty()){ TreeNode node = stack.pop();//移除栈顶结点,并返回其值 System.out.print(node.data);//访问结点 stack.push(node.right);//结点的右孩子入栈 } } }
3.后根遍历(RDL)
-
递归法
public void postRootTraverse(TreeNode root){ if(root!=null){ preRootTraverse(root.left); preRootTraverse(root.right); System.out.println(root.data); } }
-
非递归
/** 非递归使用单栈实现二叉树后序遍历 */ public static void iterativePostOrder(BinaryTreeNode root) { Stack<BinaryTreeNode> stack = new Stack<>(); BinaryTreeNode node = root; // 访问根节点时判断其右子树是够被访问过 BinaryTreeNode preNode = null; while (node != null || stack.size() > 0) { // 把当前节点的左侧节点全部入栈 while (node != null) { stack.push(node); node = node.left; } if (stack.size() > 0) { BinaryTreeNode temp = stack.peek().right; // 一个根节点被访问的前提是:无右子树或右子树已被访问过 if (temp == null || temp == preNode) { node = stack.pop(); visit(node); preNode = node;// 记录刚被访问过的节点 node = null; } else { // 处理右子树 node = temp; } } } }
4.层次遍历
- 广度优先遍历(BFS)
(1)创建一个队列对象,根结点入栈;
(2)若队列为空,将队列首结点出堆,并访问该结点,再将该节点的非空左右孩子结点入队列。
(3)重复(2),直到队列为空。
public void levelTraverse(TreeNode root){
if(root!=null){
LinkQueue q = new LinkQueue;//构造队列
q.offer(root);//根结点入队列
while(!q.isEmpty()){//判断队列是否为空
TreeNode node = q.poll();//抛出队列首元素,并返回其值
System.out.print(node.data);//访问上一步返回的结点
while(node!=null){
if(node.left!=null){//访问左孩子,如果非空就入队列,
q.offer(node.left);
}
if(noded.right!=null){//访问右孩子,如果非空就入队列,
q.offer(node.right);
}
}
}
}
}
-
深度优先遍历(DFS)
就是前后中遍历
4.算法应用
1.二叉树上的查找算法
(1)若二叉树为空,则不存在这个节点,返回空值,否则,将根结点的值与x进行比较,若相等,返回该结点。
(2)若不相等,则在该结点的左子树中进行查找,若找到返回找到的结点。
(3)若在左子树中没有找到值为x的结点,就继续在右子树中进行查找,并返回查找的结点。
public TreeNode searchNode(TreeNode T,Object x){//运用的先根节点的递归查找
if(T!=null){
if(T.data.equals(x)){
return T;
else{
TreeNode Lresult = searchNode(T.left,x);
return lresult!=nuLL? lresult:searchNode(T.right,x);
}
}
}
return null;
}
2.二叉树中结点的个数
-
先根遍历的方法
public int countNode(TreeNode T){ int count = 0; if(T!=null){ count++; count+=countNode(T.left); count+=countNode(T.right); } return count; }
-
层次遍历的方法
public int countNode(TreeNode root){ int count = 0; if(root!=null){ LinkQueue q = new LinkQueue;//构造队列 q.offer(root);//根结点入队列 while(!q.isEmpty()){//判断队列是否为空 TreeNode node = q.poll();//抛出队列首元素,并返回其值 count++; while(node!=null){ if(node.left!=null){//访问左孩子,如果非空就入队列, q.offer(node.left); } if(noded.right!=null){//访问右孩子,如果非空就入队列, q.offer(node.right); } } } } }
-
递归法
publiv int countNode(TreeNode T){ if(T==null){ return 0; } else{ return countNode(T.left)+countNode(T.right)+1; } }
3.二叉树的深度
-
递归法
publiv int getDepth(TreeNode T){ if(T==null){ return 0; } else{ int lDepth = getDepth(T.left); int rDepth = getDepth(T.right); return Math.max(lDepth,rDepth)+1; } }
-
层次遍历(bfs)
class Solution { public int maxDepth(TreeNode root) { if (root == null) return 0; int depth = 0; Queue<TreeNode> q = new LinkedList<>(); q.add(root); while (!q.isEmpty()) { int size = q.size(); for (int i = 0; i < size; i++) { TreeNode tmp = q.poll(); if (tmp.left != null) { q.add(tmp.left); } if (tmp.right != null) { q.add(tmp.right); } } depth++; } return depth; } }
4.判断二叉树是否相等
-
递归法
public boolean isEqual(TreeNode T1,TreeNode T2){ if(T1=null&&T2==null){ return true; }else if(T1!=null&&T2!==null){ return(T1.data.equals(T2.data)&&(isEqual(T1.left,T2.left))&&(isEqual(T1.right,T2.right2)); }else return false; }
5.二叉树的建立
-
由先根遍历序列和中根遍历序列建立一颗二叉树
(1)先取先根遍历的第一个结点作为根结点;
(2)在中根遍历序列汇总寻找根结点,确定根结点在中根遍历序列中的位置,假设为i(0<=i<+=count-1),其中,count就是二叉树遍历序列中总结点的个数。
(3)在中根遍历序列中确定:根结点前的i个结点构成坐姿输的中根遍历序列,根结点后的count-1-i个结点构成右子树的中根遍历序列;
(4)在先根遍历序列中确定:根结点后i个结点序列构成左子树的先根遍历序列,剩下的count-i-1个结点序列构成右子树的先根遍历序列;
(5)由(3)、(4)又确定了左右子树的先根和中根遍历序列,接下来可以用同上述的方法来确定左、右子树的根结点,以此递归就可建立唯一的一课二叉树。
//声明参数:preOrder是整棵树的先根遍历序列,inOrder是整棵树的中根遍历序列,preIndex是先根遍历序列在preOrder中开始的位置,inIndex是中根遍历序列在inIndex中开始的位置,count便是树中结点的总个数。
public BiTree(String preOrder,String inOrder,int preIndex,int inIndex,int count){
if(count>0){
char r = preOrder.charAt(preIndex);
int i=0;
for(;i<count;i++){
if(r=inOrder.charAt(i+inIndex))
break;
}
root = new TreeNode(r);
root.left = new BiTree(preOrder,inOrder,preIndex+1,inIndex,i).root;
root.right = new BiTree(preOrder,inOrder,preIndex+1+i,inIndex+1+i,count-1-i).root;
}
}