算法学习ing

算法

1. 算法基础

1.1 算法效率

算法效率得主要指标是基本操作次数增长次数。包含最差效率,最优效率,平均效率(输入具有随机性)和摊销效率(同样的数据结构执行多次操作,然后分摊到每一次上)。

1.2 渐进符号O,Ω,Θ

  • 1.2.1 符号O
    O表示上界,定义为:对于足够大的n,t(n)的上界由g(n)的常数倍来确定,即:t(n) ≤ cg(n),c为常数,记为 t ( n ) ∈ O ( g ( n ) ) t(n) \in O(g(n)) t(n)O(g(n))

-1.2.1 符号Ω
Ω表示下界,定义为:对于足够大的n,t(n)的下界由g(n)的常数倍来确定,即:t(n) ≥ cg(n),c为常数,记为 t ( n ) ∈ Ω ( g ( n ) ) t(n) \in \Omega(g(n)) t(n)Ω(g(n))

-1.2.3 紧界Θ
Θ \Theta Θ表示紧界,定义为:对于足够大的n,t(n)的上界和下界由g(n)的常数倍来确定,即: c 2 g ( n ) ≤ t ( n ) ≤ c 1 g ( n ) c_{2}g(n) \le t(n) \le c_{1}g(n) c2g(n)t(n)c1g(n),c为常数,记为 t ( n ) ∈ Θ ( g ( n ) ) t(n) \in \Theta(g(n)) t(n)Θ(g(n))

当算法由两个连续执行的部分组成时,该算法的整体效率由具有较大增长次数的那部分所决定。

1.3 运算规则

  1. O ( f ) + O ( g ) = O ( m a x f , g ) O(f) + O(g) = O(max{f, g}) O(f)+O(g)=O(maxf,g)
  2. O ( f ) + O ( g ) = O ( f + g ) O(f) + O(g) = O(f+g) O(f)+O(g)=O(f+g)
  3. O ( f ) ∗ O ( g ) = O ( f ∗ g ) O(f) * O(g) = O(f * g) O(f)O(g)=O(fg)
  4. 如 果 g ( n ) = O ( f ( n ) ) , 则 O ( f ) + O ( g ) = O ( f ) 如果g(n) = O(f(n)),则O(f) + O(g) = O(f) g(n)=O(f(n))O(f)+O(g)=O(f)
  5. O ( c f ( n ) ) = O ( f ( n ) ) O(cf(n)) = O(f(n)) O(cf(n))=O(f(n))
  6. f = O ( f ) f = O(f) f=O(f)

1.4 求解递归问题的时间复杂度

1.4.1 迭代方法

指循环的展开递归方程,把递归方程转化为和式,然后使用求和技术求解

1.4.2 Master主定理

求解 T ( n ) = a T ( n b ) + f ( n ) , a ≥ 1 , b > 0 是 常 数 , f ( n ) ≥ 0 T(n) = aT(\frac{n}{b}) + f(n), a \ge 1, b>0是常数, f(n) \ge 0 T(n)=aT(bn)+f(n),a1,b>0,f(n)0型方程

  1. f ( n ) ≤ n l o g b a f(n) \le n^{log_{b}{a}} f(n)nlogba, 则 T ( n ) = Θ ( n l o g b a ) T(n) = \Theta(n^{log_{b}{a}}) T(n)=Θ(nlogba)
  2. f ( n ) = n l o g b a f(n) = n^{log_{b}{a}} f(n)=nlogba, 则 T ( n ) = Θ ( n l o g b a l g n ) T(n) = \Theta(n^{log_{b}{a}} lg{n}) T(n)=Θ(nlogbalgn)
  3. f ( n ) ≥ n l o g b a f(n) \ge n^{log_{b}{a}} f(n)nlogba对于所有充分大的n, a f ( n / b ) ≤ c f ( n ) af(n/b) \le cf(n) af(n/b)cf(n),则 T ( n ) = Θ ( f ( n ) ) T(n) = \Theta(f(n)) T(n)=Θ(f(n))

1.5 习题

在这里插入图片描述

3在这里插入图片描述

2. 分治法

将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立与原问题的形式相同递归的解决这些子问题,然后将各子问题的解合并得到原问题的解。

