第四百一十四節,python常用演算法學習

weixin_34391854發表於2018-04-24

本節內容

  1. 演算法定義
  2. 時間複雜度
  3. 空間複雜度
  4. 常用演算法例項

1.演算法定義 

演算法(Algorithm)是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,演算法代表著用系統的方法描述解決問題的策略機制。也就是說,能夠對一定規範的輸入,在有限時間內獲得所要求的輸出。如果一個演算法有缺陷,或不適合於某個問題,執行這個演算法將不會解決這個問題。不同的演算法可能用不同的時間、空間或效率來完成同樣的任務。一個演算法的優劣可以用空間複雜度與時間複雜度來衡量。

一個演算法應該具有以下七個重要的特徵:

①有窮性(Finiteness):演算法的有窮性是指演算法必須能在執行有限個步驟之後終止;

②確切性(Definiteness):演算法的每一步驟必須有確切的定義;

③輸入項(Input):一個演算法有0個或多個輸入,以刻畫運算物件的初始情況,所謂0個輸     入是指演算法本身定出了初始條件;

④輸出項(Output):一個演算法有一個或多個輸出,以反映對輸入資料加工後的結果。沒       有輸出的演算法是毫無意義的;

⑤可行性(Effectiveness):演算法中執行的任何計算步驟都是可以被分解為基本的可執行       的操作步,即每個計算步都可以在有限時間內完成(也稱之為有效性);

⑥高效性(High efficiency):執行速度快,佔用資源少;

⑦健壯性(Robustness):對資料響應正確。

 

叫賣錄音網
錄音網站

 

2. 時間複雜度

電腦科學中,演算法的時間複雜度是一個函式,它定量描述了該演算法的執行時間,時間複雜度常用大O符號(大O符號(Big O notation)是用於描述函式漸進行為的數學符號。更確切地說,它是用另一個(通常更簡單的)函式來描述一個函式數量級的漸近上界。在數學中,它一般用來刻畫被截斷的無窮級數尤其是漸近級數的剩餘項;在電腦科學中,它在分析演算法複雜性的方面非常有用。)表述,使用這種方式時,時間複雜度可被稱為是漸近的,它考察當輸入值大小趨近無窮時的情況。

大O,簡而言之可以認為它的含義是“order of”(大約是)

無窮大漸近
大O符號在分析演算法效率的時候非常有用。舉個例子,解決一個規模為 n 的問題所花費的時間(或者所需步驟的數目)可以被求得:T(n) = 4n^2 - 2n + 2。
當 n 增大時,n^2; 項將開始占主導地位,而其他各項可以被忽略——舉例說明:當 n = 500,4n^2; 項是 2n 項的1000倍大,因此在大多數場合下,省略後者對錶達式的值的影響將是可以忽略不計的。

 

常數階O(1)

常數又稱定數,是指一個數值不變的常量,與之相反的是變數

為什麼下面演算法的時間複雜度不是O(3),而是O(1)。

1
2
3
int sum = 0,n = 100; /*執行一次*/ 
sum = (1+n)*n/2; /*執行一次*/ 
printf("%d", sum); /*行次*/

這個演算法的執行次數函式是f(n)=3。根據我們推導大O階的方法,第一步就是把常數項3改為1。在保留最高階項時發現,它根本沒有最高階項,所以這個演算法的時間複雜度為O(1)。

另外,我們試想一下,如果這個演算法當中的語句sum=(1+n)*n/2有10句,即:

1
2
3
4
5
6
7
8
9
10
11
12
int sum = 0, n = 100; /*執行1次*/ 
sum = (1+n)*n/2; /*執行第1次*/ 
sum = (1+n)*n/2; /*執行第2次*/ 
sum = (1+n)*n/2; /*執行第3次*/ 
sum = (1+n)*n/2; /*執行第4次*/ 
sum = (1+n)*n/2; /*執行第5次*/ 
sum = (1+n)*n/2; /*執行第6次*/ 
sum = (1+n)*n/2; /*執行第7次*/ 
sum = (1+n)*n/2; /*執行第8次*/ 
sum = (1+n)*n/2; /*執行第9次*/ 
sum = (1+n)*n/2; /*執行第10次*/ 
printf("%d",sum); /*執行1次*/

事實上無論n為多少,上面的兩段程式碼就是3次和12次執行的差異。這種與問題的大小無關(n的多少),執行時間恆定的演算法,我們稱之為具有O(1)的時間複雜度,又叫常數階。

注意:不管這個常數是多少,我們都記作O(1),而不能是O(3)、O(12)等其他任何數字,這是初學者常常犯的錯誤。

 

 O(n【時間規模,也可以理解為執行次數】),當運算裡沒有最高階項(也就是次方)時,時間規模就是1,所以為o(1),

