本文給出楊輝三角的幾種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求解楊輝值,即第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所對應的組合恆等式,可方便地寫出楊輝三角的遞歸算法。
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 }
經過組合公式推導,可得等效的迭表明達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 }
本節將用一維數組代替二維數組,並結合對稱性(「折半」),使加法次數和存儲空間減半。其示意圖以下所示:
圖中紅色數字爲折半邊界,同列數字對應一維數組的同一存儲位置。數組順序存儲單行楊輝值,只計算邊界以左的楊輝值,每次計算後用新行值覆蓋前行值。爲便於說明,將前行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三種典型算法的時間複雜度。計算主要用到如下公式:
主要計算BasicYangHui函數內層循環中加法運算(13行)的執行次數。
可知,每行楊輝值須要執行dwRow - 1次加法運算。經過求和公式推導總的加法次數爲
遞歸算法的時間複雜度計算稍微複雜,如下藉助二項式定理進行推導。
對於(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中採用遞歸調用算法時間複雜度很高。遞歸代碼在緊湊易懂的同時,犧牲了執行速度(實際上由於大量使用堆棧內存也犧牲了空間)。
主要計算BinomialYangHui函數內層循環中dwTriVal * (dwRow-dwCol) / (dwCol+1)句的運算次數。將其計爲一次乘法、一次減法和一次除法(加1運算不計),共三次運算。
可知,每行楊輝值須要執行(dwRow + 1) * 3次運算。經過求和公式推導總的運算次數爲
對比BasicYangHui、RecursiveYangHui和BinomialYangHui三種算法的複雜度可知:
若是,您認爲閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的【推薦】。 若是,您但願更容易地發現個人新博客,不妨點擊一下左下角的【+加關注】。 若是,您對個人博客所講述的內容有興趣,請繼續關注個人後續博客,我是【clover_toeic】。本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。 |