一题多做--杨辉三角形

一题多做

从不同立场、不同视角看问题,会得到不同认识;用不同分析方法、不同设计方案,会得到不同代码。从提高编程能力角度讲,对于同一个题目,尝试几种不同的设计思路,往往比漫不经心地做几个题目来得有效。下面用许多教程中都使用的题目“打印杨辉三角形”为例,介绍有关的分析、设计思路。

[例题]输出下述的“杨辉三角形”的前若干行(不超过15行)

1

1          1

1          2   1

1   3   3   1

1   4   6   4   1

1   5  10  10   5   1

……

[分析1]

*可以使用二维数组存储三角形中的数据;

*每一行的数据个数比上一行多一个;

*每一行最前面的的数据是1,最后面的数据(在对角线上)也是1

*其它数据是上一行左上方和正上方数据之和;

*……

在分析过程中,我们可以列出许多我们能观察到的东西,但并非列出的东西在设计中有用,我们应该抓住一些最基本的、最直观的东西,发现规律、指导设计。比如有些人还观察到:

*将每一列视为数列,第一列衡为1,第二列相邻数据值差1,第三列相邻数据值差为234

    如何在分析过程中,抓住最重要最本质的东西,往往需要理论与实践相结合。

#include <stdio.h>

int Yang[20][20];   

/*存储杨辉三角形的二维数组,因为二维数组不容易在函数间传递,所以定义为全局形式。但从软件工程的角度上认识,这并不是一种良好的程序设计风格,有能力的话该当不使用全局变量或全局数组*/

void printit(int line);     /*函数原型,输出生成的数据*/

void triangle(int line)    /* 生成共line行的杨辉三角形*/

{

       int i,j;

       for(i=0; i<line; i++)   /*逐行*/

       {

              for(j=0; j<=i; j++)  /*i行有i+1个元素*/

              {

                     if(j==0 || j==i)  /*位置在第0列或在对角线上*/

                     {

                            Yang[i][j]=1;   /*每一行的第0个元素和第i个元素值为1*/

                     }

                     else           

                     {

                            Yang[i][j] = Yang[i-1][j-1] + Yang[i-1][j];

/*其它元素的值是上一行左上和正上元素值之和*/

                     }

              }/*forj*/

       }    /*fori*/

}        /*End of void triangle(int line) */

 

/* 主函数 */

int main( )

{

       int line;

       printf(“Please input the number of lines”);

       scanf(“%d”,&line);  /*运行时获取要打印的行数*/

    triangle(line);      /*生成杨辉三角形*/

       printit(line);       /*打印生成的三角形*/

       system(“pause”);

       return 0;

}

void printit(int line) /*  打印杨辉三角形  形参line 行数*/

{

       int i,j;

       for(i=0; i<line; i++)  /*打印 line */

       {

              printf(“/n”);       /*换行控制*/

              for(j=0; j<=i; j++)  /*每一行打印i+1个元素值*/

              {

                     printf(“%5d”,Yang[i][j]);

              }  /* for(j)*/

}      /*for(i)*/

       printf(“/n”);  

} /*End of void printit(int line)*/

 

[评价1 ]

*二维数组的存储利用率不高,刚刚超过50%,数组的右上部没有数据

*按外部标识符形式在各函数间传递二维数组信息,无法达到函数通用、复用

 

[分析2]压缩掉所有无用的存储空间,将二维的三角阵改成一维数组

1

1

1

1

2

1

1

3

3

1

1

6

4

4

1

1

0

1

2

3

4

k

如何构造一维数组中的元素下标k与二维数组中行i、列j之间的关系成为关键。初等代数的知识足以帮助我们解决这个问题:三角形第i行之上有i行(从第0行至第i-1行),元素的总个数为

1+2+3+…+i = i*i+1/2

而第i行第j列左有j个元素,则二维数组中第i行第j列元素在一维数组中的位置

k = i*i+1/2+j

在这类问题里,我们将讨论中的二维数组称为数据的逻辑结构,而把对应的一维数组称为数据的物理结构,则ij称为逻辑位置,k称为物理位置。(请读者自行尝试一下,反向导出已知一维数组中物理位置为k的元素在二维数组中的逻辑位置ij是什么)。

#include <stdio.h>

#define N 20

void printit(int arr[],int line);

/* 生成杨辉三角形的一维数组,Yang[ ] 一维存储数组,line 行数*/

void triangle(int Yang[], int line)

{

       int i,j;

       for(i=0; i<line; i++)

       {

              for(j=0; j<=i; j++)

              {

                     Yang[i*(i+1)/2+j] = ((j==0 || j==i))

?         1              

 :  Yang[(i-1)*i/2+j-1]+Yang[(i-1)*i/2+j];

            /*利用逻辑位置计算物理位置,并利用条件表达式简化了if语句*/

              }

       }

}

 

/* 主函数,构造一维的存储数组,

int main()

{

       int Yang[(N*N+N)/2];  /*定义一维物理结构的数组*/

       int line;           /*要打印的二维数组行数*/

       printf(“Please input the number of lines(<%d): “,N);

       scanf(“%d”,&line);

    triangle(Yang, line);   /*生成数组*/

       printit(Yang, line);    /*打印三角形*/

       system(“pause”);

       return 0;

}

 

/* 打印三角形,数据在一维数组Yang中,共line */

void printit(int Yang[], int line)