如果運算裡包含了最高階項(也就是次方)時,且次方數不為1時,時間規模就是最高階項(也就是次方數),如o(3),

 

推導大O階方法

1.用常數1取代執行時間中的所有加法常數

2.在修改後的執行次數函式中,只保留最高階項

3.如果最高階項存在且不是1,則去除與這個項相乘的常數

  

對數階O(log2n) 

對數

如果a的x次方等於N(a>0,且a不等於1),那麼數x叫做以a為底N的對數(logarithm),記作x=logaN, 。其中,a叫做對數的底數,N叫做真數。
5^2 = 25 , 記作 2= log5 25 
對數是一種運算,與指數是互逆的運算。例如

① 3^2=9 <==> 2=log<3>9;

② 4^(3/2)=8 <==> 3/2=log<4>8;

③ 10^n=35 <==> n=lg35。為了使用方便,人們逐漸把以10為底的常用對數記作lgN

 

對數階

1
2
3
4
5
6
7
8
9
int count = 1;
 
while (count < n)
 
{   
 
count = count * 2; /* 時間複雜度為O(1)的程式步驟序列 */
 
}


由於每次count乘以2之後,就距離n更近了一分。

也就是說,有多少個2相乘後大於n,則會退出迴圈。

由2^x=n得到x=log2n。所以這個迴圈的時間複雜度為O(logn)。

 

線性階O(n)  

執行時間隨問題規模增長呈正比例增長

1
2
3
4
5
data = [ 8,3,67,77,78,22,6,3,88,21,2]
find_num = 22
for i in data:
    if i == 22:
        print("find",find_num,i )

 

線性對數階O(nlog2n)

 

 

平方階O(n^2)

1
2
3
4
for i in range(100):
 
    for k in range(100):
        print(i,k)

  

立方階O(n^3)
k次方階O(n^k),
指數階O(2^n)。
隨著問題規模n的不斷增大,上述時間複雜度不斷增大,演算法的執行效率越低。  

 

 

