楊輝三角(Pascal Triangle)的幾種C語言實現及其複雜度分析

 

說明 

     本文給出楊輝三角的幾種C語言實現,並簡要分析典型方法的複雜度。算法

     本文假定讀者具有二項式定理、排列組合、求和等方面的數學知識。編程

 

 

一  基本概念

     楊輝三角,又稱賈憲三角、帕斯卡三角,是二項式係數在三角形中的一種幾何排列。此處引用維基百科上的一張動態圖以直觀說明(原文連接http://zh.wikipedia.org/wiki/楊輝三角):數組

     從上圖可看出楊輝三角的幾個顯著特徵:函數

     1. 每行數值左右對稱,且均爲正整數。優化

     2. 行數遞增時,列數亦遞增。ui

     3. 除斜邊上的1外,其他數值均等於其肩部兩數之和。spa

     楊輝三角與二項式定理有密切關係,即楊輝三角的第n行(n=0…MAX_ROW)對應二項式(a+b)n展開(Binomial Expansion)的係數集合。例如,第二行的數值1-2-1爲冪指數爲2的二項式(a+b)2展開形式a2 + 2ab + b2的係數,即3d

     應用組合公式可推導出楊輝三角的特徵1和3,以下:code

 

 

 

二  題目要求

     用C語言編程打印出MAX_ROW行楊輝三角數,如(MAX_ROW=5):blog

1

1    1

1    2    1

1    3    3    1

1    4    6    4    1

1    5   10   10    5    1

…… …… …… ……

     並分析程序所用的加法和乘法次數,比較其複雜度。

 

 

三  算法實現

     因整型數值輸出位寬限制,本節實現中將楊輝三角行數限制爲10。該限制並不影響算法實現的完整性和表達性。

3.1 基本算法

     直接利用特徵3求解楊輝值,即第i行的第j個數等於第i-1行的第j-1個數與第j個數之和,用二維數組形式表達即爲a[i][j] = a[i-1][j-1] + a[i-1][j]。

     算法實現以下:

 1 void BasicYangHui(void)
 2 {
 3     int dwRow = 0, dwCol = 0, aTriVal[MAX_ROW][MAX_COL] = {{0}};
 4 
 5     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
 6     {
 7         aTriVal[dwRow][0] = aTriVal[dwRow][dwRow] = 1;  //若爲i行0或i列,則i行j列楊輝值爲1
 8     }
 9 
10     for(dwRow = 2; dwRow < MAX_ROW; dwRow++)
11     {
12         for(dwCol = 1; dwCol < dwRow; dwCol++) //不然,i行j列楊輝值爲i-1行中第j-1列與第j列值之和
13             aTriVal[dwRow][dwCol] = aTriVal[dwRow-1][dwCol-1] + aTriVal[dwRow-1][dwCol];
14     }
15 
16     //輸出楊輝三角值
17     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
18     {
19         for(dwCol = 0; dwCol <= dwRow; dwCol++)
20         {
21             printf("%5d", aTriVal[dwRow][dwCol]);
22         }
23         printf("\n");
24     }
25 }

     上述程序還可優化,利用對稱性折半賦值以使加法計算減半。

 1 void BasicYangHui2(void)
 2 {
 3     int dwRow = 0, dwCol = 0, aTriVal[MAX_ROW][MAX_COL] = {{0}};
 4 
 5     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
 6     {
 7         aTriVal[dwRow][0] = aTriVal[dwRow][dwRow] = 1;  //若爲i行0或i列,則i行j列楊輝值爲1
 8     }
 9 
10     for(dwRow = 2; dwRow < MAX_ROW; dwRow++)
11     {
12         for(dwCol = 1; dwCol <= dwRow/2; dwCol++)
13             aTriVal[dwRow][dwCol] = aTriVal[dwRow-1][dwCol-1] + aTriVal[dwRow-1][dwCol];
14         for(dwCol = dwRow-1; dwCol > dwRow/2; dwCol--) //此處必須取大於號,才能保證正確對摺
15             aTriVal[dwRow][dwCol] = aTriVal[dwRow][dwRow-dwCol];
16     }
17 
18     //輸出楊輝三角值
19     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
20     {
21         for(dwCol = 0; dwCol <= dwRow; dwCol++)
22         {
23             printf("%5d", aTriVal[dwRow][dwCol]);
24         }
25         printf("\n");
26     }
27 }

     注意,BasicYangHui和BasicYangHui2均先計算楊輝值後統一打印輸出。也可邊計算邊輸出:

 1 void BasicYangHui3(void)
 2 {
 3     int dwRow = 0, dwCol = 0, aTriVal[MAX_ROW][MAX_COL] = {{0}};
 4 
 5     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
 6     {
 7         for(dwCol = 0; dwCol <= dwRow; dwCol++)
 8         {
 9             if((0 == dwCol) || (dwRow == dwCol))
10                 aTriVal[dwRow][dwCol] = 1;
11             else
12                 aTriVal[dwRow][dwCol] = aTriVal[dwRow-1][dwCol-1] + aTriVal[dwRow-1][dwCol];
13             
14             printf("%5d", aTriVal[dwRow][dwCol]);
15         }
16         printf("\n");
17     }
18 }

3.2 遞歸算法

     利用特徵3所對應的組合恆等式,可方便地寫出楊輝三角的遞歸算法。

 1 //求楊輝三角中第i行第j列的值
 2 int CalcTriVal(int dwRow, int dwCol)
 3 {
 4     if((0 == dwCol) || (dwRow == dwCol))
 5         return 1;
 6     else
 7         return CalcTriVal(dwRow-1, dwCol-1) + CalcTriVal(dwRow-1, dwCol);
 8 }
 9 
10 void RecursiveYangHui(void)
11 {
12     int dwRow = 0, dwCol = 0;
13 
14     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
15     {
16         for(dwCol = 0; dwCol <= dwRow; dwCol++)
17         {
18             printf("%5d", CalcTriVal(dwRow, dwCol));
19         }
20         printf("\n");
21     }
22 }

3.3 迭代算法

     經過組合公式推導,可得等效的迭表明達dwTriVal = dwTriVal * (dwRow-dwCol) / (dwCol+1)。

     相應的算法實現以下:

 1 void BinomialYangHui(void)
 2 {
 3     int dwRow = 0, dwCol = 0, dwTriVal;
 4 
 5     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
 6     {   //首列直接輸出1,不然由二項式係數遞推公式求出楊輝值
 7         dwTriVal = 1;
 8         for(dwCol = 0; dwCol <= dwRow; dwCol++)
 9         {
10             printf("%5d",dwTriVal);
11             dwTriVal = dwTriVal * (dwRow-dwCol) / (dwCol+1);
12         }
13         printf("\n");
14     }
15 }

3.4 覆蓋算法

     本節將用一維數組代替二維數組,並結合對稱性(「折半」),使加法次數和存儲空間減半。其示意圖以下所示:

 

     圖中紅色數字爲折半邊界,同列數字對應一維數組的同一存儲位置。數組順序存儲單行楊輝值,只計算邊界以左的楊輝值,每次計算後用新行值覆蓋前行值。爲便於說明,將前行col列值記爲a[col],新行col列值記爲a’[col],注意a[col]和a’[col]實際上對應同一存儲位置。

     可見,計算奇數行(行數從0開始)首列邊界處的楊輝值a’[col]時,可將a[col]與a[col-1]值相加後賦值給a’[col];計算偶數行首列邊界處的楊輝值a’[col]時,因a[col]位於折半邊界以右(其值爲0),需將a[col-1]賦予a[col]再與a[col-1]值相加後賦值給a’[col]。自邊界處向左依次計算至第1列(0列直接置1),而後正向輸出存儲的楊輝值(對應邊界以左值),再反向輸出所存值(對應邊界以右值)。繼續以上步驟處理下一行。

     考慮到偶數行相對前行邊界右移一位,故數組空間大小定義爲(MAX_ROW+1)/2。

     算法實現以下。注意,計算row行數據時,數組預存的是row-1行數據。

 1 void EfficientYangHui(void)
 2 {
 3     int dwRow = 0, dwCol = 0, aTriVal[(MAX_ROW+1)/2] = {1};
 4     printf("%5d\n", aTriVal[0]); //先輸出首行楊輝值,以便後面各行可採用統一的算法
 5 
 6     for(dwRow = 1; dwRow < MAX_ROW; dwRow++)
 7     {
 8         if(0 == (dwRow % 2)) //偶數行折半處爲元素自加,如1-3-0-0爲1+三、3+3(而非3+0)
 9             aTriVal[dwRow/2] = aTriVal[dwRow/2-1];
10         for(dwCol = dwRow/2; dwCol >= 1; dwCol--)
11         {
12             aTriVal[dwCol] = aTriVal[dwCol] + aTriVal[dwCol-1];
13         }
14         aTriVal[0] = 1; //首列置1
15 
16         for(dwCol = 0; dwCol <= dwRow/2; dwCol++)
17         {
18             printf("%5d", aTriVal[dwCol]); //並輸出aTriVal[dwCol]做爲前半行楊輝值
19         }
20         for(dwCol = (dwRow-1)/2; dwCol >= 0; dwCol--)
21         {
22             printf("%5d", aTriVal[dwCol]); //反向輸出aTriVal[dwCol],構成後半行楊輝值
23         }
24         printf("\n");
25     }
26 }

     如下給出另外一種覆蓋算法。該算法未使用折半處理,但使用臨時變量暫存待覆蓋的右肩值(即示意圖中前行同列值),並從首列開始從左至右計算並覆蓋。

 1 void EfficientYangHui2(void)
 2 {
 3     int dwRow = 0, dwCol = 0, dwLeft = 0, dwRight = 0;
 4     int aTriVal[MAX_ROW+1] = {1};
 5 
 6     for(dwRow = 0; dwRow < MAX_ROW; dwRow++)
 7     {
 8         dwLeft = 0;
 9         for(dwCol = 0; dwCol <= dwRow; dwCol++)
10         {
11             dwRight = aTriVal[dwCol];
12             aTriVal[dwCol] = dwLeft + dwRight;
13             dwLeft = dwRight;
14             printf("%5d", aTriVal[dwCol]);
15         }
16         printf("\n");
17     }
18 }

 

 

四  複雜度分析

     不一樣於傳統定義的時間複雜度計算,本節將時間複雜度等同於循環體內楊輝值加減乘除運算的次數,即側重運算效率。基於相應的算法思想,可方便地改編爲符合傳統時間複雜度指望的實現。

     此外,本節將空間複雜度等同於存儲楊輝值的數組大小。因代碼中已加以體現,此處再也不分析。

     將楊輝三角總行數記爲N(亦即MAX_ROW),本節計算BasicYangHui、RecursiveYangHui和BinomialYangHui三種典型算法的時間複雜度。計算主要用到如下公式:

4.1 BasicYangHui複雜度

     主要計算BasicYangHui函數內層循環中加法運算(13行)的執行次數。

     可知,每行楊輝值須要執行dwRow - 1次加法運算。經過求和公式推導總的加法次數爲

 

4.2 RecursiveYangHui複雜度

     遞歸算法的時間複雜度計算稍微複雜,如下藉助二項式定理進行推導。

     對於(a+b)n,其展開式第r項的係數知足:

     由此結合遞歸算法,可得:

     以此類推,將各個楊輝值對應的計算次數寫成以下形式:

0

0        0

0        1         0

0        2         2         0

0        3         5         3        0

0        4         9         9        4         0

0        5         14       19       14       5         0

……  ……  ……  ……

     可看出所造成的新三角至關於楊輝三角每一個元素減1而成。

     根據二項式係數和公式,可知每行元素和(加法次數)爲

     求和得總的加法次數爲

 

     可見RecursiveYangHui中採用遞歸調用算法時間複雜度很高。遞歸代碼在緊湊易懂的同時,犧牲了執行速度(實際上由於大量使用堆棧內存也犧牲了空間)。

4.3 BinomialYangHui複雜度

     主要計算BinomialYangHui函數內層循環中dwTriVal * (dwRow-dwCol) / (dwCol+1)句的運算次數。將其計爲一次乘法、一次減法和一次除法(加1運算不計),共三次運算。

     可知,每行楊輝值須要執行(dwRow + 1) * 3次運算。經過求和公式推導總的運算次數爲

 

 

五  總結

     對比BasicYangHui、RecursiveYangHui和BinomialYangHui三種算法的複雜度可知:

  • Ÿ時間複雜度:BasicYangHui最低,RecursiveYangHui最高(達到指數級);
  • Ÿ空間複雜度:BinomialYangHui最低,BasicYangHui較高。RecursiveYangHui因消耗大量棧空間故複雜度也較高。 

 

 

若是,您認爲閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的【推薦】。
若是,您但願更容易地發現個人新博客,不妨點擊一下左下角的【+加關注】。
若是,您對個人博客所講述的內容有興趣,請繼續關注個人後續博客,我是【clover_toeic】。本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索