天天看點

從零開始_學_資料結構(一)——算法的基本概念

從零開始_學_資料結構(一)——算法

算法的定義:

解決問題的方法。

對于同一個問題,一個好的算法比一個差的算法,效率更高,更節約資源。

for computer:算法是解決特定問題的求解步驟的描述,在計算機中,表示指令的有限序列,每條指令表示一個或者多個操作。

簡單來說,算法就是輸入代碼,告訴計算機,你應該怎麼解決這個問題。

算法的特性:

(1)輸入和輸出。

       光算出結果但不輸出結果,跟沒算沒差別;要計算,總得有資料,不然沒法計算。

(2)有窮性:

       能出結果,并且在接受時間之内出結果。如果時間太長,這個算法往往就沒什麼意義了。

(3)确定性:

       同樣的資料,同一個算法,出同樣的結果。

(4)可行性;

       如果一個算法太複雜,都沒辦法變成代碼給計算機,那肯定不行。

一個好的算法的要求:

(1)正确。

對于計算的資料,能得到預期的結果。

①首先得做到程式沒問題,能運作;

②其次得對于計算的資料,能得到符合要求的結果;

③更好的算法,對于非法資料,能得出滿足要求的結果(比如說提示有問題);

④對于專門用于為難人的資料,也能有滿足要求的輸出結果;

至少要做到第②步

(2)可讀性

代碼和算法,起碼讓人能讀懂,不然寫代碼的人都不明白自己寫的代碼,如果代碼出問題,那麼也沒辦法鑒别出來。

(3)健壯性

面對不符合要求(非法)的資料,能夠适當的處理。

例如,我們正常處理的一組資料裡,隻有正數,比如說我們使用unsigned int類型。那麼假如有負數輸入,如果沒有代碼去鑒别這些,那麼就容易出現出乎意料的結果。

(4)速度快,用記憶體小

假如計算一組1gb大小的資料,同樣的處理器

①假如一個算法用1分鐘,另一個算法用1小時,顯然前者更好;

②假如一個算法用10mb記憶體,另一個算法用1gb記憶體,顯然前者更好。

在實際中,有時候會出現對于較少的資料來說,某一種算法更好,對于較多的資料來說,另一種算法更好。是以,選擇哪種算法,要根據實際情況而決定。

算法效率的度量方法:

(1)事後統計:

簡單直接,運作一遍就知道。

他到底用1秒還是100秒,用10mb記憶體還是500mb記憶體,一目了然。

缺點:假如需要1天甚至10天難道也要這麼做?肯定是不靠譜的。是以,一般是不會使用這種方法的。

(2)事前分析估算方法:

這個方法,簡單來說,就是:

①預估資料量(比如100萬個資料)

②根據算法計算其運作次數(比如說3行代碼,一般是運算3次的。但若涉及到循環,那麼就是循環代碼的行數*循環次數)

③機器執行代碼的速度(計算求和與在一串字元串中插入一個字元,其執行速度自然是有差別的。而且,機器配置不同,運作同樣指令其時間也是有差別的)

由于③是不可控的,是以一般不考慮。

而①是我們要為之服務的對象,是以是需要考慮,但無法選擇的;

隻有②,是我們最需要關心的内容。

以循環為例:

int total;

for(int i=0; i < n; i ++)

{

       cin>>data[i];

       data[i]*=2;

       data[i]+=5;

       total += data[i];

}

       cout<< total;

//假如cin是讀取某個檔案的int類型的資料(實際上不能這麼寫)

首先,循環範圍内有4條指令。是以,當data數組裡有n個元素時,這個代碼将執行4*n + 2次,4*n是循環,1是int total,另一個1是cout<<total。

如果我們使用另外一個方法:

for(int i=0; i<n; i++)

       total+=data[i];

total=total*2+5*n;

cout<<total;

//注意,這裡的cin隻是用來意會,實際應用ifstream類變量來讀取檔案

與上面相比,這個算法需要執行的次數是:2*n+3次。

假如n=100萬時,第一個算法需要執行的次數是400萬+2次,第二個算法執行的次數是200萬+3次,節約了幾乎50%的時間。

這樣的話,哪個算法好,通過簡單的預估執行代碼的次數,一目了然。

當然了,實際中,會因為代碼不同,不同的代碼的效率略有差異,但是顯然執行次數更少的代碼,更剩時間,效率也更高。

