zoukankan      html  css  js  c++  java
  • 赏月斋源码共享计划 第一期

    #include <stdio.h>
    //using  namespace std;
    /* 
    using namespace std;
    If you see the following error when trying to compile a C++ application:
    main.cpp: : : warning: using directive refers to implicitly-defined namespace 'std'
    then that means you do not have any header file inclusion that uses std namespace.
    You can fix this warning by including a C++ header file that uses a std namespace otherwise the compile will not know about std namespace.
     */
    
    int c[101][101] = {{0}}; //数组初始化必须用花括号
    /* int c[101][101] = {0};  
    warning: “suggest braces around initialization of subobject [-Wmissing-braces]”
    This warning should not be suppressed. 
    If the code is according to C++11, initializer list using {{ .. }} is recommended instead of single { .. }.
    There is a purpose for it and hence the warning should not be suppressed instead of improving the code that can cause problems later.
     */
    
    void init_matrix(void)
    { //生成杨辉三角, c[i][j]表示组合数c_i^j
        int i, j;
        c[0][0] = 1;
        for (i = 1; i < 101; ++i)
        {
            c[i][0] = 1;
            for (j = 1; j < 101; ++j)
            {
                c[i][j] = c[i - 1][j - 1] + c[i - 1][j]; //杨辉三角,某元素等于上一行两个对应元素之和,对应二项展开式(a+b)^n 的系数
            }
        }
    
        //测试用例
        // for (i = 0; i < 7; ++i)
        // {
        //     for (j = 0; j < 7; ++j)
        //     {
        //         printf("%d ", c[i][j]);
        //     }
        //     printf("
    ");
        // }
    }
    
    int main(void)
    {
        int k, a, x, b, y;
        int i, ans = 0;
        init_matrix();
    
        scanf("%d", &k);
        scanf("%d%d%d%d", &a, &x, &b, &y);
    
        for (i = 0; i < x; ++i) //i代表了歌曲a数目的可能取值
        {
            if (i * a <= k && (k - i*a)%b == 0 && (k-i*a)/b <= y )  
            {//第一项判断a歌曲数目小于k,第二项判断选好a后剩余长度能被b整除,第三项判断需要的b歌曲数目小于y
                ans += c[x][i] * c[y][(k - i * a) / b];
            }
        }
    
        printf("%d
    ", ans);
    
        return 0;
    }
    
    /* 1、注释 快捷键:
    
      a) 单行注释:[ctrl+k,ctrl+c] 或 ctrl+/
    
      b) 取消单行注释:[ctrl+k,ctrl+u] (按下ctrl不放,再按k + u)
    
      c) 多行注释:[alt+shift+A]
    
      d) 多行注释:/** 
    */
    
    /* 
    关于数组初始化的问题:
    
    只说一句: 数据的初始化 按行,一行一行的初始化, 我可以不知道有多少行, 但是我必须知道一行放几个数据(也就是有几列), 这是二维, 然后扩展3维, 我可以不知道有几页数据,但是我必须知道一页数据存储几行几列.
    
    ---华丽分割线---
    
    大于等于二维的,第一个都可以省略. 其他的都不可以省略. 第一个不一定是行数. 只是打个比喻,这么理解容易记忆.
    
    就像你有一些 麻将, 按行来排列. 你只需要知道一行放几个(几列), 就可以知道放几行. (2维) 
    int arr[行数][5];
    如果知道了行和列, 你就能推算出放几层了. (3维) 
    int arr[层数][5][5];
    如果知道了行,列,层, 我就能知道放几堆了. (4维)
    int arr[堆数][5][5][5];
    如果知道了行,列,层,堆, 我就能推算出可以放在几张桌子上了.(5维)
    int arr[桌子数][5][5][5][5];
    如果知道了行,列,层,堆,桌子的张数, 我就能推算出, 我要用几间屋子来存放了.(6维)
    int arr[屋子][5][5][5][5][5];
    ...
    
    数字化之后:
    int arr[n-1]...[6][5][4][3][2][1][0]; 
    
    n-1下标是可以省略的.其他都不可以;
    对于多维数组的初始化,我们根本不需要这么多花括号。在《C和指针》第162页作者有指出,用花括号只是为了好识别罢了
    掌握方法,学会类推,比什么都重要;别死记答案.
    
    增加了一行注释---
     */

    源码解析及相关资料:

    说明:有X首长度为A的不同的歌,和Y首长度为B的不同的歌,欲用这些歌组成一个总长度恰好为K的歌单,每首歌最多出现一次,不考虑先后顺序,求有多少种组成歌单的方法。

    杨辉三角的几种实现方案:

         本文给出杨辉三角的几种C语言实现,并简要分析典型方法的复杂度。

         本文假定读者具备二项式定理、排列组合、求和等方面的数学知识。

    一  基本概念

         杨辉三角,又称贾宪三角、帕斯卡三角,是二项式系数在三角形中的一种几何排列。此处引用维基百科上的一张动态图以直观说明(原文链接http://zh.wikipedia.org/wiki/杨辉三角):

         从上图可看出杨辉三角的几个显著特征:

         1. 每行数值左右对称,且均为正整数。

         2. 行数递增时,列数亦递增。

         3. 除斜边上的1外,其余数值均等于其肩部两数之和。

         杨辉三角与二项式定理有密切关系,即杨辉三角的第n行(n=0…MAX_ROW)对应二项式(a+b)n展开(Binomial Expansion)的系数集合。例如,第二行的数值1-2-1为幂指数为2的二项式(a+b)2展开形式a+ 2ab + b2的系数,即

         应用组合公式可推导出杨辉三角的特征1和3,如下:

     

    二  题目要求

         用C语言编程打印出MAX_ROW行杨辉三角数,如(MAX_ROW=5):

    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("
    ");
    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("
    ");
    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("
    ");
    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("
    ");
    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("
    ");
    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
    ", 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(而非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("
    ");
    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("
    ");
    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因消耗大量栈空间故复杂度也较高。

     https://www.cnblogs.com/clover-toeic/p/3766001.html

      

  • 相关阅读:
    3D Computer Grapihcs Using OpenGL
    3D Computer Grapihcs Using OpenGL
    3D Computer Grapihcs Using OpenGL
    3D Computer Grapihcs Using OpenGL
    转:RealThinClient LinkedObjects Demo解析
    转:RealThinClient (RTC)是什么?
    DataSanp的控制老大-DSServer
    5.Firedac错误信息
    4.FireDAC组件快照 二
    3.FireDAC组件快照
  • 原文地址:https://www.cnblogs.com/sddai/p/9520628.html
Copyright © 2011-2022 走看看