第一部分 基础知识
第1章 算法基础
1.1 插入排序
-
关键词:我们要排序的数。
-
伪代码:我们使用最清晰,最简洁的表示方法来说明给定的算法。
- 约定:
- 缩进表示块结构
- while,for与repeat-until等循环结构以及if-else等条件结构与代码类似
- //:表示该行后面部分是个注释
- i=j=e:将e的值赋给变量i和j
- 数组元素通过“数组名[下标]”的形式来访问
- 复合数据通常被组织成对象,对象又由属性组成
- 我们按值把参数传递给过程
- 约定:
-
INSERTION-SORT:插入排序的伪代码过程。
for j = 2 to A.length key = A[j] //Insert A[j] into the sorted sequence A[1..j-1]. i = j - 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i - 1 A[i+1] = key
A[1…n]:包含长度为n的要排序的一个序列。
A.length:A中元素的数目n
-
循环不变式:
- 性质:
- 初始化:循环之前它为真
- 保持:下次迭代依然为真
- 终止:循环终止时,不变式为我们提供有用的性质
- 性质:
-
python代码:
def insert_sort(mylist): length = len(mylist) #获取列表长度 for i in range(1,length): j = i - 1 #设置当前值前一个元素的标识 if(mylist[i] < mylist[j]): #如果当前值小于前一个元素,则将当前值作为一个临时变量存储,将前一个元素后移一位 temp = mylist[i] mylist[i] = mylist[j] j = j-1 #继续往前寻找,如果有比临时变量大的数字,则后移一位,直到找到比临时变量小的元素或者达到列表第一个元素 while j>=0 and mylist[j] > temp: mylist[j+1] = mylist[j] j = j-1 mylist[j+1] = temp #将临时变量赋值给合适位置 return mylist mylist = [49,38,65,97,76,13,27,49] print(insert_sort(mylist))
1.2 分析算法
-
输入规模:概念:输入规模的最佳概念依赖于研究的问题。
问题 量度 排序,计算离散傅里叶变换 输入中的项数 两个整数相乘 用二进制记号表示输入所需的总位数 输入是一个图 该图的顶点数和边数 -
运行时间:是指执行的基本操作数或步数
-
增长量级: θ ( n 2 ) \theta(n^2) θ(n2)
-
插入排序的最坏情况运行时间: T ( n ) = θ ( n 2 ) T(n)=\theta(n^2) T(n)=θ(n2)
1.3 设计算法
1.3.1 分治法
- 思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,再合并这些子问题的解来建立原问题的解。
- 步骤:分解,解决,合并
第2章 函数的增长
2.1 渐近记号
-
渐近紧确线: θ \theta θ
-
运行表达式 T ( n ) T(n) T(n)中n的最大次数项。例如: T ( n ) = 30 n 4 + 20 n 3 + 40 n 2 + 46 n + 100 T(n)=30n^4+20n^3+40n^2+46n+100 T(n)=30n4+20n3+40n2+46n+100,记为 T ( n ) = θ ( n 4 ) T(n)=\theta(n^4) T(n)=θ(n4)
-
数学含义: f ( n ) f(n) f(n)和 g ( n ) g(n) g(n)同阶
-
注意: θ ( g ( n ) ) \theta(g(n)) θ(g(n))的定义要求当n足够大时, g ( n ) g(n) g(n)非负
-
-
渐近上界记号: O O O
- 渐近地给出函数的上界
- O ( g ( n ) ) = f ( n ) O(g(n))=f(n) O(g(n))=f(n)
- 这个上界的阶越低,评估越精确,越有价值
- 用作算法的最坏情况运行时间
-
渐近下界记号: Ω \Omega Ω
- 下界的阶越高,评估越精确,越有价值
- Ω ( g ( n ) ) = f ( n ) \Omega(g(n))=f(n) Ω(g(n))=f(n)
-
非渐近紧确上界: o o o
- o ( g ( n ) ) = f ( n ) o(g(n))=f(n) o(g(n))=f(n)
- 比渐近紧确上界阶数高
-
非渐近紧确下界: ω \omega ω
- ω ( g ( n ) ) = f ( n ) \omega(g(n))=f(n) ω(g(n))=f(n)
- 比渐近紧确下界阶数低
记号 含义 理解 θ \theta θ 紧确界 相当于“=” O O O 紧确上界 相当于“ ⩾ \geqslant ⩾” o o o 非紧确上界 相当于“>” Ω \Omega Ω 紧确下界 相当于“ ⩽ \leqslant ⩽” ω \omega ω 非紧确下界 相当于“<”
2.2 标准记号与常用函数
-
单调性
-
向下取整与向上取整: x − 1 < ⌊ x ⌋ ⩽ x ⩽ ⌈ x ⌉ < x + 1 x-1<\lfloor x\rfloor \leqslant x\leqslant\lceil x\rceil<x+1 x−1<⌊x⌋⩽x⩽⌈x⌉<x+1
-
模运算: a m o d n = a − n ⌊ a / n ⌋ amodn=a-n\lfloor a/n\rfloor amodn=a−n⌊a/n⌋,商a/n的余数
-
阶乘: n ! n! n!
-
多重函数
-
多重对数函数
-
斐波那契数: F 0 = 0 F_0=0 F0=0
F 1 = 1 F_1=1 F1=1
F i = F i − 1 + F i − 2 F_i=F_{i-1}+F_{i-2} Fi=Fi−1+Fi−2
第3章 分治策略
- 步骤:
- 分解
- 解决
- 合并
- 递归情况:当子问题足够大
- 基本情况:当子问题足够小
- 递归式:刻画分治算法的运行时间
- 求解递归式的方法:
- 代入法
- 递归树法
- 主方法
3.1 最大子数组
-
例题: 买股票,使利益最大化
-
思路:低价买进,高价卖出
-
暴力解决问题:尝试每对可能的买进和卖出日期组合。n天中共有 ( 2 n ) (_2^n) (2n)种日期组合。运行时间为 Ω ( n 2 ) \Omega(n^2) Ω(n2)
-
问题变换:寻找一段日期,使得从第一天到最后一天的股票净变值。问题转化为寻找A的和最大的非空连续子数组。称这样的连续子数组为最大子数组。
-
使用分治策略的求解方法:将子数组划分为两个规模尽量相等的子数组,分别求解两个子数组的最大子数组。
FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
left-sum = 负无穷 sum = 0 for i = mid downto low sum = sum + A[i] if sum > left-sum left-sum = sum max-left = i right-sum = 负无穷 sum = 0 for j = mid + 1 to high sum = sum + A[j] if sum > right-sum right-sum = sum max-right = j return(max-left, max-right, left-sum + reght-sum)
FIND-MAXIMUN-SUBARRAY(A, low, high)
if high == low return(low, high, A[low]) else mid=(low+high)/2的下限 (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid) (right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid+1, high) (cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) if left-sum>=right-sum and left-sum>=cross-sum return (left-low, left-high, left-sum) elseif right-sum>=left-sum and right-sum>=cross-sum return (right-low, right-high, right-sum) else return (cross-low, cross-high, cross-sum)
-
python代码
def FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high): left_sum = 0 sum = 0 for i in range(low, mid): sum = sum + A[i] if sum > left_sum: left_sum = sum right_sum = 0 sum = 0 for j in range(mid+1, high): sum = sum + A[j] if sum > right_sum: right_sum = sum return left_sum, right_sum, left_sum + right_sum def FIND_MAXIMUM_SUBARRAY(A, low, high): if high == low: return low, high, A[low] else: mid = (low+high)//2 left_low, left_high, left_sum = FIND_MAXIMUM_SUBARRAY(A, low, mid) right_low, right_high, right_sum = FIND_MAXIMUM_SUBARRAY(A, mid+1, high) cross_low, cross_high, cross_sum = FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high) if left_sum>=right_sum and left_sum>=cross_sum: return left_low, left_high, left_sum elif right_sum>=left_sum and right_sum>=cross_sum: return right_low, right_high, right_sum else: return cross_low, cross_high, cross_sum B = [2,4,5,-3,-5,2,-6,2,7,-5,4,1,3,6,-9,-2,-4,8] B_low = 0 B_high = 16 B_mid = 8 print(FIND_MAXIMUM_SUBARRAY(B, B_low, B_high))
-
分治算法的分析
- 运行时间:
{ θ , n = 1 2 T ( n / 2 ) + θ ( n ) , n > 1 \left\{ \begin{array}{c} \theta,n=1\\ 2T(n/2)+\theta(n),n>1 \end{array} \right. {θ,n=12T(n/2)+θ(n),n>1
- 运行时间:
3.2 矩阵乘法的Strassen算法
-
SQUARE-MATRIX-MULTIPLY(A,B)
n = A.rows let C be a new n×n matrix for i = 1 to n for j = 1 to n Cij = 0 for k = 1 to n Cij = Cij + aik * bkj return C
-
运行时间: θ ( n 3 ) \theta(n^3) θ(n3)
-
分治算法:
-
伪代码:SQUARE-MATRIX-MULTIPLY-RECURSIVE(A,B)
n = A.rows let C be a new n×n matrix if n == 1 c11=a11 * b11 else partition A, B, and C as in equations(4,9) C11=SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11,B11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12,B21) C12=SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11,B12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12,B22) C21=SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21,B11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22,B21) C22=SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21,B12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22,B22) return C
-
运行时间:
T ( n ) = { θ ( 1 ) , n = 1 8 T ( n / 2 ) + θ ( n 2 ) , n > 1 T(n)=\left\{ \begin{array}{c} \theta(1),n=1\\ 8T(n/2)+\theta(n^2),n>1 \end{array} \right. T(n)={θ(1),n=18T(n/2)+θ(n2),n>1
-
-
Strassen方法
-
步骤:
- 将输入矩阵A,B和输出矩阵C各分解为四个 n / 2 × n / 2 n/2\times n/2 n/2×n/2的子矩阵。
- 创建10个 n / 2 × n / 2 n/2\times n/2 n/2×n/2的空矩阵 S 1 , S 2 , ⋅ ⋅ ⋅ , S 10 S_1,S_2,···,S_{10} S1,S2,⋅⋅⋅,S10,每个矩阵保存步骤1中创建的两个子矩阵的和与差。花费时间为 θ ( n 2 ) \theta(n^2) θ(n2)。
- 用步骤1中创建的子矩阵和步骤2中创建的10个矩阵,递归地计算7个矩阵积 P 1 , P 2 , ⋅ ⋅ ⋅ , P 7 P_1,P_2,···,P_7 P1,P2,⋅⋅⋅,P7。每个矩阵 P i P_i Pi都是 n / 2 × n / 2 n/2\times n/2 n/2×n/2的。
- 通过 P i P_i Pi矩阵的不同组合进行加减运算,计算出结果矩阵C的子矩阵 C 11 , C 12 , C 21 , C 22 C_{11},C_{12},C_{21},C_{22} C11,C12,C21,C22。花费时间 θ ( n 2 ) \theta(n^2) θ(n2)。
-
运行时间:
T ( n ) = { θ ( 1 ) , n = 1 7 T ( n / 2 ) + θ ( n 2 ) , n > 1 T(n)=\left\{ \begin{array}{c} \theta(1),n=1\\ 7T(n/2)+\theta(n^2),n>1 \end{array} \right. T(n)={θ(1),n=17T(n/2)+θ(n2),n>1
-
3.3 用代入法求解递归式
- 代入法求解递归式:
- 步骤:
- 猜测解的形式
- 用数学归纳法求出解中的常数,并证明解是正确的
- 步骤:
- 做出好的猜测
- 避免陷阱
- 改变变量
3.4 用递归树方法求解递归式
- 递归树中,每个结点表示一个单一子问题的代价,子问题对应某次递归函数调用。创建递归树之后,对树的每层的各子问题的代价进行求和,得到每一层的代价,然后将所有层的代价加起来,得到整棵递归树的总代价,这个总代价就是递归式的解。
3.5 用主方法求解递归式
- 主定理:令 a≥1和b>1是常数, f ( n ) f(n) f(n)是一个函数, T ( n ) T(n) T(n)是定义在非负整数上的递归式: T ( n ) = a T ( n / b ) + f ( n ) T(n) = aT(n/b) + f(n) T(n)=aT(n/b)+f(n)
第4章 概率分布和随机分布
4.1 雇用问题
-
题目:代理公司帮你物色办公助理候选人,面试一个候选人支付代理公司1K。
-
策略:在面试完每个应聘者后,如果该应聘者比目前的办公助理更合适,就辞退当前的办公助理,然后雇佣新的。
-
伪代码:HIRE-ASSISTANT(n)
best = 0 for i = 1 to n interview candidate i if candidate i is better than candidate best best = i hire candidate i
-
最坏情形分析:当应聘者质量按出现次序严格递增时。此时雇佣n次,总费用为 O ( c k n ) O(c_kn) O(ckn)。
-
概率分析:对所有可能输出产生的运行时间取平均。称其为平均情况运行时间。
-
随机算法:一般地,如果一个算法的行为不仅由输入决定,而且也由随机数生成器产生的数字决定,则称这个算法是随机的。
4.2 指示器随机变量
-
指示器随机变量:
I A = { 1 , A 发 生 0 , A 不 发 生 I{A}=\left\{ \begin{array}{c} 1,A发生\\ 0,A不发生 \end{array} \right. IA={1,A发生0,A不发生 -
给定一个样本空间S和S中的一个时间A,设 X A = I { A } X_A=I\{A\} XA=I{A},那么 E [ X A ] = P r { A } E[X_A]=Pr\{A\} E[XA]=Pr{A}。
-
用指示器随机变量分析雇用问题问题:
X ( i ) = I { 应 聘 者 i 被 雇 佣 } = { 1 , 应 聘 者 i 被 雇 用 0 , 应 聘 者 i 不 被 雇 用 X(i)=I\{应聘者i被雇佣\}=\left\{ \begin{array}{c} 1,应聘者i被雇用\\ 0,应聘者i不被雇用 \end{array} \right. X(i)=I{应聘者i被雇佣}={1,应聘者i被雇用0,应聘者i不被雇用 -
E [ X ] = ln n + O ( 1 ) E[X]=\ln n+O(1) E[X]=lnn+O(1),平均只雇用 ln n \ln n lnn个人
4.3 随机算法
-
思想:随机排列应聘者,以加强所有排序都是等可能出现的性质。
-
伪代码:RANDOMIZED-HIRE-ASSISTANT(n)
randomly permute the list of candidates best = 0 for i = 1 to n interview candidate i if candidate i is better than candidate best best = i hire candidate i
-
随机排列数组:
- 方法一:为数组的每个元素
A
[
i
]
A[i]
A[i]赋一个随机的优先级
P
[
i
]
P[i]
P[i],然后依据优先级对数组A中的元素进行排序。
- 伪代码:PERMUTE-BY-SORTING(A)
n = A.length let P[1..n] be a new array for i = 1 to n P[i] = RANDOM(1,n^3) sort A, using P as sort keys
-
方法二:原址排列给定数组
-
伪代码:RANDOMIZE-IN-PLACE(A)
n = A.length for i = 1 to n swap A[i] with A[RANDOM(i,n)]
-
- 方法一:为数组的每个元素
A
[
i
]
A[i]
A[i]赋一个随机的优先级
P
[
i
]
P[i]
P[i],然后依据优先级对数组A中的元素进行排序。