系列文章目录
文章目录
第2节 递归与二叉树
递归的概念及特性
- 直接或间接调用自身的方法,称为递归
- 递归两要素:(1)边界条件(2)推导公式
- 递归的优点:逻辑清晰,开发效率高(公式即代码)
- 递归的不足:(1)空间需求大(重复调用占用栈空间)(2)时间效率不高,并且可能有重复调用
例题1:阶乘函数
阶乘函数公式如下:
n
!
=
{
1
n
=
0
n
(
n
−
1
)
!
n
>
0
n!= \begin{cases} 1 \quad\quad\quad\quad n = 0\\ n(n-1)! \quad n>0 \end{cases}
n!={1n=0n(n−1)!n>0
根据公式可以直接写出递归代码:
static int fac(int n) {
if (n == 0) return 1;
else return n * fac(n - 1);
}
例题2:斐波那契数列
斐波那契数列为递推数列,每个数的值为前面两个数值之和,数列表示如下:
1 1 2 3 5 8 13 21...
斐波那契数列的函数定义如下:
f
(
n
)
=
{
1
n
≤
1
f
(
n
−
1
)
+
f
(
n
−
2
)
n
>
1
f(n)= \begin{cases} 1 \quad\quad\quad\quad\quad\quad\quad\quad n \leq 1\\ f(n-1) + f(n-2) \quad n>1 \end{cases}
f(n)={1n≤1f(n−1)+f(n−2)n>1
通过以上函数我们可以很直观地写出相应代码:
static int f(int n) {
if (n <= 1) return 1;
return f(n - 1) + f(n - 2);
}
为了更深一步地加深理解递归的执行机制,我们不妨自己动手“运行”一下这段程序,尝试写出f(4)时调用的次序,包括入栈顺序和出栈顺序
以下为验证入栈/出栈队列的测试代码:
import java.util.*;
public class Main {
// 演示用,分别储存入栈队列与出栈队列
static List<Integer> inStack = new ArrayList<>();
static List<Integer> outStack = new ArrayList<>();
static int fib(int n) {
inStack.add(n); // 在方法入口记录入栈
try {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
finally {
outStack.add(n); // 在方法结束前记录出栈
}
}
public static void main(String[] args) {
int ans = fib(4);
System.out.print("入栈次序:");
for (int i : inStack) System.out.print(" " + i);
System.out.println();
System.out.print("出栈次序:");
for (int i : outStack) System.out.print(" " + i);
System.out.println();
System.out.println("fib(4)=" + ans);
}
}
程序执行结果如下:
入栈次序: 4 3 2 1 0 1 2 1 0
出栈次序: 1 0 2 1 3 1 0 2 4
fib(4)=5
思考题:求N的二进制中1的个数?要求使用递归实现
解题思路:这里首先补充一些位运算的基础,位运算符&的运算规则如下:
1 & 1 = 0
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
进一步,我们总结出有以下公式:
a & 1 = a
a & a = a
a & 0 = 0
我们通过以上结论,推导出一个公式:
n & (n - 1): 表示将n的二进制最末尾的1消除(变为0)
利用该结论我们写出题解代码:
int countBits(int n) {
if (n == 0) return 0;
return 1 + countBits(n & (n - 1));
}
补充:关于n的二进制1的位数计算,Java框架早有内置的方法实现,直接调用Integer.bitCount(n)即可求解,Integer.bitCount的实现原理同学们可查看Java源代码之后研究学习。(提示:使用了分治思想)
二叉树知识点回顾
学习二叉树的意义
二叉树在数据结构与算法的学习中的意义非常重大,主要表现为二叉树在各个知识点中起着支撑和连接的作用,同时,二叉树也对我们培养工科逻辑思维能力非常有帮助。
树与二叉树的应用
树与二叉树的应用其实也很广泛,以下仅列举部分例子:
- TreeMap / TreeSet
- 底层实现:红黑树(一种二叉搜索树)
- PriorityQueue(优先队列)
- 底层实现:堆(基于完全二叉树)
- 哈夫曼编码
- 基于贪心法构建二叉树
- MySQL
- 索引底层实现原理:B+树
二叉树的遍历举例:
对于上面的斐波那契数列例子,fib(4)的解空间树如下:
对以上解空间树的前序和后序遍历如下:
前序遍历: 4 3 2 1 0 1 2 1 0
后序遍历: 1 0 2 1 3 1 0 2 4
我们发现以上结果恰好和前面递归做的入栈/出栈练习的答案是一样的,具体原因同学们可以思考一下并在评论区给出答案。这也说明我们学习的技能知识点都是有联系的,学习过程中要学会融会贯通,举一反三。
二叉搜索树(BST)
概述:
- 定义:二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树,满足以下性质:每个节点的值大于其左子树中所有节点的值,小于其右子树中所有节点的值。
- 用途:BST常用于实现动态查找、排序等操作,具有较高的效率和良好的稳定性。
二、节点信息
节点结构:
- BST的节点通常包括三个属性:值(value)、左子节点(left)、右子节点(right)。
- 节点结构:对于一棵健康的BST,平均插入和查找效率为
O(logN)
以下两个命题是等价的(即满足“当且仅当”/iff的关系):
- 一棵二叉树是二叉搜索树
- 对一棵二叉树的中序遍历满足升序排序
例如,以下这棵二叉树是二叉搜索树,其中序遍历为:123456789
完全二叉树
(首先弄清楚概念,完全二叉树并不是满二叉树)
- 满足:按层序遍历连续标号,没有空缺
- 对任意节点
k
:左=2k
,右=2k+1
,父=k / 2
- 完全二叉树与数组等价
- 完全二叉树应用举例:
- 堆排序(即优先队列的底层实现)
- 线段树(在算法竞赛中线段树为核心知识点)