目录
算法效率的度量方法
所谓算法,是指基于特定的计算模型,旨在解决某一信息处理问题而设计的一个指令序列。
在程序编写前,需要依据统计方法对算法进行估算。
1.1 影响执行速度的因素
-
算法采用的策略方案
-
编译产生的代码质量
-
问题的输入规模
-
机器执行指令的速度
1.2 时间复杂度和空间复杂度
写程序时需要分析时间和空间复杂度
掌握一两种编程工具,刻意练习到熟练
1.3 递归
分支转向是算法的灵魂;函数和过程及其之间的相互调用,是在经过抽象和封装之后,实现分支转向的一种重要机制;而递归则是函数和过程调用的一种特殊形式,即允许函数和过程进行自我调用。
递归的价值在于,许多应用问题都可简洁而准确地描述为递归形式。以操作系统为例,多数文件系统的目录结构都是递归定义的。具体地,每个文件系统都有一个最顶层的目录,其中可以包含若干文件和下一层的子目录;而在每一子目录中,也同样可能包含若干文件和再下一层的子目录;如此递推,直至不含任何下层的子目录。
递归也是一种基本而典型的算法设计模式。这一模式可以对实际问题中反复出现的结构和形式做高度概括,并从本质层面加以描述与刻画,进而导出高效的算法。
1.3.1 线性递归
数组求和
1 int sum(int A[], int n) { //数组求和算法
2 if (1 > n)
3 return 0; //直接计算
4 else //一般情况
5 return sum(A, n - 1) + A[n - 1];
6 } //O(1)*递归深度 = O(1)*(n + 1) = O(n)
由此实例,可以看出保证递归算法有穷性的基本技巧:
首先判断并处理n = 0之类的平凡情况,以免因无限递归而导致系统溢出。这类平凡情况统称“递归基”(base case of recursion)。平凡情况可能有多种,但至少要有一种(比如此处),且迟早必然会出现。
线性递归
算法sum()可能朝着更深一层进行自我调用,且每一递归实例对自身的调用至多一次。于是,每一层次上至多只有一个实例,且它们构成一个线性的次序关系。此类递归模式因而称作“线性递归”(linear recursion),它也是递归的最基本形式。
减而治之
线性递归的模式,往往对应于所谓减而治之(decrease-and-conquer)的算法策略:递归每深入一层,待求解问题的规模都缩减一个常数,直至最终蜕化为平凡的小(简单)问题。
1.3.2 递归分析
递归算法时间和空间复杂度的分析与常规算法很不一样,有其自身的规律和特定的技巧,以下介绍递归跟踪与递推方程这两种主要的方法。
递归跟踪
作为一种直观且可视的方法,递归跟踪(recursion trace)可用以分析递归算法的总体运行时间与空间。具体地,就是按照以下原则,将递归算法的执行过程整理为图的形式:
递推方程
递归算法的另一常用分析方法,即递推方程(recurrence equation)法。
递归算法的另一常用分析方法,即递推方程(recurrence equation)法。与递归跟踪分析相反,该方法无需绘出具体的调用过程,而是通过对递归模式的数学归纳,导出复杂度定界函数的递推方程(组)及其边界条件,从而将复杂度的分析,转化为递归方程(组)的求解。
递归算法所消耗的空间量主要取决于递归深度。因其高度的抽象性和简洁性,递归已成为多数高级程序语言普遍支持的一项重要特性。
递归的时间复杂度求解
因为递归每一层的计算次数都是上一层需要计算次数的二倍,因此递归的时间复杂度是2的n次幂
1.3.3 leetcode习题
1. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
思路
1.动态规划
爬到N阶,可以从第N-1阶爬1步,也可以从第N-2阶爬2步。也就是说到N阶的方式有f(N-1)+f(N-2)种方式(因为要么1步,要么2步),后面一次类推。可以看出这是个菲波那切数列。每次的结果都是前两次结果的总和。
答案:
class Solution {
public:
int climbStairs(int n) {
if(n<=2) return n;
int res = 0, index = 2, i = 1, j = 2;
while(index<n) {
res = i+j;
i=j;
j=res;
index++;
}
return res;
}
};