杨辉三角,是二项式系数在三角形中的一种几何排列。在欧洲,这个表叫做帕斯卡三角形。帕斯卡(1623----1662)是在1654年发现这一规律的,比杨辉要迟393年,比贾宪迟600年。杨辉三角是中国古代数学的杰出研究成果之一,它把二项式系数图形化,把组合数内在的一些代数性质直观地从图形中体现出来,是一种离散型的数与形的结合[2]。
帕斯卡三角形(4)前提:每行端点与结尾的数为1.
性质5和性质7是杨辉三角的基本性质,是研究杨辉三角其他规律的基础。
与杨辉三角联系最紧密的是二项式乘方展开式的系数规律,即二项式定理。例如在杨辉三角中,第3行的三个数恰好对应着两数和的平方的展开式的每一项的系数(性质 8),第4行的四个数恰好依次对应两数和的立方的展开式的每一项的系数,即,以此类推。
又因为性质5:第n行的m个数可表示为C(n-1,m-1),即为从n-1个不同元素中取m-1个元素的组合数。因此可得出二项式定理的公式为:
因此,二项式定理与杨辉三角形是一对天然的数形趣遇,它把数形结合带进了计算数学。求二项式展开式系数的问题,实际上是一种组合数的计算问题。用系数通项公式来计算,称为“式算”;用杨辉三角形来计算,称作“图算”[3]。
由1开始,正整数在杨辉三角形出现的次数为∞,1, 2, 2, 2, 3, 2, 2, 2, 4, 2, 2, 2, 2, 4, ... (OEIS:A003016)。最小而又大于1的数在贾宪三角形至少出现n次的数为2, 3, 6, 10, 120, 120, 3003, 3003, ... (OEIS:A062527)
除了1之外,所有正整数都出现有限次,只有2出现刚好一次,6,20,70等出现三次;出现两次和四次的数很多,还未能找到出现刚好五次的数。120,210,1540等出现刚好六次。(OEIS:A098565)
因为丢番图方程有无穷个解,所以出现至少六次的数有无穷个多。解为,其中Fn表示第n个斐波那契数(F1=F2=1)。
3003是第一个出现八次的数。
帕斯卡三角形北宋人贾宪约1050年首先使用“贾宪三角”进行高次开方运算。
杨辉,字谦光,南宋时期杭州人。在他1261年所著的《详解九章算法》一书中,辑录了如上所示的三角形数表,称之为“开方作法本源”图,并说明此表引自11世纪中叶(约公元1050年)贾宪的《释锁算术》,并绘画了“古法七乘方图”。故此,杨辉三角又被称为“贾宪三角”。
元朝数学家朱世杰在《四元玉鉴》(1303年)扩充了“贾宪三角”成“古法七乘方图”。
意大利人称之为“塔塔利亚三角形”(Triangolo di Tartaglia)以纪念在16世纪发现一元三次方程解的塔塔利亚。
在欧洲直到1623年以后,法国数学家帕斯卡在13岁时发现了“帕斯卡三角”。
布莱士·帕斯卡的著作Traité du triangle arithmétique(1655年)介绍了这个三角形。帕斯卡搜集了几个关于它的结果,并以此解决一些概率论上的问题,影响面广泛,Pierre Raymond de Montmort(1708年)和亚伯拉罕·棣·美弗(1730年)都用帕斯卡来称呼这个三角形。
21世纪以来国外也逐渐承认这项成果属于中国,所以有些书上称这是“中国三角形”(Chinese triangle)
历史上曾经独立绘制过这种图表的数学家有:
其实,中国古代数学家在数学的许多重要领域中处于遥遥领先的地位。中国古代数学史曾经有自己光辉灿烂的篇章,而杨辉三角的发现就是十分精彩的一页。
杨辉三角在编程实现中较为容易。最常见的算法便是用上一行递推计算;也有运用和组合的对应关系而使用阶乘计算的,然而后者速度较慢且阶乘容易溢出。编程的输出大多相类,此处并不过多添加截图。
C、C++、C#、Java 语言之间的语法也大多相类,因此这里也不会将每一种算法都在这些语言中各实现一遍。要在这些语言的版本间修改,实际上只需注意一些简单的语法和函数名称的改变,如 C 的 int yh[M][M] 应改写为 Java 的 int[][] yh = new int[M][M]、C# 的 int[,] yh=new int[M,M];C printf 应使用 Java 的 System.out.print、C# 的 Console.Write 、C++ 中更智能的 cout 来替换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include<iostream> #include<iomanip> using namespace std; int main() { const int n = 15; const int m = 2 * n-1; int arr[n + 1][m] = { 0 }; for (int i = 0; i < n; i++) { arr[i][n - i- 1] = 1; arr[i][n + i -1] = 1;
} for (int i = 2; i < n; i++) { for (int j = n - i + 1; j < n-2+i; j = j + 2) arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j + 1]; } int p; for (int i = 0; i < n; i++) { for (int j = 0; j < n - i - 1; j++) cout << " "; p = 1; for (int j = n - i - 1; p < i + 2; j = j + 2) { cout << setw(4) << arr[i][j] << " "; p = p + 1; } cout << endl; } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> #include <cstring> #include <iomanip> using namespace std; int a[1000000]={0,1} , b[1000000];//这些随便改,想咋改咋改,用的是分类讨论! int main() { int n , i , j; cin >> n; for(i=2;i<=n+1;i++) { if(i%2==0) { for(j=1;j<=i;j++) b[j]=a[j]+a[j-1]; for(j=0;j<n-i+1;j++) cout << " "; for(j=1;j<i;j++) cout << setw(6) << setfill(' ') << a[j]; memset(a,0,sizeof(a)); } else { for(j=1;j<=i;j++) a[j]=b[j]+b[j-1]; for(j=0;j<n-i+1;j++) cout << " "; for(j=1;j<i;j++) cout << setw(6) << setfill(' ') << b[j]; memset(b,0,sizeof(b)); } cout << endl; } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include<iostream> #include<iomanip> using namespace std; int main() { const int n = 15; const int m = 2 * n-1; int arr[n + 1][m] = { 0 }; for (int i = 0; i < n; i++) { arr[i][n - i- 1] = 1; arr[i][n + i -1] = 1;
} for (int i = 2; i < n; i++) { for (int j = n - i + 1; j < n-2+i; j = j + 2) arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j + 1]; } int p; for (int i = 0; i < n; i++) { for (int j = 0; j < n - i - 1; j++) cout << " "; p = 1; for (int j = n - i - 1; p < i + 2; j = j + 2) { cout << setw(4) << arr[i][j] << " "; p = p + 1; } cout << endl; } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> #include <cstring> #include <iomanip> using namespace std; int a[1000000]={0,1} , b[1000000];//这些随便改,想咋改咋改,用的是分类讨论! int main() { int n , i , j; cin >> n; for(i=2;i<=n+1;i++) { if(i%2==0) { for(j=1;j<=i;j++) b[j]=a[j]+a[j-1]; for(j=0;j<n-i+1;j++) cout << " "; for(j=1;j<i;j++) cout << setw(6) << setfill(' ') << a[j]; memset(a,0,sizeof(a)); } else { for(j=1;j<=i;j++) a[j]=b[j]+b[j-1]; for(j=0;j<n-i+1;j++) cout << " "; for(j=1;j<i;j++) cout << setw(6) << setfill(' ') << b[j]; memset(b,0,sizeof(b)); } cout << endl; } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#! /bin/bash # 用法:./pasTrig [个数],若不指明个数为 5。 # 填充指定个数的空格 pad(){ for ((k=0;k<$1;k++)); do echo -n ' '; done; } # 层数和新旧层 lyrs=${1-5} prev[0]=1 curr[0]=1 # 接下来每行第一个始终为一,无需重复赋值 # 执行 pad $(((lyrs-1)*2)) echo 1 for ((i=2; i<=lyrs; i++)); do # 略过 1,已处理 pad $(((lyrs-i)*2)) # 填充空格,注意这里不会怎么顾及三位以上的数,即第 14 层开始会混乱 curr[i]=1 printf '%-4d' ${curr[0]} for ((j=1; j<i-1; j++)); do # 首尾极值已处理,略过 ((curr[j]=prev[j-1]+prev[j])) printf '%-4d' ${curr[j]} done printf '%-4d\n' ${curr[i]} # 最后一个和换行 # 搬家 prev=(${curr[*]}) done |
Bash 输出杨辉三角并使用 nl 表明行号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#! /bin/bash # 用法:./pasTrig [个数],若不指明个数为 5。 # 填充指定个数的空格 pad(){ for ((k=0;k<$1;k++)); do echo -n ' '; done; } # 层数和新旧层 lyrs=${1-5} prev[0]=1 curr[0]=1 # 接下来每行第一个始终为一,无需重复赋值 # 执行 pad $(((lyrs-1)*2)) echo 1 for ((i=2; i<=lyrs; i++)); do # 略过 1,已处理 pad $(((lyrs-i)*2)) # 填充空格,注意这里不会怎么顾及三位以上的数,即第 14 层开始会混乱 curr[i]=1 printf '%-4d' ${curr[0]} for ((j=1; j<i-1; j++)); do # 首尾极值已处理,略过 ((curr[j]=prev[j-1]+prev[j])) printf '%-4d' ${curr[j]} done printf '%-4d\n' ${curr[i]} # 最后一个和换行 # 搬家 prev=(${curr[*]}) done |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Program { public int yanghui(int value) { if(value<3) return 1; int[,]arry=new int[value,value]; Console.WriteLine("数组为:"); for(int i=0;i<value;i++) { string str=""; str=str.PadLeft(value-i); Console.Write(str); for(int j=0;j<=i;j++) { if(i==j||j==0) arry[i,j]=1; else arry[i,j]=arry[i-1,j-1]+arry[i-1,j]; Console.Write(arry[i,j]+""); } Console.WriteLine(); } }
static void Main(string[]args) { Program p=new Program(); Console.WriteLine("请输入数组值:"); if (p.yanghui(Convert.ToInt16(Console.ReadLine()))) Console.WriteLine("输入数值必须大于3"); Console.Readkey(); }
} |
以下的代码均用标准 C 语言写成,可以被包括 MSVC(含 VC6)、GCC 的多种 C 编译器编译。
这个算法使用只行列位置和左侧的数值算出数值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* yh-rt1.c - 时间和空间最优算法 */ #include <stdio.h> #include <stdlib.h> int main() { int s = 1, h; // 数值和高度 int i, j; // 循环计数 scanf("%d", &h); // 输入层数 printf("1\n"); // 输出第一个 1 for (i = 2; i <= h; s = 1, i++) // 行数 i 从 2 到层高 { printf("1 "); // 第一个 1 for (j = 1; j <= i - 2; j++) // 列位置 j 绕过第一个直接开始循环 //printf("%d ", (s = (i - j) / j * s)); printf("%d ", (s = (i - j) * s / j)); printf("1\n"); // 最后一个 1,换行 } getchar(); // 暂停等待 return 0; } |
默认求直角三角形,可通过注释的开关或使用编译器的 -D 定义开关调节等腰三角形和菱形输出。如果觉得复杂,可按照 define 使用的情况剔除因不符合 ifdef 条件从而未启用的代码之后阅读。
这个算法创建了一个二维数组,并且通过上一行的数值求当前行。在反过来再次打印时,这个程序会使用以前算好的值,从而节省了重复迭代的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* yh-2d.c - 二维数组迭代 */ #include<stdio.h> #define M 10 // 行数 // #define PYRAMID // 金字塔,会额外填充空格 // #define REVERSE // 反向再来一次,得到菱形 int main(void) { int a [M][M], i, j; // 二维数组和循环变量,a[行][列] for (i = 0; i<M; i++) // 每一行 { #ifdef PYRAMID for (j = 0;j <= M-i; j++) printf (" "); #endif // 填充结束 for (j = 0; j <= i; j++) // 赋值打印 printf("%4d", (a[i][j] = (i == j || j == 0) ? 1 : // 首尾置 1 a[i - 1][j] + a[i - 1][j - 1] )); // 使用上一行计算 printf("\n"); } #ifdef REVERSE for(i = M-2; i >= 0; i--) { #ifdef PYRAMID for (j = 0;j <= M - i; j++) printf(" "); #endif // 填充结束 for (j = 0;j <= i; j++) printf("%4d",a[i][j]); // 直接使用以前求得的值 printf("\n"); } #endif // 菱形结束 getchar(); // 暂停等待 } |
这一个使用大数组写成,风格更接近教科书上的 VC6 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* yh-rt3.c - 较为暴力的大数组 */ #include <stdio.h> #include "string.h" int main() { int a[10000]; //容器,由n*(n+1)/2<=10000可知,n<=141 int b,CR,i; //b为当前行数,CR为要求显示的行数,i为循环数 printf("请输入要显示的行数(3~141):"); scanf("%d",&CR); YHSJ(CR); a[1]=a[2]=1; //前两行数值少且全为1,故直接输出 printf("%d\n",a[1]); printf("%d %d\n",a[1],a[2]); for(b=3;b<=CR;b++) //从第三行开始判断 { for(i=b;i>=2;i--) //从倒数第一个数开始加 a[i]=a[i]+a[i-1]; //杨辉三角的规律,没有值的数组默认为0 for(i=1;i<=b;i++) //显示循环 printf("%d ",a[i]); printf("\n"); // 换行 } return 0; } |
这个版本使用队列的方式输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include <stdio.h> #include <stdlib.h> #define EMPTY 1 #define OFLOW 2 #define INVAL 3 #define MAX_Q 100 typedef int DataType; // 数据类型选择 typedef struct{ DataType elem[MAX_Q]; int front, rear; } LinkQ; // 队列及检查宏 #define InitQ(Q) LinkQ Q; Q.front=Q.rear=-1; #define _EQ(Q,e) Q.elem[(Q.rear=(Q.rear+1)%MAX_Q)]=e #define EnQ(Q,e) if((Q.rear+1)%MAX_Q==Q.front) Exit(OFLOW,"Overflow"); _EQ(Q,e) #define DeQ(Q,e) e=Q.elem[(Q.front=(Q.front+1)%MAX_Q)] #define Front(Q) Q.elem[(Q.front+1)%MAX_Q] // 退出 int Exit(int err, char msg[]) { puts(msg); exit(err); return err; } int main(void){ int n=1,i,j,k,t; InitQ(Q); printf("please enter a number:"); scanf("%d",&n); if (n<=0) { printf("ERROR!\n"); exit(INVAL); } for(i=0;i<n;i++) printf(" "); puts("1"); EnQ(Q,1); EnQ(Q,1); for(i=1;i<n;i++) { for(k=0;k<n-i;k++) printf(" "); EnQ(Q,1); for(j=0;j<i;j++){ DeQ(Q,t); printf("%3d ",t); EnQ(Q,t+Front(Q)); } EnQ(Q,1); DeQ(Q,t); printf("%d\n",t); } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Program { public int yanghui(int value) { if(value<3) return 1; int[,]arry=new int[value,value]; Console.WriteLine("数组为:"); for(int i=0;i<value;i++) { string str=""; str=str.PadLeft(value-i); Console.Write(str); for(int j=0;j<=i;j++) { if(i==j||j==0) arry[i,j]=1; else arry[i,j]=arry[i-1,j-1]+arry[i-1,j]; Console.Write(arry[i,j]+""); } Console.WriteLine(); } }
static void Main(string[]args) { Program p=new Program(); Console.WriteLine("请输入数组值:"); if (p.yanghui(Convert.ToInt16(Console.ReadLine()))) Console.WriteLine("输入数值必须大于3"); Console.Readkey(); }
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* yh-rt1.c - 时间和空间最优算法 */ #include <stdio.h> #include <stdlib.h> int main() { int s = 1, h; // 数值和高度 int i, j; // 循环计数 scanf("%d", &h); // 输入层数 printf("1\n"); // 输出第一个 1 for (i = 2; i <= h; s = 1, i++) // 行数 i 从 2 到层高 { printf("1 "); // 第一个 1 for (j = 1; j <= i - 2; j++) // 列位置 j 绕过第一个直接开始循环 //printf("%d ", (s = (i - j) / j * s)); printf("%d ", (s = (i - j) * s / j)); printf("1\n"); // 最后一个 1,换行 } getchar(); // 暂停等待 return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* yh-2d.c - 二维数组迭代 */ #include<stdio.h> #define M 10 // 行数 // #define PYRAMID // 金字塔,会额外填充空格 // #define REVERSE // 反向再来一次,得到菱形 int main(void) { int a [M][M], i, j; // 二维数组和循环变量,a[行][列] for (i = 0; i<M; i++) // 每一行 { #ifdef PYRAMID for (j = 0;j <= M-i; j++) printf (" "); #endif // 填充结束 for (j = 0; j <= i; j++) // 赋值打印 printf("%4d", (a[i][j] = (i == j || j == 0) ? 1 : // 首尾置 1 a[i - 1][j] + a[i - 1][j - 1] )); // 使用上一行计算 printf("\n"); } #ifdef REVERSE for(i = M-2; i >= 0; i--) { #ifdef PYRAMID for (j = 0;j <= M - i; j++) printf(" "); #endif // 填充结束 for (j = 0;j <= i; j++) printf("%4d",a[i][j]); // 直接使用以前求得的值 printf("\n"); } #endif // 菱形结束 getchar(); // 暂停等待 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* yh-rt3.c - 较为暴力的大数组 */ #include <stdio.h> #include "string.h" int main() { int a[10000]; //容器,由n*(n+1)/2<=10000可知,n<=141 int b,CR,i; //b为当前行数,CR为要求显示的行数,i为循环数 printf("请输入要显示的行数(3~141):"); scanf("%d",&CR); YHSJ(CR); a[1]=a[2]=1; //前两行数值少且全为1,故直接输出 printf("%d\n",a[1]); printf("%d %d\n",a[1],a[2]); for(b=3;b<=CR;b++) //从第三行开始判断 { for(i=b;i>=2;i--) //从倒数第一个数开始加 a[i]=a[i]+a[i-1]; //杨辉三角的规律,没有值的数组默认为0 for(i=1;i<=b;i++) //显示循环 printf("%d ",a[i]); printf("\n"); // 换行 } return 0; } |