{

       int i,j;

       for(i=0; i<line; i++)

       {

              printf(“/n”);

              for(j=0; j<=i; j++)

              {

                     printf(“%5d”,Yang[i*(i+1)/2+j]); /*用逻辑位置计算物理位置*/

              }

       }

       printf(“/n”);  

}

[评价2]

*典型的用时间换空间,用逻辑位置计算物理位置不那么直观,但存储空间利用率是100%

*过份追求代码的简洁,可读性下降

但是读者有兴趣的话,这种空间位置的变换,,还是应该多做练习,有利于提高空间结构的认识,比如说前面提到的由一维数组的下标k求对应的二维数组下标ij;又如三角阵为对角线右上部分,如何变换成一维数组?

 

[分析3]

还可以进一步提高存储空间的利用率:每一行数据的计算只依赖上一行的数据,因此在计算中只需保留最新生成的一行供下一行计算即可,而下一行直接利用同一块存储空间。

1

1

1

1

下一行

i

。。。。。。

从后向前计算

#include <stdio.h>

void printit(int arr[],int line);

/*计算第i行数据,Yang为一维(一行)数组*/

void triangle(int Yang[], int i)

{

       int j;

           Yang[i] = 1;        /*i行的最后一个元素位置为i (对角线)*/

              for(j=i-1; j>0; j–)    /*从后向前依次计算,第0个数据不必重算*/

              {

                     Yang[j] = Yang[j-1]+Yang[j];   /*=左为第i行数据,=右实际是上一行数据*/

              }

}

/*主函数,存储空间满足最后一行的需要即可*/

int main()

{

       int Yang[20];  

       int line;

       printf(“Please input the number of lines<%d: “, 20);

       scanf(“%d”,&line);

       printit(Yang, line);   /*将每一行数据的生成放在打印过程中控制*/

       system(“pause”);

       return 0;

}

/*打印共line行的三角形*/

void printit(int Yang[], int line)

{

       int i,j;

       for(i=0; i<line; i++)    /*逐行生成并打印数据*/

       {

              printf(“/n”);

              triangle(Yang,i);   /*生成第i行数据*/

              for(j=0; j<=i; j++)  /*打印第i行数据*/

              {

                     printf(“%5d”,Yang[j]);

              }

       }

       printf(“/n”);  

}

[评价3]

这是典型的递推迭代法,从时间效果上看,用上一行的数据递推出新一行的数据;从存储空间上,重复使用(迭代)同一组空间(关于递推和迭代的准确意义,笔者不做深入讨论)。

 

[分析4]

事实上许多读者都知道杨辉三角形的任一行数据是k次二项式公式展开后的各项系数,如

a+b4 = 1*a4  + 4*a3*b + 6*a2*b2 + 4*a*b3 + 1*b4

特别的,k=0

a+b0 = 1

而这些系数还可以用组合数CKi表示:

CKi  = K/i*K-i)!)

K是二项式的冖次,也是杨辉三角形的第K行;

i是展开式的项次,也是杨辉三角形第K行的项次(Ki都从0开始,从而可见语言中数组元素的下标从0开始,在数学上是正确的)。

组合公式的计算中如果直接使用阶乘则存在着大量冗余计算,并且先计算K!可能值要溢出(超出数据类型的表示范围)。以C72的计算为例,导出一个计算量小的过程:

C72 =7/2*7-2)!)=7*6*5*4*3*2*1/((2*1*5*4*3*2*1))

   =7*6/2*1=7/2*6/1

可以看出:

*计算步数可控制在2次(27-2两个数中的较小数)

*每一步计算值不会象阶乘那样增长很快

*注意整数除法带来的误差

[设计4]

#include <stdio.h>

long Com(int m,int n);    /*组合公式计算*/

void printit(int line)

{    

       int i,j;

       for(i=0; i< line; i++)

       {

              printf(“/n”);

              for(j=0; j<=i; j++)

                     printf(“%5d”,Com(i,j));    /*直接输出组合数*/

       }

}

int main()

{

       int line;

       printf(“Please input the number of lines: “);

       scanf(“%d”,&line);

       printit(line);

       printf(“/n”);

       system(“pause”);

       return 0;

}

long Com(int m, int n)

{

       int i, min;

       double p=1;     /*不用整数类型,避免整除的误差*/

       min = (n < (m-n)) ? n : m-n; 

       if(min == 0 )

           return 1;   /* Cm0    Cmm */

       n = min;        /*使用形参变量作为工作变量不是好习惯*/

       for(i=0; i<min; i++)

       {

           p = p * m / n ;

              m–;

              n–;

       }

       return (long)p;  

}

[评价4]这是典型的解析法,从时间效果上看,由于存在着大量的重复运算(如同一行数据是对称的,不必重复计算,等等),效率极低;从存储角度上看,没有使用数组,节省了运行空间—-又是一个以时间换空间的例子。

[提示]1.我们调整一下运算顺序:7/2*6/1=(7/1)*(6/2)=7/1*6/2,会发现即使使用整数运算也不会出现整除误差(这涉及到一个代数原理:连续的n个整数积一定能被n整除)

2.当计算出C72=7*6/2*1)), C73=7*6*5/3*2*1))应该怎样计算,能否利用已知结果进行计算,作为本题目的扩展性问题,请读者自行考虑。

一般情况下,程序员应当做出选择,将时间空间性能控制在自己能够掌控的范围内,—-读者应当在学习了《数据结构》之后进行更深入的讨论。

 

    原文作者:杨辉三角问题
    原文地址: https://blog.csdn.net/cao_julians/article/details/5687470
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