我学习算法的心得

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jinxlzc/article/details/83661433

今天我想给大家分享一下我学习算法的一些心得,起因是有学弟问我两道题,在给他讲题的时候给他讲了一些我学习或者说研究算法的心得,我觉得可能会对大家有帮助所以想分享给大家。

先上题说话吧:


第一题:a和n均为1到9中的一个数字,编写程序请输入a和n,求s=a+aa+aaa+···+aaa(n个a)。

大部分的同学看到这一道题的思路是,用循环依次生成a,aa,aaa,···,aaa(n个a),然后使用一个计数器将他们求和。这个思路用代码实现是如下的(Java):

public class T1   
{  
    public static void main(String[] args)   
    {  
        int s=fun(6,9);  
        System.out.println(s);  
    }  
    static int getnum(int i,int a)  
    {  
        int res=0;  
        for(int j=i;j>=0;j--)  
        {  
            res=(int) (res+a*Math.pow(10, j));  
        }  
        System.out.println(res);  //输出
        return res;  
    }  
    static int fun(int a,int n)  
    {  
        int res=0;  
        for(int i=0;i<n;i++)  
        {  
            res=res+getnum(i,a);  
        }  
        return res;  
    }  
} 

使用这个方法时使用math.pow()方法,在c语言中是在math.h中,意思是求10的n次方。

而我是这样分析这道题的,题目中的算式转换为竖式如下(以n为五为例):                                           

我们很容易发现从个位刚高位走,a的个数开始是n然后依次递减1,但是位权从1开始然后依次增大10倍。根据这一思路编写如下代码:

import java.util.Scanner;  
public class T1 
{  
    public static void main(String[] args)  
    {  
        Scanner sc=new Scanner(System.in);  
        int a,n,sum=0;  
        int j=1;  
        a=sc.nextInt();  //输入
        n=sc.nextInt();  
        for(int i=n;i>0;i--)  
        {  
            sum=sum+a*j*i;  
            j=j*10;  
        }  
        System.out.println(sum);  
    }  
}  

这样同样实现了程序,相比方法一不但代码精简,并且没有调用math.pow()方法,使逻辑更简单,并且大大降低了资源占用。


第二道题:将一张100元的钱按如下要求换成5元、1元、5角的零钱。

  1. 每种零钱的张数不得少于1张
  2. 零钱的总张数为100张

编写程序输出总共有多少种换法,并输出每种换法。

这道题大部分同学的解题方法是建立三层100次的循环去将所有组合去使用if语句验证条件,如果条件符合,计数并输出,代码实现如下:

public class T2   
{  
    public static void main(String[] args)   
    {  
        int res=0;  
        for(int wujiao= 1;wujiao<=100;wujiao++)  
        {  
            for(int yiyuan=1 ;yiyuan<=100;yiyuan++)  
            {  
                for(int wuyuan= 1;wuyuan<= 100;wuyuan++)  
                {  
                    if(wujiao+ 2*yiyuan+ 10*wuyuan==200&&wujiao+yiyuan+wuyuan==100)  
                    {  
                        System.out.println("五角:"+wujiao+"一元:" +yiyuan+ "五元:" +wuyuan);  
                        res++;  
                    }  
                }  
            }         
        }  
        System. out.println("共"+res+"种");  
    }  
}

这种方法可以实现题目的要求,但是循环高达1M次,计算代价是非常高的。

我分析的解法是,先看最大面额的5元,100=5*20,又因为每个面额至少一张所以5元面额的一共只有1~18张的十八种情况,将此作为最外层循环,再看1元的纸币,循环1元纸币次数应从1开始到99张减去外层循环的5元的张数,最后五角的张数则为100张减去外层循环的5元的张数和次外层的一元的张数,三个面额的个数已知然后if判断其总金额是否符合要求即可,这样把循环次数控制在了1k次以内,代码实现如下:

public class T2 {  
    public static void main(String[] args) {  
        int sum1=100,flag=0;  
        for(int i=1;i<19;i++)  
        {  
            for(int j=1;j<(100-i);j++)  
            {  
                if(j+((100-i-j)*0.5)+i*5==100)  
                {  
                    flag++;  
                    System.out.println(i+" "+j+" "+(100-j-i));  
                }  
            }  
        }  
        System.out.println(flag);  
    }  
}  

