我學習算法的心得

今天我想給大家分享一下我學習算法的一些心得,起因是有學弟問我兩道題,在給他講題的時候給他講了一些我學習或者說研究算法的心得,我覺得可能會對大家有幫助所以想分享給大家。

先上題說話吧:


第一題: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等好的平臺,我們受前人的幫助不斷前行,也同樣有義務幫助後人,共勉。


本人目前在一個普通的高校讀大三,如果本文有什麼錯誤或者建議歡迎指出,我將虛心改正並聽取,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章