数据结构与算法-递归

Gitee上开源的数据结构与算法代码库:数据结构与算法Gitee代码库

定义

  • 计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集
  • 比如单链表递归遍历的例子:
void f(Node node) {
    if(node == null) {
        return;
    }
    f(node.next);
}

说明:

  1. 自己调用自己,如果说每个函数对应着一种解决方案,自己调用自己意味着解决方案是一样的(有规律的)
  2. 每次调用,函数处理的数据会较上次缩减(子集),而且最后会缩减至无需继续递归
  3. 内层函数调用(子集处理)完成,外层函数才能算调用完成

思路

  1. 确定能否使用递归求解
  2. 推导出递推关系,即父问题与子问题的关系,以及递归的结束条件

例如之前遍历链表的递推关系为
f ( n ) = { 停止 n = n u l l f ( n . n e x t ) n ≠ n u l l f(n) = \begin{cases} 停止& n = null \\ f(n.next) & n \neq null \end{cases} f(n)={停止f(n.next)n=nulln=null

  • 深入到最里层叫做
  • 从最里层出来叫做
  • 的过程中,外层函数内的局部变量(以及方法参数)并未消失,的时候还可以用到

代码案例

1. 单路递归 Single Recursion

E01. 阶乘

用递归方法求阶乘

  • 阶乘的定义 n ! = 1 ⋅ 2 ⋅ 3 ⋯ ( n − 2 ) ⋅ ( n − 1 ) ⋅ n n!= 1⋅2⋅3⋯(n-2)⋅(n-1)⋅n n!=123(n2)(n1)n,其中 n n n 为自然数,当然 0 ! = 1 0! = 1 0!=1

  • 递推关系

f ( n ) = { 1 n = 1 n ∗ f ( n − 1 ) n > 1 f(n) = \begin{cases} 1 & n = 1\\ n * f(n-1) & n > 1 \end{cases} f(n)={1nf(n1)n=1n>1

代码

private static int f(int n) {
    if (n == 1) {
        return 1;
    }
    return n * f(n - 1);
}

拆解伪码如下,假设 n 初始值为 3

f(int n = 3) { // 解决不了,递
    return 3 * f(int n = 2) { // 解决不了,继续递
        return 2 * f(int n = 1) {
            if (n == 1) { // 可以解决, 开始归
                return 1;
            }
        }
    }
}

E02. 反向打印字符串

用递归反向打印字符串,n 为字符在整个字符串 str 中的索引位置

  • :n 从 0 开始,每次 n + 1,一直递到 n == str.length() - 1
  • :从 n == str.length() 开始归,从归打印,自然是逆序的

递推关系
f ( n ) = { 停止 n = s t r . l e n g t h ( ) f ( n + 1 ) 0 ≤ n ≤ s t r . l e n g t h ( ) − 1 f(n) = \begin{cases} 停止 & n = str.length() \\ f(n+1) & 0 \leq n \leq str.length() - 1 \end{cases} f(n)={停止f(n+1)n=str.length()0nstr.length()1
代码为

public static void reversePrint(String str, int index) {
    if (index == str.length()) {
        return;
    }
    reversePrint(str, index + 1);
    System.out.println(str.charAt(index));
}

拆解伪码如下,假设字符串为 “abc”

void reversePrint(String str, int index = 0) {
    void reversePrint(String str, int index = 1) {
        void reversePrint(String str, int index = 2) {
            void reversePrint(String str, int index = 3) { 
                if (index == str.length()) {
                    return; // 开始归
                }
            }
            System.out.println(str.charAt(index)); // 打印 c
        }
        System.out.println(str.charAt(index)); // 打印 b
    }
    System.out.println(str.charAt(index)); // 打印 a
}

E03. 递归-二分查找

/**
 * 递归-二分查找
 */
public class BinarySearch {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(search(nums, 5));
    }

    public static int search(int[] nums, int target){
        return f(nums, target, 0, nums.length - 1);
    }

    private static int f(int[] nums, int target, int left, int right){
        if (left > right){
            return -1;
        }
        int mid = (left + right) >>> 1;
        if (target < nums[mid]) {
            return f(nums, target, left, mid - 1);
        }else if(nums[mid] < target){
            return f(nums, target, mid + 1, right);
        }else {
            return mid;
        }
    }
}

E04. 递归-冒泡排序

  • 将数组划分成两部分[0 …j] [j+1 … a.length - 1]
  • 左边[0…j]是未排序部分
  • 右边[j+1 … a.length-1]是已排序部分
  • 未排序区间内,相邻的两个元素比较,如果前一个大于后一个,则交换位置
public class BubbleSort {

    public static void sort(int[] nums){
        bubble(nums, nums.length - 1 );
    }

    /**
     * @param nums 待排序数组
     * @param j 未排序的右边界
     */
    private static void bubble(int[] nums, int j){
        if (j == 0){
            return;
        }
        int x = 0;
        for (int i = 0; i < j; i++) {
            if (nums[i] > nums[i+1]){
                int temp = nums[i];
                nums[i] = nums[i+1];
                nums[i+1] = temp;
                x = i;
            }
        }
        bubble(nums, x);
    }

    @Test
    public void test(){
        int[] expected = {1, 2, 3, 4, 5};
        int[] nums = {5, 4, 3, 2, 1};
        BubbleSort.sort(nums);
        Assertions.assertArrayEquals(expected,nums);
    }
}

E05. 递归-插入排序

/**
 * 插入排序
 */
public class InsertionSort {

    public static void sort(int[] nums){
        insertion(nums, 1);
    }

    /**
     * 插入
     * @param nums 待排序数组
     * @param low 未排序区域的左边界
     */
    private static void insertion(int[] nums, int low){
        if (low == nums.length){
            return;
        }

        int temp = nums[low];
        int i = low - 1; // 已排序区域的指针

        while(i >= 0 && nums[i] > temp){ // 没有找到插入位置一直循环
            nums[i + 1] = nums[i]; // 空出插入位置
            i--;

        }
        // 找到插入位置
        if (i + 1 != low) {
            nums[i + 1] = temp;
        }

        insertion(nums, low+1);
    }

    @Test
    public void test(){
        int[] expected = {1, 2, 3, 4, 5};
        int[] a1 = {5, 4, 3, 2, 1};
        sort(a1);
        Assertions.assertArrayEquals(expected, a1);

        int[] a2 = {5, 3, 1, 2, 4};
        sort(a2);
        Assertions.assertArrayEquals(expected, a2);
    }

}

2. 多路递归 Multi Recursion

E01. 斐波那契数列

  • 之前的例子是每个递归函数只包含一个自身的调用,这称之为 single recursion
  • 如果每个递归函数例包含多个自身调用,称之为 multi recursion

递推关系
f ( n ) = { 0 n = 0 1 n = 1 f ( n − 1 ) + f ( n − 2 ) n > 1 f(n) = \begin{cases} 0 & n=0 \\ 1 & n=1 \\ f(n-1) + f(n-2) & n>1 \end{cases} f(n)= 01f(n1)+f(n2)n=0n=1n>1

下面的表格列出了数列的前几项

F0F1F2F3F4F5F6F7F8F9F10F11F12F13
01123581321345589144233
/**
 * 多路递归-斐波那契数列
 */
public class Fibonacci {

    public static int f(int n){
        if (n == 0) {
            return 0;
        }
        if (n == 1){
            return 1;
        }
        int x = f(n - 1);
        int y = f(n - 2);
        return x + y;
    }

    public static void main(String[] args) {
        int f = f(12);
        System.out.println(f);
    }
}

时间复杂度

  • 递归的次数也符合斐波那契规律, 2 ∗ f ( n + 1 ) − 1 2 * f(n+1)-1 2f(n+1)1
  • 时间复杂度推导过程
    • 斐波那契通项公式 f ( n ) = 1 5 ∗ ( 1 + 5 2 n − 1 − 5 2 n ) f(n) = \frac{1}{\sqrt{5}}*({\frac{1+\sqrt{5}}{2}}^n - {\frac{1-\sqrt{5}}{2}}^n) f(n)=5 1(21+5 n215 n)
    • 简化为: f ( n ) = 1 2.236 ∗ ( 1.618 n − ( − 0.618 ) n ) f(n) = \frac{1}{2.236}*({1.618}^n - {(-0.618)}^n) f(n)=2.2361(1.618n(0.618)n)
    • 带入递归次数公式 2 ∗ 1 2.236 ∗ ( 1.618 n + 1 − ( − 0.618 ) n + 1 ) − 1 2*\frac{1}{2.236}*({1.618}^{n+1} - {(-0.618)}^{n+1})-1 22.2361(1.618n+1(0.618)n+1)1
    • 时间复杂度为 Θ ( 1.61 8 n ) \Theta(1.618^n) Θ(1.618n)

E02. 汉诺塔

Tower of Hanoi,是一个源于印度古老传说:大梵天创建世界时做了三根金刚石柱,在一根柱子从下往上按大小顺序摞着 64 片黄金圆盘,大梵天命令婆罗门把圆盘重新摆放在另一根柱子上,并且规定

  • 一次只能移动一个圆盘
  • 小圆盘上不能放大圆盘

思路

  • 假设每根柱子标号 a,b,c,每个圆盘用 1,2,3 … 表示其大小,圆盘初始在 a,要移动到的目标是 c

  • 如果只有一个圆盘,此时是最小问题,可以直接求解

    • 移动圆盘1 a ↦ c a \mapsto c ac
  • 如果有两个圆盘,那么

    • 圆盘1 a ↦ b a \mapsto b ab
    • 圆盘2 a ↦ c a \mapsto c ac
    • 圆盘1 b ↦ c b \mapsto c bc
  • 如果有三个圆盘,那么

    • 圆盘12 a ↦ b a \mapsto b ab
    • 圆盘3 a ↦ c a \mapsto c ac
    • 圆盘12 b ↦ c b \mapsto c bc
  • 如果有四个圆盘,那么

    • 圆盘 123 a ↦ b a \mapsto b ab
    • 圆盘4 a ↦ c a \mapsto c ac
    • 圆盘 123 b ↦ c b \mapsto c bc

题解
在这里插入图片描述

/**
 * 多路递归-汉诺塔
 */
public class HanoiTower {

    static LinkedList<Integer> a = new LinkedList<>();
    static LinkedList<Integer> b = new LinkedList<>();
    static LinkedList<Integer> c = new LinkedList<>();

    /**
     * 初始化圆盘
     * @param n 圆盘的个数(由大到小)
     */
    static void init(int n){
        for (int i = n; i >= 1 ; i--) {
            a.addLast(i);
        }
    }

    /**
     *
     * @param n 圆盘个数
     * @param a 原柱子 (左)
     * @param b 借用的柱子 (中)
     * @param c 目标的柱子 (右)
     */
    static void move(int n, LinkedList<Integer> a, LinkedList<Integer> b, LinkedList<Integer> c){
        if (n == 0){
            return;
        }
        move(n-1, a, c, b);     // 把 n-1个盘子 由a,借c,移至b
        c.addLast(a.removeLast()); // 把最后的盘子由 a 移至 c
        print();
        move(n-1, b, a, c);     // // 把 n-1个盘子 由b,借a,移至c
    }

    public static void main(String[] args) {
        init(3);
        print();
        move(3, a, b, c);
    }

    private static void print() {
        System.out.println("--------------");
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}

E03. 杨辉三角

分析

把它斜着看

        1
      1   1
    1   2   1
  1   3   3   1
1   4   6   4   1
  • i i i,列 j j j,那么 [ i ] [ j ] [i][j] [i][j] 的取值应为 [ i − 1 ] [ j − 1 ] + [ i − 1 ] [ j ] [i-1][j-1] + [i-1][j] [i1][j1]+[i1][j]
  • j = 0 j=0 j=0 i = j i=j i=j 时, [ i ] [ j ] [i][j] [i][j] 取值为 1 1 1
/**
 * 多路递归-杨辉三角
 */
public class PascalTriangle {

    public static void main(String[] args) {
//        System.out.println(element(4, 2)); // 计算某列某行的值
//        print(10);
//        print1(10);
        print2(10);
    }

    /**
     * 优化2 - 使用一位数组记忆法
     * @param row
     * @param i
     */
    private static void createRow(int[] row, int i){
        if (i == 0){
            row[0] = 1;
            return;
        }
        for (int j = i; j > 0; j--) {
            row[j] = row[j] +row[j - 1];
        }
    }

    /**
     * 配合createRow调用
     * @param n
     */
    public static void print2(int n){
        int[] row = new int[n];
        for (int i = 0; i < n; i++) { // 行
            createRow(row, i);
            // 打印空格
            printSpace(n, i);
            for (int j = 0; j <= i; j++) { // 列
                System.out.printf("%-4d", row[j]);
            }
            System.out.println();
        }
    }


    /**
     * 优化1-使用二维数组记忆法
     * @param triangle 二位数组
     * @param i 行坐标
     * @param j 列坐标
     * @return
     */
    private static int element1(int[][] triangle, int i, int j){
        if (triangle[i][j] > 0){
            return triangle[i][j];
        }

        if (j == 0 || i == j){
            triangle[i][j] = 1;
            return 1;
        }
        triangle[i][j] = element1(triangle,i - 1, j - 1) + element1(triangle, i - 1, j);
        return triangle[i][j];
    }

    /**
     * 配合element1调用
     * @param n
     */
    public static void print1(int n){
        int[][] triangle = new int[n][];
        for (int i = 0; i < n; i++) { // 行
            triangle[i] = new int[i + 1];
            // 打印空格
            printSpace(n, i);
            for (int j = 0; j <= i; j++) { // 列
                System.out.printf("%-4d", element1(triangle, i, j));
            }
            System.out.println();
        }
    }

    /**
     * 直接递归-未优化
     * @param i 行坐标
     * @param j 列坐标
     * @return
     */
    private static int element(int i, int j){
        if (j == 0 || i == j){
            return 1;
        }
        return element(i - 1, j - 1) + element(i - 1, j);
    }

    private static void printSpace(int n, int i){
        int num = (n - 1 - i) * 2;
        for (int j = 0; j < num; j++) {
            System.out.print(" ");
        }
    }

    public static void print(int n){
        for (int i = 0; i < n; i++) {
            // 打印行
            // 打印空格
            printSpace(n, i);
            for (int j = 0; j <= i; j++) {
                // 打印列
                System.out.printf("%-4d", element(i, j));
            }
            System.out.println();
        }
    }

}

3. 递归优化

a.记忆法

  • Memoization 记忆法(也称备忘录)是一种优化技术,通过存储函数调用结果(通常比较昂贵),当再次出现相同的输入(子问题)时,就能实现加速效果,改进后的代码
/**
 * 多路递归-斐波那契数列
 */
public class Fibonacci {

    private static int[] cache;

    public static int fibonacci(int n){
        cache = new int[n + 1];
        Arrays.fill(cache, -1); // 让数组初始值都是-1
        cache[0] = 0;
        cache[1] = 1;
        return f(n);
    }

    public static int f(int n){
        if (cache[n] != -1){
            return cache[n];
        }

        int x = f(n - 1);
        int y = f(n - 2);
        cache[n] = x + y;
        return x + y;
    }
    
    public static void main(String[] args) {
        int f = fibonacci(12);
        System.out.println(f);
    }
}

优化后,只要结果被缓存,就不会执行其子问题

  • 改进后的时间复杂度为 O ( n ) O(n) O(n)
  • 请自行验证改进后的效果
  • 请自行分析改进后的空间复杂度

注意

  1. 记忆法是动态规划的一种情况,强调的是自顶向下的解决
  2. 记忆法的本质是空间换时间

b.尾递归

爆栈

用递归做 n + ( n − 1 ) + ( n − 2 ) . . . + 1 n + (n-1) + (n-2) ... + 1 n+(n1)+(n2)...+1

public static long sum(long n) {
    if (n == 1) {
        return 1;
    }
    return n + sum(n - 1);
}

在我的机器上 n = 12000 n = 12000 n=12000 时,爆栈了

Exception in thread "main" java.lang.StackOverflowError
	at Test.sum(Test.java:10)
	at Test.sum(Test.java:10)
	at Test.sum(Test.java:10)
	at Test.sum(Test.java:10)
	at Test.sum(Test.java:10)
	...

为什么呢?

  • 每次方法调用是需要消耗一定的栈内存的,这些内存用来存储方法参数、方法内局部变量、返回地址等等
  • 方法调用占用的内存需要等到方法结束时才会释放

尾调用

如果函数的最后一步是调用一个函数,那么称为尾调用,例如

function a() {
    return b()
}

下面三段代码不能叫做尾调用 :

function a() {
    const c = b()
    return c
}
  • 因为最后一步并非调用函数
function a() {
    return b() + 1
}
  • 最后一步执行的是加法
function a(x) {
    return b() + x
}
  • 最后一步执行的是加法

为何尾递归才能优化?

调用 a 时

  • a 返回时发现:没什么可留给 b 的,将来返回的结果 b 提供就可以了,用不着我 a 了,我的内存就可以释放

调用 b 时

  • b 返回时发现:没什么可留给 c 的,将来返回的结果 c 提供就可以了,用不着我 b 了,我的内存就可以释放

如果调用 a 时

  • 不是尾调用,例如 return b() + 1,那么 a 就不能提前结束,因为它还得利用 b 的结果做加法

尾递归

  • 尾递归是尾调用的一种特例,也就是最后一步执行的是同一个函数
  • Java语言不支持尾递归

改循环避免爆栈 (改成循环)

public static void main(String[] args) {
    long n = 100000000;
    long sum = 0;
    for (long i = n; i >= 1; i--) {
        sum += i;
    }
    System.out.println(sum);
}

4. 递归时间复杂度

若有递归式
T ( n ) = a T ( n b ) + f ( n ) T(n) = aT(\frac{n}{b}) + f(n) T(n)=aT(bn)+f(n)
其中

  • T ( n ) T(n) T(n) 是问题的运行时间, n n n 是数据规模
  • a a a 是子问题个数
  • T ( n b ) T(\frac{n}{b}) T(bn) 是子问题运行时间,每个子问题被拆成原问题数据规模的 n b \frac{n}{b} bn
  • f ( n ) f(n) f(n) 是除递归外执行的计算 ( n c n^c nc)

x = log ⁡ b a x = \log_{b}{a} x=logba,即 x = log ⁡ 子问题缩小倍数 子问题个数 x = \log_{子问题缩小倍数}{子问题个数} x=log子问题缩小倍数子问题个数

那么
T ( n ) = { Θ ( n x ) f ( n ) = O ( n c ) 并且 c < x Θ ( n x log ⁡ n ) f ( n ) = Θ ( n x ) Θ ( n c ) f ( n ) = Ω ( n c ) 并且 c > x T(n) = \begin{cases} \Theta(n^x) & f(n) = O(n^c) 并且 c \lt x\\ \Theta(n^x\log{n}) & f(n) = \Theta(n^x)\\ \Theta(n^c) & f(n) = \Omega(n^c) 并且 c \gt x \end{cases} T(n)= Θ(nx)Θ(nxlogn)Θ(nc)f(n)=O(nc)并且c<xf(n)=Θ(nx)f(n)=Ω(nc)并且c>x

例1
T ( n ) = 2 T ( n 2 ) + n 4 T(n) = 2T(\frac{n}{2}) + n^4 T(n)=2T(2n)+n4

  • 此时 x = 1 < 4 x = 1 < 4 x=1<4,由后者决定整个时间复杂度 Θ ( n 4 ) \Theta(n^4) Θ(n4)
  • 如果觉得对数不好算,可以换为求【 b b b 的几次方能等于 a a a

例2

T ( n ) = T ( 7 n 10 ) + n T(n) = T(\frac{7n}{10}) + n T(n)=T(107n)+n

  • a = 1 , b = 10 7 , x = 0 , c = 1 a=1, b=\frac{10}{7}, x=0, c=1 a=1,b=710,x=0,c=1
  • 此时 x = 0 < 1 x = 0 < 1 x=0<1,由后者决定整个时间复杂度 Θ ( n ) \Theta(n) Θ(n)

例3

T ( n ) = 16 T ( n 4 ) + n 2 T(n) = 16T(\frac{n}{4}) + n^2 T(n)=16T(4n)+n2

  • a = 16 , b = 4 , x = 2 , c = 2 a=16, b=4, x=2, c=2 a=16,b=4,x=2,c=2
  • 此时 x = 2 = c x=2 = c x=2=c,时间复杂度 Θ ( n 2 log ⁡ n ) \Theta(n^2 \log{n}) Θ(n2logn)

例4

T ( n ) = 7 T ( n 3 ) + n 2 T(n)=7T(\frac{n}{3}) + n^2 T(n)=7T(3n)+n2

  • a = 7 , b = 3 , x = 1. ? , c = 2 a=7, b=3, x=1.?, c=2 a=7,b=3,x=1.?,c=2
  • 此时 x = log ⁡ 3 7 < 2 x = \log_{3}{7} < 2 x=log37<2,由后者决定整个时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2)

例5

T ( n ) = 7 T ( n 2 ) + n 2 T(n) = 7T(\frac{n}{2}) + n^2 T(n)=7T(2n)+n2

  • a = 7 , b = 2 , x = 2. ? , c = 2 a=7, b=2, x=2.?, c=2 a=7,b=2,x=2.?,c=2
  • 此时 x = l o g 2 7 > 2 x = log_2{7} > 2 x=log27>2,由前者决定整个时间复杂度 Θ ( n log ⁡ 2 7 ) \Theta(n^{\log_2{7}}) Θ(nlog27)

例6

T ( n ) = 2 T ( n 4 ) + n T(n) = 2T(\frac{n}{4}) + \sqrt{n} T(n)=2T(4n)+n

  • a = 2 , b = 4 , x = 0.5 , c = 0.5 a=2, b=4, x = 0.5, c=0.5 a=2,b=4,x=0.5,c=0.5
  • 此时 x = 0.5 = c x = 0.5 = c x=0.5=c,时间复杂度 Θ ( n   log ⁡ n ) \Theta(\sqrt{n}\ \log{n}) Θ(n  logn)

例7. 二分查找递归

int f(int[] a, int target, int i, int j) {
    if (i > j) {
        return -1;
    }
    int m = (i + j) >>> 1;
    if (target < a[m]) {
        return f(a, target, i, m - 1);
    } else if (a[m] < target) {
        return f(a, target, m + 1, j);
    } else {
        return m;
    }
}
  • 子问题个数 a = 1 a = 1 a=1
  • 子问题数据规模缩小倍数 b = 2 b = 2 b=2
  • 除递归外执行的计算是常数级 c = 0 c=0 c=0

T ( n ) = T ( n 2 ) + n 0 T(n) = T(\frac{n}{2}) + n^0 T(n)=T(2n)+n0

  • 此时 x = 0 = c x=0 = c x=0=c,时间复杂度 Θ ( log ⁡ n ) \Theta(\log{n}) Θ(logn)

5. 递归时间复杂度-展开求解

像下面的递归式,都不能用主定理求解

例1 - 递归求和

long sum(long n) {
    if (n == 1) {
        return 1;
    }
    return n + sum(n - 1);
}

T ( n ) = T ( n − 1 ) + c T(n) = T(n-1) + c T(n)=T(n1)+c T ( 1 ) = c T(1) = c T(1)=c

下面为展开过程

T ( n ) = T ( n − 2 ) + c + c T(n) = T(n-2) + c + c T(n)=T(n2)+c+c

T ( n ) = T ( n − 3 ) + c + c + c T(n) = T(n-3) + c + c + c T(n)=T(n3)+c+c+c

T ( n ) = T ( n − ( n − 1 ) ) + ( n − 1 ) c T(n) = T(n-(n-1)) + (n-1)c T(n)=T(n(n1))+(n1)c

  • 其中 T ( n − ( n − 1 ) ) T(n-(n-1)) T(n(n1)) T ( 1 ) T(1) T(1)
  • 带入求得 T ( n ) = c + ( n − 1 ) c = n c T(n) = c + (n-1)c = nc T(n)=c+(n1)c=nc

时间复杂度为 O ( n ) O(n) O(n)

例2 - 递归冒泡排序

void bubble(int[] a, int high) {
    if(0 == high) {
        return;
    }
    for (int i = 0; i < high; i++) {
        if (a[i] > a[i + 1]) {
            swap(a, i, i + 1);
        }
    }
    bubble(a, high - 1);
}

T ( n ) = T ( n − 1 ) + n T(n) = T(n-1) + n T(n)=T(n1)+n T ( 1 ) = c T(1) = c T(1)=c

下面为展开过程

T ( n ) = T ( n − 2 ) + ( n − 1 ) + n T(n) = T(n-2) + (n-1) + n T(n)=T(n2)+(n1)+n

T ( n ) = T ( n − 3 ) + ( n − 2 ) + ( n − 1 ) + n T(n) = T(n-3) + (n-2) + (n-1) + n T(n)=T(n3)+(n2)+(n1)+n

T ( n ) = T ( 1 ) + 2 + . . . + n = T ( 1 ) + ( n − 1 ) 2 + n 2 = c + n 2 2 + n 2 − 1 T(n) = T(1) + 2 + ... + n = T(1) + (n-1)\frac{2+n}{2} = c + \frac{n^2}{2} + \frac{n}{2} -1 T(n)=T(1)+2+...+n=T(1)+(n1)22+n=c+2n2+2n1

时间复杂度 O ( n 2 ) O(n^2) O(n2)

注:

  • 等差数列求和为 个数 ∗ ∣ 首项 − 末项 ∣ 2 个数*\frac{\vert首项-末项\vert}{2} 个数2首项末项

不会推导的同学可以进入 https://www.wolframalpha.com/ (选Math input)

  • 例1 输入 f(n) = f(n - 1) + c, f(1) = c
  • 例2 输入 f(n) = f(n - 1) + n, f(1) = c
  • 例3 输入 f(n) = f(n - 1) + n + c, f(1) = c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值