那么这种方法就是最优化的方法了吗?不,其实在我第一次看到这道题的时候,我脑海浮现出了一道小学的经典的奥数题:“鸡兔同笼问题”,外层循环内,在判断1元和5角的时候不就是一个鸡兔同笼问题吗,1元5角面额是兔鸡的脚数,个数就是头数,使用假设法可以在不用循环的情况下求出两者分别的个数,如果解为符合题目要求的整数解,则视为符合现实情况,计数输出即可。这样一来循环次数就降到了18次。代码实现如下:

public class  T2{  
    public static void main(String[] args) {  
        int num,j,flag=0;  
        double k;  
        for(int i=1;i<20;i++)  
        {  
            num=(100-i)*1-(100-5*i);  
            k=num/0.5;  
            if((i+k)<=99)  
            {  
                j=100-i-(int)k;  
                flag++;  
                System.out.println(i+" "+j+" "+(int)k);    
            }  
        }  
        System.out.println(flag);  
    }  
}  

目前是我能想到的优化程度最高的优化,如果大家有什么更好的方法也可以去尝试。


那么可能有人要问,把题能做出来不就行了?为什么要去优化算法,研究学习算法有什么必要吗?

当然有必要,首先说当前作为学生,我参加过很多程序设计的比赛,往往选手们比拼的不是说你能不能把题做出来,而是能不能把题通过OJ系统,而且尽量优化时间和空间复杂度来争取更好的排名,比如说程序应在1s内运行完成,程序运行内存不应超过多少。这是在比赛的方面,而在就业的层面,程序优化的意义就更为重要了。一个已经毕业的学长给我讲过这么一个真实的故事,在一个跟通讯有关的公司,需要一个程序来实现实时监控多个区域内很多特殊号码的实时通话位置,因为当时的技术手段有限,所以要机械的将在线的所有手机号和特殊的手机号进行匹配,属于一个多对多的关系,但原算法效率很低,往往成功找到了手机号,通话也已经挂断了,为了提高速度,公司需要花巨资购置更多的服务器进行计算,然而一个聪明的程序员使用了一个巧妙的方法(哈希算法)修改了原程序,让算法使用原来的一半的运算资源达到了预期的结果,使得公司节省下千万元的开支,这就是算法优化创造的价值,如果我们拥有很好的算法能力,还担心找不到高薪的工资吗?

但是还有很多的人认为学习算法属实没有必要,因为现在很多新兴语言的兴起,Java的包,Python的库,MATLAB的函数,里面拥有大量别人已经写好的优秀算法功能,我们跟本没必要去深学算法,我们大二甚至大三的同学现在连冒泡排序都写不出了的人大有人在,因为排序在Java中一个sort函数就完成了,自己没必要去懂。我也曾为此迷茫,但向老师咨询了这一困惑后,老师说,算法好比内功,语言好比招式,当年内功修炼的很好以后,不管你去学习什么武功都可以如鱼得水,我仔细回想确实如此,我最早学习的是Pascal语言,在初中,当时真的是一窍不通,用了一年多的时间钻研简单算法后才豁然开朗,然后无论学c语言还是Java,c#,我感觉我都要比大部分的同学能入门快一些。而且听学长说,在公司,虽然熟练导包调库可以大大提高生产力,但是一些好的公司是鼓励程序员去自己实现一些功能的,这样可以为公司积累专利和公司文化。

那么如和去学好算法,优化程序呢?我有几个我自己的经验:

学好数学是十分关键的,举两个简单的例子,比如说要求1~100的累和,可以使用一个for循环,然后用计数器累加,但是我们知道高斯定理,等差数列求和公式就可以用一个式子省去这一循环,如果累加操作本来就在一个循环里,你们优化的次数是几何倍数减少的。还有一个例子就是求最大公约数和最小公倍数,我们常规的做法是遍历查找,但是如果使用欧几里得发明的辗转相除法短短几步就可以求出两数的最大公约数,又因为两数之积等于两数最大公约数和最小公倍数之积,所以易求最小公倍数。我在第二题中使用鸡兔同笼问题分析也是同样的道理,我们可以使用数学上巧妙的数学算法降低程序算法的复杂度。

