学习笔记|算法设计与分析

算法设计与分析 课程笔记

注:笔记摘自《计算机算法设计与分析》王晓东 第五版

第二章 递归与分治策略

2.1 递归的概念

  • 递归算法:一个直接或间接地调用自身的算法

  • 递归函数:使用函数自身给出定义的函数

  • 递归方程:对于递归算法,一般可把时间代价表示为一个递归方程

  • 解递归方程最常用的方法是进行递归扩展
    阶乘函数

    1. 边界条件
    2. 递归关系

n ! = { 1 , n = 1 n ( n − 1 ) ! , n > 1 n!=\begin{cases} 1,n=1\\ n(n-1)!,n>1\end{cases} n!={ 1,n=1n(n1)!,n>1

Fibonacci数列
F ( n ) = { 1 , n = 0 1 , n = 1 F ( n − 1 ) + F ( n − 2 ) , n > 1 F(n)=\begin{cases} 1,n=0\\ 1,n=1\\F(n-1)+F(n-2),n>1\end{cases} F(n)=1,n=01,n=1F(n1)+F(n2),n>1

初始条件递归方程是递归函数的两个要素

Ackerman函数
{ A ( 1 , 0 ) = 2 A ( 0 , m ) = 1 , m > = 0 A ( n , 0 ) = n + 2 , n > = 2 A ( n , m ) = A ( A ( n − 1 , m ) , m − 1 ) , n 、 m > = 1 \begin{cases} A(1,0)=2\\ A(0,m)=1,m>=0\\A(n,0)=n+2,n>=2\\A(n,m)=A(A(n-1,m),m-1),n、m>=1\end{cases} A(1,0)=2A(0,m)=1,m>=0A(n,0)=n+2,n>=2A(n,m)=A(A(n1,m),m1),nm>=1

  • Ackerman函数为双递归函数
  • 双递归函数:一个函数及它的一个变量由函数自身定义
  • A(n,m)的自变量m的每一个值都定义了一个单变量函数

注:需推一遍证明

排列问题

设R={r1,r2,…,rn}是要进行排列的n个元素,Ri=R-{ri}。集合X中的元素的全排列记为Perm(X)。

(ri)Perm(X)表示在全排列Perm(X)的每个排列前加上前缀ri得到的排列。

R的全排列定义如下:

​ 当n=1时,Perm®=®,其中r是集合R中唯一的元素;

​ 当n>1时,Perm®由(r1)Perm(R1),(r2)Perm(R2),…,(rn)Perm(Rn)构成。

为便于理解,以{1,2,3,4,5,6}为例:

1 2 3 4 5 6 --> 1 2 3 4 5 6 --> 1 2 3 4 5 6 --> 1 2 3 4 5 6 --> 1 2 3 4 5 6 --> 1 2 3 4 5 6

​ --> 1 2 3 4 6 5 --> 1 2 3 4 6 5

template<class Type>
void Perm(Type list[],int k, int m){			//产生list[k:m]的所有排列
	if(k==m){									//只剩下一个元素,到达递归的最底层
		for (int i=0;i<=m;i++)
            cout << list[i];
        cout << endl;
    }
    else{										//还有多个元素待排列,递归产生排列
        for (int i=k;i<=m;i++)
        {
            Swap(list[k],list[i]);
            Perm(list,k+1,m);
            Swap(list[k],list[i]);				//复位,保证所有元素都能依次做前缀
		}
    }
}

template<class Type>
inline void Swap(Type &a, Type &b)
{
    Type temp=a;
    a = b;
    b = temp;
}

整数划分问题

将正整数n表示成一系列正整数之和,n=n1+n2+…+nk(n1≥n2≥…≥nk,k≥1)。

正整数n的不同的划分个数称为正整数n的划分数,记为p(n)。以正整数6为例:

6;

5+1;

4+2;4+1+1;

3+3;3+2+1;3+1+1+1;

2+2+2;2+2+1+1;2+1+1+1+1;

1+1+1+1+1+1。

将最大加数n1不大于m的划分个数记作q(n,m),建立如下递归关系:
q ( n . m ) = { 1 , n = 1 、 m = 1 q ( n , n ) , n < m 1 + q ( n , n − 1 ) , n = m q ( n , m − 1 ) + q ( n − m , m ) , n > m > 1 q(n.m)=\begin{cases} 1,n=1、m=1\\q(n,n),n<m\\1+q(n,n-1),n=m\\q(n,m-1)+q(n-m,m),n>m>1\end{cases} q(n.m)=1,n=1m=1q(n,n),nm1+q(n,n1),n=mq(n,m1)+q(nm,m),nm1

  1. q(n,1)=1,n≥1。当最大加数n1不大于1时,任何正整数n只有一种划分形式,即n=1+1+…+1
  2. q(n,m)=q(n,n),m≥n。最大加数n1不能大于n。
  3. q(n,n)=1+q(n,n-1)。正整数n的划分由n1=n的划分和n1≤n-1的划分组成;n1=n时,划分仅有一种。
  4. q(n,m-1)+q(n-m,m),n>m>1。正整数n的最大加数n1不大于m的划分由n1=m的划分和n1≤m-1的划分组成。

Hanoi塔问题

设a,b,c是3个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座a上的这一叠圆盘移到塔座b上,并仍按同样顺序叠置。在移动圆盘时遵守:

  1. 每次只移动1个圆盘
  2. 任何时刻都不允许将较大的圆盘压到较小的圆盘之上
  3. 在满足1和2的前提下,可将圆盘移动至a,b,c中任一塔座上

T ( n ) = { 2 T ( n − 1 ) + 1 , n > 1 1 , n = 1 T(n)=\begin{cases}2T(n-1)+1,n>1\\1,n=1\end{cases} T(n)={ 2T(n1)+1,n>11,n=1

void hanoi(int n,a,b,c)				//a上n个盘借助c移到b
{
    if (n==1)						//将第1个盘从a移到b
        move(1,a,b);
    else{
		hanoi(n-1,a,c,b);			//将第1至n-1个盘,从a移到c
        move(n,a,b);				//将第n个盘从a移到b
        hanoi(n-1,c,b,a);			//将第1至n-1个盘从c移到b
    }
}

使用这种递归调用的关键在于建立递归调用工作栈

递归程序逐层调用需要分配存储空间,一旦某一层被启用,就要为之开辟新的空间。当一层执行完毕,释放相应空间,退到上一层。

递归优点:结构清晰,可读性强

递归缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多

解决方法:在递归算法中消除递归调用,使其转化为非递归算法。可采用一个用户定义的栈来模拟系统的递归调用工作栈。

2.2 分治策略

基本思想

将问题分解成若干个子问题,然后求解子问题。分治策略可以递归地进行,即子问题仍然可以用分值策略来处理,但最后的问题要非常基本而简单。

divide-and-conquer(P){
	if (|P| <= n0)									//直接解决小规模问题
		adhoc(P);
	divide P into smaller subinstances P1,P2,…,Pk	//分解问题
    for (i = 1; i<=k; i++)							//递归解各子问题
        yi=divide-and-conquer(Pi);					//将格子问题的解合并为原问题的解
    return merge(y1,y2,…,yk)
}

代价分析
{ O ( 1 ) , n = 1 k T ( n / m ) + f ( n ) , n > 1 \begin{cases}O(1),n=1\\kT(n/m)+f(n),n>1\end{cases} { O(1),n=1kT(n/m)+f(n),n>1
T ( N ) = a T ( N / b ) + N k , a ≥ 1 , b > 1 T(N)=aT(N/b)+N^k,a≥1,b>1 T(N)=aT(N/b)+Nk,a1,b>1
T ( n ) = { O ( N t ) , t = l o g b a , a > b k O ( N k l o g N ) , a = b k O ( N k ) , a < b k T(n)=\begin{cases}O(N^t),t=log_ba,a>b^k\\O(N^klogN),a=b^k\\O(N^k),a<b^k\end{cases} T(n)=O(Nt),t=logba,a>bkO(NklogN),a=bkO(Nk),a<bk

2.3 二分搜索技术

给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。

  1. 顺序搜索方法:最好时1次,最坏时n次

  2. 二分搜索方法

    基本思想

    将n个元素分成个数大致相同的两半,取a[n/2]与x作比较。

    • 如果x=a[n/2],则找到x,算法终止;
    • 如果x<a[n/2],则在数组a的左半部继续搜索x;
    • 如果x>a[n/2],则在数组a的右半部继续搜索x。
template<class Type>
int BinarySearch(Type a[],const Type& x, int l, int r)	//l=0,r=n-1
{//找到x时返回其在数组中的位置,否则返回-1
    while(r >= l){
        int m = (l+r)/2;
        if(x==a[m])
            return m;
        if (x<a[m])
            r=m-1;
        else
            l=m+1;
    }
    return -1;
}

T ( n ) = { T ( n / 2 ) + O ( 1 ) , n > 1 O ( 1 ) , n = 1 T(n)=\begin{cases}T(n/2)+O(1),n>1\\O(1),n=1\end{cases} Tn={ T(n/2)+O(1),n>1O(1),n=1

求解: T ( n ) = l o g n T(n)=logn T(n)=logn

复杂性: O ( l o g n ) O(logn) O(logn)

2.4 大整数的乘法

设X和Y都是n位二进制整数,计算它们的乘积 X Y XY XY

小学方法: O ( n 2 ) O(n^2) O(n2)

将n位二进制整数 X X X Y Y Y分为2段,每段的长为n/2位,由此 X = A × 2 t + B , t = n / 2 X=A×2^t+B,t=n/2 X=A×2t+B,t=n/2 Y = C × 2 t + D , t = n / 2 Y=C×2^t+D,t=n/2 Y=C×2t+D,t=n/2

由此, X Y = ( A × 2 t + B ) ( C × 2 t + D ) = A C × 2 t + ( A D + B C ) × 2 t + B D , t = n / 2 XY=(A×2^t+B)(C×2^t+D)=AC×2^t+(AD+BC)×2^t+BD,t=n/2 XY=(A×2t+B)(C×2t+D)=AC×2t+(AD+BC)×2t+BD,t=n/2

分治法: X Y = A C × 2 n + ( ( A − B ) ( D − C ) + A C + B D ) × 2 t + B D , t = n / 2 XY=AC×2^n+((A-B)(D-C)+AC+BD)×2^t+BD,t=n/2 XY=AC×2n+((AB)(DC)+AC+BD)×2t+BD,t=n/2

仅需做3次n/2位整数的乘法(AC、BD和(A-B)(D-C))、6次加减法和2次移位。
T ( n ) = { O ( 1 ) , n = 1 3 T ( n / 2 ) + O ( n ) , n > 1 T(n)=\begin{cases}O(1),n=1\\3T(n/2)+O(n),n>1\end{cases} Tn={ O(1),n=13T(n/2)+O(n),n>1
易求得,其解为 T ( n ) = O

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值