除了以上兩種以外,還有雙重for循環(即外層是一個for循環,執行n次;然後在外層的for循環内部還有一個for循環,執行m次,如代碼);

for (int i=0; i < n ; i++)

       for(int j=0; j<m; j++)

              total=total+m+n;

這樣一段代碼,其執行次數将為m*n次(假如m=n的話,可以認為其運作次數為n的乘方)。

總結一下:

不同的算法(假設代碼行數為m),針對不同的資料量(n),将執行不同的次數:

有的次數為n,有的次數為m,有的為m*n,有的為n*n,也有其他的。

一般來說,需要特别設計算法的,資料量都比較大(比如說幾萬甚至更多),代碼行數相對就偏少(也許隻有幾行或者幾十行)。

是以,m次最好,n次其次,m*n次再次,n*n次最次。

四個字:避免乘方

如下表:

資料量n

算法a執行代碼(n2)次

記作f(a)=

算法b執行代碼(3n)次

f(b)=

1

3

2

4

6

9

10

100

30

一萬

三百

10000

一億

三萬

假如需要特别的算法,一般來說,原因是資料量都很大(幾百mb,幾gb,幾十幾百gb,甚至tb級别),隻有一個好的算法,才能做到高效率的完成任務。

是以,需要盡量避免那種執行的代碼次數,為資料量的乘方甚至n次方這樣的情況。

以下概念有印象就行,反正知道怎麼用最重要,單純背概念毫無意義。

概念:函數的漸進增長

如上表,當n<3時,f(a)>f(b);

當n=3時,f(a)=f(b);

當n>3時,f(a)>f(b);

假如對于給定的函數,存在一個整數n,使得對于所有n>n的情況,f(a)總是大于f(b),那麼我們就說,f(a)的漸進增長快于f(b)

假如兩個函數,都存在幂的情況(比如說n的2次方或者3次方)。

那麼主要關心其最高階的部分(因為假如n=10,其2次方的值,僅僅隻有3次方的十分之一,n越大,差距越大。即使一個是n的平方+一萬,另一個是n的3次方,隻需要n=100時,前者的效率便能輕松高于後者)。

是以:避免使用需要執行n的m次方次代碼的算法。

除此之外,一個算法的需要執行代碼的次數還有對數的情況、或者是指數的情況。

個人意見是,這裡有個概念就可以,暫時不需要深究,畢竟這裡學的是資料結構。

算法需要考慮最壞的情況:

例如我們設計一個算法,然後通過這個算法去試圖去尋找一個檔案裡的資料。

那麼①這個資料可能存在于檔案的開始,也可能存在于資料的結尾;

②如果這個算法是逐個位元組尋找,那麼假如在開始,就會立刻找到,但若假如在結尾,那麼可能需要找很久很久。而這個

最壞的情況(需要很多時間),是需要我們考慮的。

③特别是當這個檔案很大很大時(周遊整個檔案需要很多時間),那麼考慮如何解決這個問題,是很重要的(是以可能需要優化算法)。比如說,假如這個資料存在的開始位址,是0xxxxx0這樣,我們就可以考慮一次讀取十六個位元組,然後先對比第一個,符合後在繼續對比,這樣的話,就相對節約了很多時間。

④除了優化算法外,我們還應該考慮平均時間。例如,當算法無法優化時,那麼最好的情況下,這個算法需要多久,最差的情況下,這個算法需要多久,二者平均起來,往往就是普通的情況下,需要的時間。

而這個 平均時間,是這個算法的期望運作時間,是需要我們關注的。

算法的存儲空間:

當使用算法時,除了需要花時間外,可能還需要使用一定的存儲空間。

例如:

①使用一個10個元素的int數組來存儲計算的内容,由于一個int是4位元組,是以需要使用40位元組來存儲這些内容;

②可能還要建立一個臨時的變量(堆或者棧)用于存儲算法中建立的臨時對象,而在提高效率方面,建立的臨時對象是有一定意義的;

③有時候,對于算法,要求其最多隻能使用多少空間,算法在運作過程中,不能超出這個界限。

是以,對于存儲空間的使用,也是有必要考慮的。

 第二章:樹

http://blog.csdn.net/qq20004604/article/details/50869632

繼續閱讀