2.1 分治法适用的情况

分治能处理的问题具有以下特征

  1. 问题规模缩小到一定程度容易解决
  2. 问题可以分解为若干个规模较小的相同问题,即问题具有最优子结构性质
  3. 子问题的解可以合并为该问题的解
  4. 各个子问题相互独立

特征二是应用分治法的前提,此特征反映了递归思想的应用。

特征三是关键,能否使用分治法完全取决于此特征。如果仅具有一二特征而不具备特征三,则则应使用贪心法或动态规划。

特征四涉及到算法的效率,如果子问题不独立则会重复的解决公共子问题,应交给动态规划

2.2 伪代码

Divide-and-Conquer(P)
#输入:待解决问题P
#输出:以解决的问题T
	if |P| < n0:
		# 如果问题规模足够小
		then return(ADHOC(P))
	# 否则将问题分解为i个Pi
	for i <- 1 to k:
		do yi <-  Divide-and-Conquer(Pi)
	T <- Merge(y1, y2, y3, ..) # 合并子问题
	return(T)
计算时间

分成两个子问题的计算时间:
在这里插入图片描述

  • g(n) 是足够小的问题规模直接计算出答案的时间
  • f(n) 是合并的计算时间 (Master主定理)

2.3 归并排序

算法 MergeSort(A[0, ...n-1])
# input: 无序数组
# output: 有序数组

copy A[0...[n/2]-1] to B[0...[n/2]-1];
copy A[[n/2]...n] to C[0...[n/2]-1];
Mergesort(B[0...[n/2]-1]);
Mergesort(C[0...[n/2]-1]);
Merge(B, C, A)
return A


# 合并操作
Merge(B, C, A)
# input: B[0...p-1],C[0...q-1],A[0...p+q-1]
# output: A

i<-0, j<-0, k<-0
while i<p & j<q:
	if B[i] < C[p]{
		then A[k] <- B[i];
		i<i+1;
	}
	else{
		A[k] <- C[j];
		j<-j+1;
	}
	k<-k+1;

if i = p:
	copy C[j...q-1] to A[k...p+q-1];
else:
	copy B[i...p-1] to A[k...p+q-1];

归并排序最优最差都是O(nlogn), 辅助空间为O(n)

2.4 最大最小值

问题:给定一个数组,求出其中的最大值和最小值。按照常规操作,需要两次n-1次的比较,使用分治法可以使比较次数降低到3n/2 - 2。
核心思想:将数组不断分解,合并时只合并每组的最大最小值

算法 MaxMin(A)
# input: A[i...j]
# output: max and min

if i = j then return A[i], A[i]
if j = i +1 then 
	if A[i]<A[j] return A[i], A[j]
else 
	return A[j], A[i]
