五大基础算法

关于基础算法,就是我们设计算法时首先应该想到的几类算法。在本文我主要会介绍的基础算法有:枚举法、递归法、分治法、贪心法、模拟法。

一、枚举法


枚举法,本质上就是搜索算法。

基本思想:
枚举也称作穷举,指的是从问题所有可能的解的集合中一一枚举各元素。
用题目中给定的检验条件判定哪些是无用的,哪些是有用的。能使命题成立。即为其解。
优缺点:
优点:算法简单,在局部地方使用枚举法,效果十分的好
缺点:运算量过大,当问题的规模变大的时候,循环的阶数越大,执行速度越慢
例题解析
例题1、百钱买百鸡问题:有一个人有一百块钱,打算买一百只鸡。到市场一看,公鸡一只3元,母鸡一只5元,小鸡3只1元,试求用100元买100只鸡,各为多少才合适?
根据题意我们可以得到方程组:

  3X + 5Y + Z/3 = 100;
  X + Y + Z = 100;
  (100 > X,Y,Z > 0, Z%3 == 0),根据这两个公式,我们可以写出最为简单的代码,一一列举所有解进行枚举

代码:


int x,y,z;
for( x = 0; x < 100; x++ )
   for( y = 0; y < 100 ; y++ )
       for( z = 0; z < 100; )
         {
           if( x + y + z == 100 && 3 * x + 5 * y + z / 3 == 100 )
                {
                  cout << x << " " << y << " " << z << endl;
                }
                z += 3;
            }
然而我们可以根据已知条件来进行优化代码,减少枚举的次数:

三种鸡的和是固定的,我们只要枚举二种鸡(x,y),第三种鸡就可以根据约束条件求得(z = 100 - x - y),这样就缩小了枚举范围。
另外我们根据方程特点,可以消去一个未知数,得到下面
4X + 7Y = 100;
X + Y + Z = 100;
(X,Y,Z > 0, Z%3 == 0),=>>    0 <= x < = 25因此代码可以优化为下面这样子:
for( x = 0; x <= 25; x++ )
{
            y = 100 - 4 * x;
            if( y % 7 == 0 && y >= 0 )
            {
                y /= 7;
                z = 100 - x - y;
                if( z % 3 == 0 && 3 * x + 5 * y + z / 3 == 100  )
                   cout << x << " " << y << " " << z << endl;
            }
}

采用枚举的方法进行问题求解,需要注意3个问题:
简单数学模型,数学模型中变量数量尽量少,它们之间相互独立。这样问题解的搜索空间的维度就小,反应到程序代码中,循环嵌套的层次就会少。我们上面从3维优化到一维。
减少搜索的空间。利用已有知识,缩小数学模型中各个变量的取值范围,避免不必要的计算。反应到程序代码中,循环体被执行的次数少
采用合适的搜索顺序。对搜索空间的遍历顺序要与数学模型中的条件表达式一致。

二、递归法


递归法是设计和描述算法的一种有力的工具,它在复杂算法的描述中被经常采用。
设一个未知函数f,用其自身构成的已知函数g来定义, 上述这种用自身的简单情况来定义自己的方式成为递归定义。
3个要素:
递归边界:算法要有一个边界出口,能结束程序
参数收敛:每次调用参数都是收敛于递归边界

自身调用
递归函数常使用堆栈,算法效率低,费时和费内存
递归按其调用方式分为直接递归和间接递归。所谓直接递归是指递归过程P直接自己调用自己;间接递归是指P包含另一个过程D,D调用P。
递归算法适用的一般场合

按递归定义的数据定义形式

例如,Fibonacci数列:1,1,2,3,5,8,13,21,34,55
按递归定义的数据之间的关系(数据结构)
树的遍历、图的搜索
例题1:Hanoi题问题
Hanoi塔是根据一个传说形成的一个问题:
有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至B杆:
每次只能移动一个圆盘;
大盘不能叠在小盘上面。
问:如何移?最少要移动多少次?
例题2:整数划分问题
整数划分问题是算法中的一个经典问题之一,有关这个问题的讲述在讲解到递归时基本都会有涉及。
将整数n表示成一系列正整数之和:n=n1+n2+n3+...+nk,其中n1>=n2>=n3>=...>=nk,k>1。求n的不同划分个数。

