【202309】算法基础-L2-算法导论(2)

系列文章目录

算法基础-L1-算法导论(1)



第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(n1)!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)={1n1f(n1)+f(n2)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
  • 完全二叉树与数组等价
  • 完全二叉树应用举例:
    • 堆排序(即优先队列的底层实现)
    • 线段树(在算法竞赛中线段树为核心知识点)

完全二叉树举例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值