Author: Mike
树的基本概念
基本概念
1 | 树的度—— 一棵树中最大的结点度数 |
形象表示
m叉树定义
每个结点的度数 <= m;
二叉树
于是二叉树有如下五种形态
二叉树的子树有左右之分,不能颠倒
特殊的二叉树
满二叉树
重点是 满
形象表示(图片中数字仅代表编号,不代表真实数值)
1.定义: 高度为h,并且含有(2^h)-1个结点的二叉树
2.对于编号为 i 的结点,如果有双亲,双亲为 i/2 向下取整;如果有左孩子,左孩子编号为 2i,如果有右孩子,右孩子编号为 (2i + 1)
完全二叉树
1.若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
2.如果有度为 1 的结点,那只可能有一个,且该节点只有左孩子,而无右孩子
满二叉树、完全二叉树、非完全二叉树对比
二叉排序树
形象表示
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。
定义
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则 左子树 上所有结点的值均 <= 它的 根结点 的值;
(2)若右子树不空,则 右子树 上所有结点的值均 >= 它的 根结点 的值;
(3)左、右子树也分别为二叉排序树 (递归定义);
平衡二叉树
形象表示
定义
平衡二叉树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的 左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,同时,平衡二叉树必定是二叉搜索树,反之则不一定
二叉树性质
性质1 在二叉树的第 i 层上至多有 2^(i-1)个 结点(i>=1)
用数学归纳法证明:
归纳基础:i=1时,有2^(i-1)=2^0=1。因为第1层上只有一个根结点,所以命题成立。
归纳假设:假设对所有的 j ( 1<=j < i ) 命题成立,即第j层上至多有 2^(j-1) 个结点,证明j=i时命题亦成立。
归纳步骤:根据归纳假设,第 i-1 层上至多有2^(i-2)个结点。由于二叉树的每个结点至多有两个孩子,故第 i 层上的结点数至多是第 i-1 层上的最大结点数的2倍。即 j=i 时,该层上至多有2×2^(i-2)=2^(i-1)个结点,故命题成立。
性质2 深度为k的二叉树至多有2^k-1个结点(k≥1)。
至多,即视之为满二叉树
证明:计算等比数列 2^0+2^1+…+2^(k-1)=2^k-1
性质3 在任意-棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则no=n2+1。
回顾 m叉树 的性质
- 设m叉树中,度数为 i 的结点树为 Ni, 则总结点数为: N = N0 + N1 + … + Nm;
- N = 分支数 + 1 , 1 为根结点
- 于是 N = N0 + N1 + …+ Nm = 0(N0) + 1(N1) + … + m*(Nm) + 1
- 对应于现在所讨论的二叉树,于是有 N = N0 + N1 + N2 = N1 + 2*(N2) + 1,于是等到结论 N0 = N2 + 1
详细证明
证明:因为二叉树中所有结点的度数均不大于2,所以结点总数(记为n)应等于0度结点数、1度结点(记为n1)和2度结点数之和:
n=no+n1+n2 (式子1)
另一方面,1度结点有一个孩子,2度结点有两个孩子,故二叉树中孩子结点总数是:
nl+2n2
树中只有根结点不是任何结点的孩子,故二叉树中的结点总数又可表示为:
n=n1+2n2+1 (式子2)
由式子1和式子2得到:
no=n2+1
二叉树遍历
二叉树的前中后序的遍历,坐标名词针对的是 根节点;无论何种遍历,左孩子永远比右孩子前
先序递归遍历 先访问根结点,再前序遍历左子树,最后前序遍历右子树。可见,这个操作的定义就是递归的
1
2
3
4
5
6
7
8public void pre_order_traversal_recursion_way(Node root) {
if (root == null) {
return;
}
out.print(root.data + " ");
pre_order_traversal_recursion_way(root.left);
pre_order_traversal_recursion_way(root.right);
}中序递归遍历 先中序遍历左子树,再访问根结点,最后中序遍历右子树。由于左子树上的值都比根结点小,右子树上的值都比根结点大,所以,中序遍历一棵树所得到的结果,是从小到大有序的,可以根据这个特点,来检验你的中序遍历是否正确实现了
1
2
3
4
5
6
7
8void mid_order_traversal_recursion_way(Node root){
if(root == null){
return;
}
mid_order_traversal_recursion_way(root.left);
out.print(root.data+" ");
mid_order_traversal_recursion_way(root.right);
}后序递归遍历 先后序遍历左子树,再后序遍历右子树,最后访问根结点
1
2
3
4
5
6
7
8void post_order_traversal_recursion_way(Node root){
if(root == null){
return;
}
post_order_traversal_recursion_way(root.left);
post_order_traversal_recursion_way(root.right);
out.print(root.data+" ");
}非递归先序遍历 先让根节点进栈,只要栈不为空,就可以做弹出操作;每次弹出一个结点,记得把它的左右结点都进栈。记得先右孩子,这样可以保证右子树在栈中总处于左孩子树的下面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void pre_order_traversal_not_recursion_way(Node root) throws StackEmptyException {
Stack<Node> stack = new Stack(100);
Node currentNode= root;
stack.push(currentNode);
while (!stack.isEmpty()){
currentNode = stack.pop();
out.print(currentNode.data + " ");
if(currentNode.right != null){
stack.push(currentNode.right);
}
if(currentNode.left != null){
stack.push(currentNode.left);
}
}
}
一直遍历到左子树最下边,边遍历边保存根节点到栈中
当p为空时,说明已经到达左子树最下边,这时需要出栈了
进入右子树,开始新的一轮左子树遍历(这是递归的自我实现)
1 | private void mid_order_traversal_not_recursion_way(Node root) throws StackEmptyException { |
- 层次遍历 队列,先进先出;每出一个结点,其左右孩子进队;依此类推
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private void level_order(Node root){
Queue<Node> queue = new LinkedList();
Node currentNode = root;
queue.add(currentNode);
while (!queue.isEmpty()){
currentNode = queue.poll();
out.print(currentNode.data+ " ");
if(currentNode.left != null){
queue.add(currentNode.left);
}
if(currentNode.right != null){
queue.add(currentNode.right);
}
}
}
数学归纳法:
1)当n=1时,显然成立.
2)假设当n=k时(把式中n换成k,写出来)成立,
则当n=k+1时,(这步比较困难,化简步骤往往繁琐)该式也成立.
由(1)(2)得,原命题对任意正整数均成立