标准算法整理IV

(一)分治法

概念:将一个难以直接解决的大问题,分割成一些规模较小的几个相似问题,以便各个击破,分而治之。
一般算法设计模式:
Divide-and-Conquer(n) //n为问题规模
{
if (n<=n0) //n0为可解子问题的规模
{解子问题;
return(子问题的解);}
for(i=1;i<=k;i++) //分解为较小的k个子问题
{分解原问题为更小的子问题Pi;
yi=Divide-and-Conquer(|Pi|);} //递归解决Pi
T=MERGE(y1,y2,…,yk); //合并子问题
return(T);
}
注:二分查找,二叉排序树查找等算法无须合并子问题
例:(金块问题)老板有一袋金块,最优秀的雇员得到其中最重的一块,最差的雇员得到其中最轻的一块,利用分治法找出最重和最轻的金块。

#include <stdio.h>
#define N 5

//用分治法求最大最小数(二分法+递归)
int maxmin(int i, int j, float *max, float *min, float a[])
{
    int mid;
    float lmax,lmin,rmax,rmin;
    if(i==j) //组内有一个元素
    {
        *max=a[i];
        *min=a[i];
    }
    else if(i==j-1) //组内有2个元素
        if(a[i]<a[j])
        {
            *max=a[j];
            *min=a[i];
        }
        else
        {
            *max=a[i];
            *min=a[j];
        }
    else
    {
        mid=(i+j)/2;
        maxmin(i,mid,&lmax,&lmin,a);
        maxmin(mid+1,j,&rmax,&rmin,a);  //递归分解
        if(lmax>rmax)
            *max=lmax;
        else
            *max=rmax;
        if(lmin>rmin)
            *min=rmin;
        else
            *min=lmin;   //溯洄合并
    }
    return 0;
}
int main() {
    int i;
    float max,min,a[N];
    printf("array a=?");
    for(i=0;i<N;i++)
        scanf("%f",&a[i]);
    maxmin(0,N-1,&max,&min,a);
    printf("max=%.1f,min=%.1f\n",max,min);
    return 0;
}

(二)贪婪法

概念:又名登山法,根本思想是逐步获得最优解,以逐步的局部最优,达到最终的全局最优。要求问题具有无后向性,即某状态以后的过程不会影响以前的状态,只与当前状态有关。
算法框架:
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
利用可行的决策,求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
例:把一个真分数表示为最少的埃及分数之和(埃及分数:分子为1)
数学模型:
将一个真分数F表示为A/B:做B/A的整除运算,商为D,余数为
K(0<Κ<Α),他们之间的关系及导出如下:
B=A*D+K
B/A=D+K/A < D+1
A/B > 1/(D+1)
记C=D+1,这样就找到了分数F所包含的“最大的”埃及分数就是1/C。进一步计算,A/B-1/C=(A*C-B)/(B*C),也就是继续要解决的是关于分子为A=A*C-B,分母为B=B*C的子问题(A < B)

#include <stdio.h>

int main() {
    long a,b,c;
    printf("a,b=?");
    scanf("%ld%ld",&a,&b);
    if(a>=b)
        printf("input error");
    else if(b%a==0)
        printf("%ld/%ld=1/%ld",a,b,b/a);
    else
    {
        printf("%ld/%ld=",a,b);
        do{
            c=b/a+1;
            a=a*c-b;
            b*=c;
            printf("1/%ld+",c);
            if(b%a==0)
            {
                printf("1/%ld",b/a);
                a=1;
            }
        }while(a!=1);
    }
    printf("\n");
    return 0;
}

(三)动态规划

概念:多阶段最优化决策问题的过程。
动态规划=贪婪策略+递推(降阶)+存储递推结果
使用问题特征:
1. 最优化原理:问题的最优解所包含的子问题的解也是最优的
2. 无后向性:某阶段状态一旦确定,就不受这个状态以后决策的影响
3. 子问题重叠:子问题之间是不独立的,一个子问题在下阶段决策中可能被多次使用到。对有分解过程的问题还表现在自顶向下分解问题时,每次产生的子问题并不总是新问题,有些子问题会反复出现多次。
基本思想:
把求解的问题分成许多阶段或子问题,然后按顺序求解各子问题。前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃掉其他局部解,依次解决各子问题,最后一个子问题就是初始问题的解。
基本步骤:
1. 划分阶段
2. 选择状态
3. 确定决策并写出状态转移方程
例:(数塔问题)一个数塔,从顶部出发,在每一个结点可以选择向左或向右走,一直走到底层,要求找出一条路径,使数值和最大
9
12 15
10 6 8
2 18 9 5
19 7 10 4 16
自下而上逐层决策,第一次决策递推出第四层和第五层的和(选择两种路径中数字和较大的结果,储存在第四层相应位置),以此类推。(若只考虑一层为贪婪法,在本题中失效)
动态规划过程:
59
50 49
38 34 29
21 28 19 21
19 7 10 4 16

#include <stdio.h>

int main() {
    int i,j,k,n,flag,b,a[50][50],d[50][50],t[50][50];       //t数组存储求解方向
    printf("n=?");
    scanf("%d",&n);//数塔行数
    printf("array a=?");
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
        {
            scanf("%d",&a[i][j]);
            d[i][j]=a[i][j];
            t[i][j]=0;
        }
    for(i=n-1;i>=1;i--)
        for(j=1;j<=i;j++) //从最低两层开始使用贪婪法
        {
            if(d[i+1][j]>d[i+1][j+1])
            {
                d[i][j]+=d[i+1][j];
                t[i+1][j]=1;
            }
            else
            {
                d[i][j]+=d[i+1][j+1];
                t[i+1][j+1]=1;
            }
        }
    printf("max=%d\n",d[1][1]);
    printf("%d",a[1][1]);
    for(k=1,i=2;i<=n;i++)
    {
        j=k;
        if(t[i][j]==1)
        {
            printf("->%d",a[i][j]);
            k=j;
        }
        else
        {
            printf("->%d",a[i][j+1]);
            k=j+1;
        }
    }
    printf("\n");
    return 0;
}

另解:

#include <stdio.h>

//结构体二维数组a存储数塔数据,递推结果,求解方向
int main() {
    int i,j,n;
    struct {
        int c1,c2,c3;
    }a[50][50];
    printf("n=?");
    scanf("%d",&n);//数塔行数
    printf("array a=?");
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
        {
            scanf("%d",&a[i][j].c1);
            a[i][j].c2=a[i][j].c1;
        }
    for(i=n-1;i>=1;i--)
        for(j=1;j<=i;j++) //从最低两层开始使用贪婪法
        {
            if(a[i+1][j].c2>a[i+1][j+1].c2)
            {
                a[i][j].c2+=a[i+1][j].c2;
                a[i][j].c3=0;
            }
            else
            {
                a[i][j].c2+=a[i+1][j+1].c2;
                a[i][j].c3=1;
            }
        }
    printf("max=%d\n",a[1][1].c2);
    for(j=i=1;i<=n-1;i++)
    {
        printf("%d->",a[i][j].c1);
        j+=a[i][j].c3;
    }
    printf("%d\n",a[n][j].c1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值