例题3:全排列问题
已知集合R={r1,r2,r3,...,rn},请设计一个算法生成R中n个元素的全排列。
全排列:从n个不同元素中任取m(m<=n)个元素,按照一定的顺序排列起来,叫做从n个元素中取出m个元素的一个排列。当m=n时,所有的排列情况叫全排列。
令Rj=R-{rj}
记集合X中元素的全排列记为perm(X)。那么(rj)perm(X)表示在全排列perm(X)的每一种排列前加上前缀rj所得到的排列,所以,R的全排列可归纳定义如下:
当n=1时,perm(R)=(r)
当n>1时,perm(R)由(r1)perm(R1),(r2)perm(R2) ... (rn)perm(Rn)构成

三、分治法


有许多算法在结构上是递归的:为了解决一个给定问题,算法要一次或多次地调用其自身来解决相关的子问题。这些算法通常采用分治策略:将原问题分成n个规模较小而结构与原问题相似的子问题。递归地对这些子问题求解,然后合并其结果就得到原问题的解。n=2时的分治法又称为二分法。
步骤
分解——将原问题分解成一系列子问题
解决——递归地解各子问题
合并——将子问题的结果合并成原问题的解
例题解析
例题1:快速排序
输入数组为a[p:r],通过排序算法使其有序。
思路:
划分:以a[q]为基准元素将a[p:r]划分成3个部分:a[p:q-1],a[q],a[q+1:r]。使a[p:q-1]中的任一元素都小于a[q],a[q+1:r]中的任一元素都大于等于a[q],q是在划分过程中确定的。
通过递归调用,对a[p:q-1]和a[q+1:r]进行同样的操作
例题2:归并排序
理牌时一种可行的方案是将一副牌一分为二,一人洗一半,然后合并起来,请将该方法用程序表示出来。
思路:将待排序的元素分成大致相同的2个子集合,分别对2个子集合进行排序,最终将排序的子集合合并成排好序的集合。


四、贪心法


贪心法:即每次选择最优值,从而达到全局最优的方法,它是从问题的某一个初始解出发,向给定的目标递推。推进的每一步做一个当时看似最佳的贪心选择,不断地将问题实例归纳为更小的相似的子问题,并期望通过所作的局部最优选择产生一个全局最优解。
例题1 删数问题
键盘输入一个高精度的正整数n(<=240位),去掉任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的n和s,寻找一种方案,使得剩下的数最小。
思路 : 首先必须注意,试题中N的有效位为240位,而计算机中整数有效位充其量也不过11位,无论如何都不能达到试题的数值要求。因此,必须采用可含256个字符的字串来代替整数。
以字串形式输入N,使用尽可能逼近目标的贪心法来逐一删除其中的s个数符,每一步总是选择一个使剩下的数最小的数符删除。
考虑只删一个数的情况,最优解是删除出现的第一个左边>右边的数,因为删除之后高位减小,很容易想...那全局最优解也就是这个了,因为删除S个数字就相当于执行了S次删除一个数,因为留下的数总是当前最优解。


五、模拟法


模拟法是最直观的算法,通常是对某一类事件进行描述,通过事件发生的先后顺序进行输入输出。模拟法只要读懂问题的要求,将问题中的各种事件进行编码,即可完成。
模拟法(Simulation)主要是在考验程式设计人员编写程式码的功力,而非考验程式设计者的急智和创意。Simulation可说是程式设计的基本功──问题说得清清楚楚,不用设计复杂的演算法,只要照着规定做就好。这类题目很适合程式设计的初学者。
Simulation的问题有时相当难缠。若是问题规定的错综复杂,那么写起程式码就会相当累人。若是一不小心犯了错误,只能望着那长篇大论、杂乱无章的程式码,从中找出错误所在,相当痛苦。
另外,有一些目前尚未解决的经典题目,像是著名的Josephus Problem,由于目前没有好的演算法,所以大家遇到了这种问题,就只能乖乖的照着题目的要求来做。这一类的题目也就变成了Simulation的题目。

egP1009 [NOIP1998 普及组] 阶乘之和

#include<iostream>
using namespace std;
int a[101],b[101];
void f(int x){
    int temp=0;
    for(int i=100;i>=0;i--){
        a[i]=a[i]*x+temp;
        temp=a[i]/10;
        a[i]=a[i]%10;
    }
}
void p(){
    int temp=0;
    for(int i=100;i>=0;i--)
    {
        b[i]=b[i]+a[i]+temp;
        temp=b[i]/10;
        b[i]=b[i]%10;
    }
}
int main(){
    int n;cin>>n;
    a[100]=1;b[100]=1;
    for(int i=2;i<=n;i++){
        f(i);
        p();
    }
    int w;
    for(int i=0;i<=100;i++)
    {
        if(b[i]!=0)
        {
            w=i;
            break;
        }
    }
    for(int i=w;i<=100;i++)
       cout<<b[i];
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值