k<- j-i+1 / 2
m1, M1 <- MaxMin(A[i:k])
m2, M2 <- MAxMin(A[k+1:j)
m<-min(m1, m2)
M<-max(M1, M2)
return m, M

2.5 快速排序

最优为O(nlogn), 最次为O( n 2 n^{2} n2)。

算法 quicksort(A)
 // 输入:待排数组A,切片索引start, end
 // 输出:有序数组
 left<-start; right<-end;
 center <- A[left]
 while left<right:
 	while left<right & A[right]>center: # 先动右指针
 		right -= 1
 	A[left] = A[right]
 	while left<right & A[left] < center:
 		left+=1
 	A[right] = A[left]
 A[left] = center
 quicksort(A, start, left-1)
 quicksort(A, left+1, end)

快排的最内循环效率非常高,在处理随机排列数组时,速度比合并排序快。

三平均分区速度更快

2.6 折半查找

找到数据并返回索引值

算法 BinarySearch(A[0...n-1], s)
//输入:有序数组
//输出:索引
start <-0, end<-n-1;
while start < end:
	mid <- (start + end) // 2
	if A[mid] = s then return mid
	elif A[mid]<s then start<-mid+1
	else end <- mid-1

2.7 大整数乘法

计算n位二进制整数X和Y的乘积,给出一个复杂度为 O ( n 1.59 ) O(n^{1.59}) O(n1.59)的算法。算法的思想是,将X和Y分成n/2位。
在这里插入图片描述
其中 A D + B C = ( A + B ) ( C + D ) − A C − B D AD+BC = (A+B)(C+D) - AC - BD AD+BC=(A+B)(C+D)ACBD

2.8 Strassen矩阵乘法

计算两个二阶矩阵乘法的算法。只用了七次乘法运算但增加了加、减法的运算次数
在这里插入图片描述

2.9 线性时间选择

给定n个元素和一个整数k, 1 ≤ k ≤ n 1 \le k \le n 1kn,要求找出n个元素中第k小的元素。

2.10 DFT & FFT

FFT问题浅显的讲,是给定一组 a 0 , a 1 , . . . . a n − 1 , 其 中 n = 2 t , t 为 正 整 数 a_{0}, a_{1}, ....a_{n-1},其中n = 2^{t}, t为正整数 a0,a1,....an1n=2tt,根据公式 A i = ∑ k = 0 n a k e 2 i k π n j , j 是 虚 数 单 位 A_{i} = \sum^{n}_{k=0}{a_{k}e^{\frac{2ik\pi}{n}j}}, j是虚数单位 Ai=k=0naken2ikπjj计算每个 A i A_{i} Ai的题目。使用蛮力法直接计算的时间复杂度为 Θ ( n 2 ) \Theta(n_{2}) Θ(n2),使用分治法可以降到 n l o g n nlogn nlogn

首先引入DFT(离散傅里叶变换)概念。n阶多项式可以用n+1个点值对来表示,也就是说确定了n+1个点就确定了n阶多项式。 { P 0 ( x ) P 1 ( x ) . . . P n ( x ) } = { 1 x 0 x 0 2 . . . x 0 n 1 x 1 x 1 2 . . . x 1 n . . . . . . . . . . . . . . . 1 x n x n 2 . . . x n n } { p 0 p 1 . . . p n } \left\{ \begin{matrix}P_{0}(x) \\ P_{1}(x) \\ ... \\ P_{n}(x) \end{matrix} \right\} = \left\{ \begin{matrix} 1 & x_{0} & x_{0}^{2} & ... & x_{0}^{n} \\ 1 & x_{1} & x_{1}^{2} & ... & x_{1}^{n} \\ ... & ... & ... & ... & ... \\ 1 & x_{n} & x_{n}^{2} & ... & x_{n}^{n} \end{matrix} \right\} \left\{ \begin{matrix} p_{0} \\ p_{1} \\ ... \\ p_{n} \end{matrix} \right\} P0(x)P1(x)...Pn(x)=11...1x0x1...xnx02x12...xn2............x0nx1n...xnnp0p1...pn。这就是离散傅里叶变换,其本质就是矩阵M和向量X做点乘。

2.10.1 FFT —— 快速傅里叶变换

分治法减少计算量的操作体现在将多项式转换成点值表示这一步,利用的是函数的对称性。

计算式 A ( x ) = a 0 + a 1 x 1 + a 2 x 2 2 + . . . + a n x n n , n 是 2 的 倍 数 A(x) = a_{0} + a_{1}x_{1} + a_{2}x_{2}^{2} + ... + a_{n}x_{n}^{n},n是2的倍数 A(x)=a0+a1x1+a2x22+...+anxnnn2,奇数项是偶函数,偶数项是奇函数,而奇数项可以分离成x乘以一个偶函数。

在DFT中提到,n阶多项式需要计算n+1个点。傅里叶提出,使用n+1个复数来代替n+1个x变量。因为函数存在奇偶性,所以原本需要的n个点,现在只需n/2个点(根据奇偶性可以求出相反符号的值),但这样做的前提是正负对一定存在,实数域上无法保证这一点,因此引入复数域。这n个复数域的点并不是随机选取的,而是在复平面将单位圆等分n分后对应的点。

同样对于计算式 A ( x ) = a 0 + a 1 x 1 + a 2 x 2 2 + . . . + a n x n n A(x) = a_{0} + a_{1}x_{1} + a_{2}x_{2}^{2} + ... + a_{n}x_{n}^{n} A(x)=a0+a1x1+a2x22+...+anxnn,整理得

  • A e v e n ( x ) = a 0 + a 2 x 2 + a 4 x 4 + . . . + a n − 2 x n − 2 2 A_{even}(x) = a_{0} + a_{2}x^{2} + a_{4}x^{4} + ... + a_{n-2}x^{\frac{n-2}{2}} Aeven(x)=a0+a2x2+a4x4+...+an2x2n2
  • A o d d ( x ) = a 1 + a 3 x 2 + a 5 x 4 + . . . + a n − 1 x n − 2 2 A_{odd}(x) = a_{1} + a_{3}x^{2} + a_{5}x^{4} + ... + a_{n-1}x^{\frac{n-2}{2}} Aodd(x)=a1+a3x2+a5x4+...+an1x2n2

进一步整理可以发现 A ( x ) = A 1 ( x 2 ) + x A 2 ( x 2 ) A(x) = A1(x^{2}) + xA2(x^{2}) A(x)=A1(x2)+xA2(x2)。 根据复平面单位圆的特点,得到 ω n k + n 2 = − ω n k \omega_{n}^{k + \frac{n}{2}} = -\omega_{n}^{k} ωnk+2n=ωnk

综上所述,在 0 ≤ i ≤ n 2 0 \le i \le \frac{n}{2} 0i2n的部分有 A ( x i ) = A 1 ( x i 2 ) + x i A 2 ( x i 2 ) A(x_{i}) = A1(x_{i}^{2}) + x_{i}A2(x_{i}^{2}) A(xi)=A1(xi2)+xiA2(xi2)
n 2 ≤ i ≤ n \frac{n}{2} \le i \le n 2nin的部分 A ( x i + n 2 ) = A ( − x i ) = A 1 ( x i 2 ) − x i A 2 ( x i 2 ) A(x_{i+\frac{n}{2}}) = A(-x_{i}) = A1(x_{i}^{2}) - x_{i}A2(x_{i}^{2}) A(xi+2n)=A(xi)=A1(xi2)xiA2(xi2)

2.10.2 FFT实现
# ------------------
# DFT & FFT
# ------------------
def DFT(x):
    x = np.asarray(x, dtype=float)  # 浅拷贝
    N = x.shape[0]

    M = [[j for j in range(N)] for j in range(N)]
    M = np.asarray(M, dtype=complex)
    w = np.exp(-2j * np.pi / N)

    for i in range(N):
        for j in range(N):
            M[i][j] = np.power(w,i*j)

    return np.dot(M, x)

def FFT(x):
    x = np.asarray(x, dtype=float)
    N = x.shape[0]

    if N < 2:
        return DFT(x)
    even_x = FFT(x[0::2])
    odd_x = FFT(x[1::2])

    coeff = np.exp(-2j * np.pi * np.arange(N) / N)

    return np.concatenate([
        even_x + coeff[:int(N/2)] * odd_x,
        even_x + coeff[int(N/2)] * odd_x
    ])

2.11 循环赛日程表

问题:有 n = 2 k n = 2^k n=2k个运动员进行循环赛,设计一个可以满足以下条件的比赛日程表

  • 每个选手必须与其他n-1个选手各赛一次
  • 每个选手一天只能赛一次
  • 循环赛一共进行n-1天

思路:n个选手的日程可以由另外n/2个选手确定。所以问题可以被分治地解决。拿k=3举例,问题一共需要4步。

  1. 初始化第一个选手的日程
12345678
  1. 第二行根据第一行填充
12345678
21436587
  1. 第三、四行根据第一二行填充
  2. 第五、六、七、八行根据前四行填充

具体填充的过程也是分治的实现。执行第二步骤的时候将问题划分为四块
在这里插入图片描述

执行第三步的时候将问题划分为两块
在这里插入图片描述

最后一步合为一块
在这里插入图片描述

实现
%%time
# ---------------------
# 循环赛日程表
# ---------------------
def create(N):
    M = np.asarray([np.zeros(N+1) for j in range(N+1)])
    # init
    for t in range(1, N+1):
        M[1][t] = t

    m=1;  # m表示填充起始行;n用于分治思想

    n = N
    k = 1
    while n>1:
        n/=2
        k+=1  # 求lgN

    for p in range(1, k+1):  # 分成k组 k = log N
        N = int(N / 2)
        for q in range(1, N+1):  # 分治
            for i in range(m+1, 2*m+1):
                for j in range(m+1, 2*m+1):
                    M[i][j + (q-1)*m*2] = M[i - m][j + (q-1)*m*2 - m] 
                    M[i][j + (q-1)*m*2 - m] = M[i - m][j + (q-1)*m*2]
        m *= 2
    
    m = int(m/2)
    for ii in range(1, m+1):
        print("第%d位选手:" % ii, end=" ")
        for jj in range(2, m+1):
            # print(jj)
            print(M[ii][jj], end=" ")
        print(end="\n")

create(8)

3. 动态规划

动态规划类似分治,也是将待求问题分解成若干个子问题。分治法的弱点是会重复计算子问题的不独立部分,消耗了不必要的效率。如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。因此,动态规划是用来解决多段决策过程最优的通用方法。

动态规划的使用条件有

  1. 最优子结构 : 问题最优解等价于子问题最优解之和
  2. 无后向性: 每个状态都是历史状态的完整总结
  3. 子问题的重叠性: 子问题不独立

动态规划的核心思想:分治和解决冗余

3.1 矩阵连乘

输入n个矩阵,计算 A 1 × A 2 × . . . . × A n A_{1} \times A_{2} \times .... \times A_{n} A1×A2×....×An过程中,乘法运算次数最少的乘法。

例如计算 A 1 = 10 × 100 , A 2 = 100 × 5 , A 3 = 5 × 50 A_{1} = 10 \times 100, A_{2} = 100 \times 5, A_{3} = 5 \times 50 A1=10×100,A2=100×5,A3=5×50时,$(A_{1} \times A_{2}) \times A_{3} 需 要 7500 次 乘 法 运 算 , 而 需要7500次乘法运算,而 7500A_{1} \times (A_{2} \times A_{3}) $则需要75000次计算。

实现

使用一个二维矩阵记录最优结构。

算法 MultiMatrix(p[0, 1, ..., n])
//输入:n个可以连乘的矩阵的行列值,Ai的行是pi, 列是pi+1
//输出:最少计算次序

n <- len(p)-1
init m[n][n]<-[0]; path[n][n]<-[0]
for r<-2 to n: # 表示进行连乘的矩阵个数
	for i<-0 to n-r+1: # 表示连乘矩阵的左边界
		j <- r+i-1  # 右边界
		m[i][j] <- m[i+1][j] + p[i] * p[i+1] * p[j+1]
		path[i][j] <- i+1
		for k<-i+1 to j-1: # 断点位置
			t <- m[i][k] + m[k+1][j] + p[i] * p[k+1] * p[j+1]
			if t < m[i][j]  then m[i][j] <- t; path[i][j] <- k

算法 traceback(path, i, j)
//输入:路径矩阵,左边界,右边界
//输出:连乘表达式

size <- j-i+1
if size = 1 then return "Ai"
k<- path[i][j] - 1 # 因为path中不是从0开始的
res <- res + "(" + traceback(path, i, k)
res <- res  + traceback(path,  k+1, j) + ")"
return res

3.2 最长公共子序列

实现
# -------------------
# 最长公共子序列
# -------------------
X = 'ABCBDAB'
Y = 'BDCABA'
def longest(x, y):
    m = np.asarray([np.zeros(len(y)) for j in range(len(x))])

    m[0][0] = (x[0] == y[0])
    for i in range(1, m.shape[0]):
        flag = (x[i] == y[0])
        m[i][0] = (m[i-1][0] or flag)
    for j in range(1, m.shape[1]):
        flag = (x[0] == y[j])
        m[0][j] = (m[0][j-1] or flag)
    
    for i in range(1, m.shape[0]):
        for j in range(1, m.shape[1]):
            flag = (x[i] == y[j])
            m[i][j] = m[i-1][j] + flag if (m[i-1][j] + flag >= m[i][j-1] + flag) else m[i][j-1] + flag
    print(m)
    return m[-1][-1], m

length, path = longest(X, Y)

3.3 二项式系数

实现
# --------------
# 二项式系数
# C(n, k)
# (a+b)^n = C(n,0)a^n + ... + C(n,n)b^n
# 给定n,用一个二维矩阵存储所有C(n,k)
# --------------

def Binomial(n:int):
    c = np.array([np.zeros(n+1) for j in range(n+1)])
    c[0][0] = 1

    for i in range(1, n+1):
        c[i][i] = 1
        c[i][0] = 1
    
    # n作行, k作列 ,且 n>=k
    for i in range(2, n+1):
        for j in range(1, i):
            c[i][j] = c[i-1][j-1] + c[i-1][j]


    print(c)

Binomial(5)

3.4 多段图

。。。。

3.5 Floyd算法

。。。。

3.6 传递闭包——Warshall

。。。

3.7 最优二叉查找树

。。。

3.8 0,1背包

。。。

3.9 TSP动态规划

。。。

3.10 曼哈顿

实现
# --------------
# 曼哈顿游客问题
# 在加权网络中寻找一条最长的路线
# --------------
import numpy as np

C = [
    [[0, 0], [3, 0], [2, 0], [4, 0], [0, 0]],
    [[0, 1], [3, 0], [2, 2], [4, 4], [2, 3]],
    [[0, 4], [0, 6], [7, 5], [3, 2], [4, 1]],
    [[0, 4], [3, 4], [3, 5], [0, 2], [2, 1]],
    [[0, 5], [1, 6], [3, 8], [2, 5], [2, 3]],
]
C = np.array(C)

def Manhattan(C:np.array):
    # s记录各点权重
    s = np.array([np.zeros(5) for j in range(5)])
    # p记录各点动作
    # 0表示向右  1表示向下
    p = np.array([np.zeros(5) for j in range(5)])

    # init
    s[0][0] = 0
    for i in range(1, 5):
        s[i][0] = s[i-1][0] + C[i][0][1]
        p[i][0] = 1

        s[0][i] = s[0][i-1] + C[0][i][0]
        p[0][i] = 0
    
    # DP
    for i in range(1, 5):
        for j in range(1, 5):
            # 从上面来
            s[i][j] = s[i-1][j] + C[i][j][1]
            p[i][j] = 1

            if s[i][j] < (s[i][j-1] + C[i][j][0]):
                s[i][j] = s[i][j-1] + C[i][j][0]
                p[i][j] = 0
    print(p)
    return s[-1][-1], p

def trace(p:np.array, i:int, j:int):
    if i==0 and j==0:
        print('(1, 1)')
        return
    print('({}, {}) ->'.format(i+1, j+1), end=' ')
    if p[i][j] == 1:
        trace(p, i-1, j)
    else:
        trace(p, i, j-1)

trace(path, 4, 4)

3.11 最大子段和

给定n个整数构成的序列 < a 1 , a 2 , . . . , a n > , a i 可 以 是 负 数 <a_{1}, a_{2}, ..., a_{n}>, a_{i}可以是负数 <a1,a2,...,an>,ai。求解 m a x { 0 , max ⁡ 1 ≤ i ≤ j ≤ n ∑ k = i j a k } max \{0, \max_{1 \le i \le j \le n} \sum_{k=i}^{j}{a_{k}}\} max{0,max1ijnk=ijak}

实例:<-2, 11, -4, 13, -5, -2>
最大子段和: a 2 + a 3 + a 4 = 20 a_{2} + a_{3} + a_{4} = 20 a2+a3+a4=20

思路:使用一个一维列表保存路径,求前向最大子段和
在这里插入图片描述

实现
# ---------------
# 最大子段和
# ---------------
def MaxChild(A:np.array):
    n = A.shape[0]
    C = np.asarray([i for i in range(n+1)])
    C[1:] = A

    path = [[]]
    for i in range(n): 
        if  C[i+1] >= (C[i+1] + C[i]):
            C[i+1] = C[i+1]
            path.append([i+1])
        else:
            C[i+1] =  C[i+1] + C[i]
            x = copy.deepcopy(path[i])
            x.append(i+1)
            path.append(x)
    m = C[0]
    for j in range(1, n+1):
        if m < C[j]:
            m = C[j]
            k = j
    return C, path, k

a = np.asarray([-2, 11, -4, 13, -5, -2], dtype=int)
C, path, k = MaxChild(a)
print("path: ", path[k])
print("value: ", C[k])

结果
在这里插入图片描述

4. 红黑树

。。。

5. 贪心算法

在贪心算法中采用逐步构造最优解的方法,在每个阶段都做出看上去最优的决策,且决策一旦做出则不可更改。贪心算法希望通过局部最优得到全局最优

贪心算法是否产生优化解需严格证明。

  1. 贪心选择性:全局最优可以通过局部最优得到
  2. 优化子结构:一个优化解包括子问题的优化解
  • 伪码
算法 GREEDY(A, n)
//in: 问题A,有n个子问题

solution = T
for i<-1 to n
	x = SELECT(A)  //贪心选择局部解
	if FEASIBLE(solution, x)  // 可行解
		then solution = solution + x  // 合并解
return solution

5.1 活动安排问题

问题:设S = {1, 2, …, n}是n个活动的集合,每个活动使用一个资源(比如时间),每个资源只能为一个活动所用。每个活动 i i i有一个起始时间 s i s_{i} si和结束时间 f i f_{i} fi,且满足 s i ≤ f i s_{i} \le f_{i} sifi。称活动 i , j i, j i,j是相容的,若 s i ≥ f j 或 s j ≥ f i s_{i} \ge f_{j} 或 s_{j} \ge f_{i} sifjsjfi

实现
def greedyselect(s:list, f:list, A:list):
	A[1] = True
	j = 1
	for i in range(2, n+1):
		if s[i] >= f[j]:
			A[i] = True
			j = i
		else:
		A[i] = False	
活动安排问题的优化子结构

设S = {1, 2, …, n}是n个活动的集合, [ s i , f i ] [s_{i}, f_{i}] [si,fi]是活动i的起始终止时间,且 f 1 ≤ f 2 ≤ . . . ≤ f n f_{1} \le f_{2} \le ... \le f_{n} f1f2...fn,设A是S的调度问题的一个优化解且包含活动 1 1 1, 则A’ = A - {1}是 S ′ = i ∈ s ∣ s i ≥ f 1 S' = {i \in s | s_{i} \ge f_{1}} S=issif1的优化解。显然,A’中的活动都是相容的,仅需要证明A’是最大的

使用反证法证明。假设存在一个S’的优化解B’, |B’| > |A’|,令B = {1} ∪ B’,对于 ∀ i ∈ S ′ , s i ≥ f 1 , B 中 活 动 相 容 \forall i \in S', s_{i} \ge f_{1}, B中活动相容 iS,sif1,B, 由于|A’| = |A| - 1, |B| = |B’| + 1 > |A’| + 1 = |A|,这与|A|最大矛盾、故优化子结构满足

活动安排问题的贪心选择性

l i l_{i} li S i = j ∈ S ∣ s j ≥ f i − 1 S_{i} = {j \in S | s_{j} \ge f_{i-1}} Si=jSsjfi1中具有最小结束时间 f l i f_{li} fli的活动 l i l_{i} li。设A是S的包含活动1的优化解,其中 f 1 ≤ . . . ≤ f n f_{1} \le ... \le f_{n} f1...fn,则 A = ⋃ i = 1 k { l i } A = \bigcup^{k}_{i=1}{\{l_{i}\}} A=i=1k{li}是一个最优解(最终解是所有f最小的相容活动的集合)。

使用归纳法证明。当|A|=1时显然成立。设|A| < k时,命题成立。当|A|=k时,根据优化子结构的性质( A = 1 ⋃ A j , A j 是 S j = j ∈ S ∣ s j ≥ f 1 A = {1} \bigcup A_{j}, A_{j}是S_{j} = {j \in S | s_{j} \ge f_{1}} A=1Aj,AjSj=jSsjf1的优化解),于是 A = 1 ⋃ A 1 ′ = ⋃ i = 1 k l i A = {1} \bigcup A'_{1} = \bigcup_{i=1}^{k}{l_{i}} A=1A1=i=1kli

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值