大一大二学好专业理论的前导课:数据结构、离散数学、计算机组成原理、数字电子技术等等,特别是数据结构和离散数学(个人观点)。数据结构是程序的灵魂,如果你可以善用数据结构就可以很大程度上升华和优化你的程序。比如说我银行卡里有一笔钱我不知道有多少但我知道数目肯定小于N元,我想要把所有钱存进支付宝,怎么存可以最少的次数尽可能将银行卡中的钱存进去,假设N为400,我如果1块1存最坏情况要存400次,引入数据结构中二分法的思想,我第一次存200,不管能不能存进去第二次存100,第三次50,第四次25,第五次13,第六次7,第七次4,第八次2,第九次1,这样保证可以把所有的钱都存进去,次数大概是log2(n)次,大大减少了平均操作步骤,数据结构中还有各种类型的结构例如队列、链表、栈、二叉树、网等,他们各有缺点,当你熟练的掌握他们,因地制宜的使用他们会使你的程序优化度有质的飞越。离散数学在你做逻辑判断,以及以后数据库的使用上起着至关重要的作用。

 最后也是最重要的一点:纸上得来终觉浅,绝知此事要躬行。问了很多身边的人,大家都觉的学习编程很困难,学不懂导致并不想学。我第一次接触编程是初一,为了参加一个叫NOIP的比赛,当时学的是Pascal语言,在刚接触的一年里,我也是什么都不会,觉的老师讲的是天书,但是我并没有放弃,我尝试推敲那些算法的原理,冒泡排序为什么这样排,找素数为什么这样找,高精度计算为什么要这样求,并举一反三做了大量的练习,终于在一年后感觉是顿悟了一般,思路全开了,并成为我们学校第一个在初二就获得NOIP二等奖的人。我曾一直以为是我得到顿悟的是一年的学习时长,但是现在分析来看,应该是代码量吧,代码量仿佛是一个程序员的经验值,当经验值达到一定的水平,你就会升级,可以刷更厉害的怪,得到更多的收益。

学习编程切勿眼高手低,一定要脚踏实地,有困难也要迎难而上,攻克困难。时不时的给自己定下小目标。我在大一刚开学的时候定下了要在大一第一次计算机等级考试中通过C语言二级,身边的人都不屑的说:“咱们专业不用考,毕业了都是这个水平,过了也没什么用。”但是我觉得这是有意义的事情,用半年的时间自学考试大纲中的知识点,有些知识甚至是大二数据结构大三软件工程的知识。最终在大一下学期3月的考试中通过了,说实话这张证,确实没有什么价值,但是这次备考对我以后的学习打下了坚实的基础,所以建议同学们去报考一些认证,也许它宛如废纸,但是你去为得到它付出努力的过程对你来说是无价的。

大部分时候,知识是抽象的,课本内容滚瓜烂熟写不出来项目的同学大有人在,建议使用项目驱动自己学习,我在大一第二学期参加了学院的计算机技能大赛,自己制定题目,我当时说要用C语言写一个控制台界面的2048小游戏,对当时的我来说应该是个不小的挑战,我在这个项目开发的时候不断发现问题,解决问题,发现问题,解决问题,来回反复,用了近一周的时间完成了,现在去看看我当时写的源码,优化简直不堪入目,但是这次项目经历对我的提升是要比我光看一学期书提升还要大的多的。尽可能参加校内外的各种比赛,输不丢人,怂才丢人,我们只有不断去尝试才能进步。

希望我的分享可以对大家有所帮助,也希望有更多的人去分享自己帮助别人,程序员都有一个共同的美德就是分享,所以有了CSDN,GitHub等好的平台,我们受前人的帮助不断前行,也同样有义务帮助后人,共勉。


本人目前在一个普通的高校读大三,如果本文有什么错误或者建议欢迎指出,我将虚心改正并听取,谢谢。

猜你喜欢

转载自blog.csdn.net/jinxlzc/article/details/83661433