一、計算方法
1.一個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機執行測試才能知道。但我們不可能也沒有必要對每個演算法都上機測試,只需知道哪個演算法花費的時間多,哪個演算法花費的時間少就可以了。並且一個演算法花費的時間與演算法中語句的執行次數成正比例,哪個演算法中語句執行次數多,它花費時間就多。
一個演算法中的語句執行次數稱為語句頻度或時間頻度。記為T(n)。
2.一般情況下,演算法的基本操作重複執行的次數是模組n的某一個函式f(n),因此,演算法的時間複雜度記做:T(n)=O(f(n))。隨著模組n的增大,演算法執行的時間的增長率和f(n)的增長率成正比,所以f(n)越小,演算法的時間複雜度越低,演算法的效率越高。
在計算時間複雜度的時候,先找出演算法的基本操作,然後根據相應的各語句確定它的執行次數,再找出T(n)的同數量級(它的同數量級有以下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出後,f(n)=該數量級,若T(n)/f(n)求極限可得到一常數c,則時間複雜度T(n)=O(f(n))。
3.常見的時間複雜度
按數量級遞增排列,常見的時間複雜度有:
常數階O(1),  對數階O(log2n),  線性階O(n),  線性對數階O(nlog2n),  平方階O(n^2), 立方階O(n^3),..., k次方階O(n^k), 指數階O(2^n) 。
其中,
1.O(n),O(n^2), 立方階O(n^3),..., k次方階O(n^k) 為多項式階時間複雜度,分別稱為一階時間複雜度,二階時間複雜度。。。。
2.O(2^n),指數階時間複雜度,該種不實用
3.對數階O(log2n),   線性對數階O(nlog2n),除了常數階以外,該種效率最高
例:演算法:
  for(i=1;i<=n;++i)
  {
     for(j=1;j<=n;++j)
     {
         c[ i ][ j ]=0; //該步驟屬於基本操作 執行次數:n^2
          for(k=1;k<=n;++k)
               c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //該步驟屬於基本操作 執行次數:n^3
     }
  }
  則有 T(n)= n^2+n^3,根據上面括號裡的同數量級,我們可以確定 n^3為T(n)的同數量級
  則有f(n)= n^3,然後根據T(n)/f(n)求極限可得到常數c
  則該演算法的 時間複雜度:T(n)=O(n^3)
四、

 

定義:如果一個問題的規模是n,解這一問題的某一演算法所需要的時間為T(n),它是n的某一函式 T(n)稱為這一演算法的“時間複雜性”。

當輸入量n逐漸加大時,時間複雜性的極限情形稱為演算法的“漸近時間複雜性”。

我們常用大O表示法表示時間複雜性,注意它是某一個演算法的時間複雜性。大O表示只是說有上界,由定義如果f(n)=O(n),那顯然成立f(n)=O(n^2),它給你一個上界,但並不是上確界,但人們在表示的時候一般都習慣表示前者。

此外,一個問題本身也有它的複雜性,如果某個演算法的複雜性到達了這個問題複雜性的下界,那就稱這樣的演算法是最佳演算法。

“大O記法”:在這種描述中使用的基本引數是 n,即問題例項的規模,把複雜性或執行時間表達為n的函式。這裡的“O”表示量級 (order),比如說“二分檢索是 O(logn)的”,也就是說它需要“通過logn量級的步驟去檢索一個規模為n的陣列”記法 O ( f(n) )表示當 n增大時,執行時間至多將以正比於 f(n)的速度增長。

這種漸進估計對演算法的理論分析和大致比較是非常有價值的,但在實踐中細節也可能造成差異。例如,一個低附加代價的O(n2)演算法在n較小的情況下可能比一個高附加代價的 O(nlogn)演算法執行得更快。當然,隨著n足夠大以後,具有較慢上升函式的演算法必然工作得更快。

O(1)

Temp=i;i=j;j=temp;                    

以上三條單個語句的頻度均為1,該程式段的執行時間是一個與問題規模n無關的常數。演算法的時間複雜度為常數階,記作T(n)=O(1)。如果演算法的執行時間不隨著問題規模n的增加而增長,即使演算法中有上千條語句,其執行時間也不過是一個較大的常數。此類演算法的時間複雜度是O(1)。

O(n^2)

2.1. 交換i和j的內容
     sum=0;                 (一次)
     for(i=1;i<=n;i++)       (n次 )
        for(j=1;j<=n;j++) (n^2次 )
         sum++;       (n^2次 )
解:T(n)=2n^2+n+1 =O(n^2)

2.2.   
    for (i=1;i<n;i++)
    {
        y=y+1;         ①   
        for (j=0;j<=(2*n);j++)    
           x++;        ②      
    }         
解: 語句1的頻度是n-1
          語句2的頻度是(n-1)*(2n+1)=2n^2-n-1
          f(n)=2n^2-n-1+(n-1)=2n^2-2
          該程式的時間複雜度T(n)=O(n^2).         

O(n)      
                                                      
2.3.
    a=0;
    b=1;                      ①
    for (i=1;i<=n;i++) ②
    {  
       s=a+b;    ③
       b=a;     ④  
       a=s;     ⑤
    }
解:語句1的頻度:2,        
           語句2的頻度: n,        
          語句3的頻度: n-1,        
          語句4的頻度:n-1,    
          語句5的頻度:n-1,                                  
          T(n)=2+n+3(n-1)=4n-1=O(n).
                                                                                                 
O(log2n )

2.4.
     i=1;       ①
    while (i<=n)
       i=i*2; ②
解: 語句1的頻度是1,  
          設語句2的頻度是f(n),   則:2^f(n)<=n;f(n)<=log2n    
          取最大值f(n)= log2n,
          T(n)=O(log2n )

O(n^3)

2.5.
    for(i=0;i<n;i++)
    {  
       for(j=0;j<i;j++)  
       {
          for(k=0;k<j;k++)
             x=x+2;  
       }
    }
解:當i=m, j=k的時候,內層迴圈的次數為k當i=m時, j 可以取 0,1,...,m-1 , 所以這裡最內迴圈共進行了0+1+...+m-1=(m-1)m/2次所以,i從0取到n, 則迴圈共進行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6所以時間複雜度為O(n^3).
                                  

我們還應該區分演算法的最壞情況的行為和期望行為。如快速排序的最 壞情況執行時間是 O(n^2),但期望時間是 O(nlogn)。通過每次都仔細 地選擇基準值,我們有可能把平方情況 (即O(n^2)情況)的概率減小到幾乎等於 0。在實際中,精心實現的快速排序一般都能以 (O(nlogn)時間執行。
下面是一些常用的記法:


訪問陣列中的元素是常數時間操作,或說O(1)操作。一個演算法如 果能在每個步驟去掉一半資料元素,如二分檢索,通常它就取 O(logn)時間。用strcmp比較兩個具有n個字元的串需要O(n)時間。常規的矩陣乘演算法是O(n^3),因為算出每個元素都需要將n對 元素相乘並加到一起,所有元素的個數是n^2。
指數時間演算法通常來源於需要求出所有可能結果。例如,n個元 素的集合共有2n個子集,所以要求出所有子集的演算法將是O(2n)的。指數演算法一般說來是太複雜了,除非n的值非常小,因為,在 這個問題中增加一個元素就導致執行時間加倍。不幸的是,確實有許多問題 (如著名的“巡迴售貨員問題” ),到目前為止找到的演算法都是指數的。如果我們真的遇到這種情況,通常應該用尋找近似最佳結果的演算法替代之。

相關文章