递归创建和显示人的金字塔-C

时间:2018-11-12 02:36:16

标签: c recursion

问题:我试图获取两个数组,并将它们传递给一个函数,该函数应该递归地显示字母“ people”的列表 ,然后将它们放置在金字塔中,在这里,我将底层人的体重压在他们上方的一半,再加上他们自己的体重,以此显示底层人的膝盖的重量。因此,最上方的人保持着自己的体重,而金字塔侧面的人只握着一个人的一半,其他人则握着2。我在一次绝对的噩梦中了解并弄清楚如何创建一个递归调用将处理此问题。

我创建了两个函数,一个函数在用户输入底行的人数后,生成列表中的人数。下一个功能只是根据总金字塔中有多少人,给每个人一个字母。

我已经将两个数组(权重和字母字符)以及总人数传递给了该函数。

我该如何创建一种根据膝盖的重量来递归生成金字塔的方法?

示例:

How many people are on the bottom row? 4
Each person's own weight:
51.18
55.90 131.25
69.05 133.66 132.82
53.43 139.61 134.06 121.63

Weight on each person's knees:
 51.18
 81.49 156.84
 109.80 252.82 211.24
 108.32 320.92 366.09 227.25

理想情况下,我想将它们各自的字母附加到值上,但这不是必需的。

甚至不确定我的函数是否具有足够的值,或者是否应该创建2D数组而不是单个数组。无法解决整个问题。

代码: 最终函数有错误,无法计算要递归调用正确输出的内容。

int bottomRow(int userNum);

float weightedKness(float arr[], int totalpeople, char letters[]);


int main() {
    int bottomrow, total_people, k = 1, j = 1;
    char letterPerson;

    printf("How many people on the bottom row?\n");
    //if 7 is entered, the letter assignment will go out of alphabet's range
    scanf("%d", &bottomrow);

//recursively finding the total number of people in the pyramid
//printf("Total people in pyramid is: %d\n", bottomRow(bottomrow));
    total_people = bottomRow(bottomrow);//total_people is an integer of all people

//populating array
    float people[total_people];
    for (; j <= total_people; ++j) {
        //insert randomized weight here between 250 and 50 (child - hopefully they aren't on the bottom)
        people[j] = 50;
        //printf("Weight of person %d is %.2flbs\n", j, people[j]);
    }

//printf("Total people is %d\n", total_people);

//populating an array of chars to align to the array of weights
    char assignedLetter[total_people];
    letterPerson = 'A';
    for (; k <= total_people; ++k) {
        assignedLetter[k] = letterPerson++;

        //printf("%d is now %c\n", k, array[k]);
    }

    for (int i = 1; i <= total_people; ++i) {
       // printf("Weight of person %c is %.2flbs\n", assignedLetter[i], people[i]);

    }

    //weightedKness(people, total_people);
    /* char letterPerson = {'@'};//holds an array of letters based on the amount of people
     //starting the ascii value to 1 before capital A
     for (; i <= total_people; ++i) {
         ++letterPerson;
         printf("Weight of person %c is %.2flbs\n", letterPerson, people[i]);
         //send through corresponding letter and weight based on i

     }

 */

    return 0;
}

int bottomRow(int userNum) {

    if (userNum == 1) {
        return 1;
    }
    //printf("Num is %d\n", userNum);
    //finding total of people in pyramid based on the given bottom
    return bottomRow(userNum - 1) + userNum;

}


float weightedKness(float arr[], int totalpeople, char letters[]) {
int list_start = totalpeople;


    if (totalpeople < 0) {
        return 0;
    }

if (arr[1] && letters[1] ) {
    return 0;
}



return weightedKness(arr[totalpeople-1],totalpeople-1, letters[totalpeople-1] + )
}

感谢您提供的任何指导!

1 个答案:

答案 0 :(得分:1)

一个递归函数,要求(1)一个将停止递归的递归测试条件,以及(2)该函数内的递归调用。

注意...避免在简单的过程方法可行的地方使用递归。每个递归调用本身就是一个单独的函数调用,需要单独的函数堆栈,局部变量等。如果递归需要太多的递归调用,则可以相当轻松地耗尽可用内存。选择递归解决方案之前,请确保您了解函数可以被调用多少次。

也就是说,存在一些问题,即递归提供了一种非常优雅的解决方案,而没有潜在的内存耗尽问题。排列,阶乘等都是很好的例子。递归函数也是一个很重要的作业问题,因为它是编程的必要领域,它要求您仔细考虑进行递归调用时会发生什么以及满足递归测试条件后会发生什么(以及每个递归调用返回时,都必须从递归中“放松”。

在您的情况下,将为您提供包含每个人的体重的数组,并将其与大小相同的单独数组一起传递,以计算金字塔中每个点的人的体重。您必须在金字塔的每个点上同时输出人员阵列和权重阵列。

您的递归测试条件非常简单,您将进行递归调用以覆盖people数组中的每一行,以计算权重。您将把当前行作为函数参数传递,因此您的递归测试仅是您的行数已达到金字塔的大小(数组的大小)。

在初始递归调用和每个递归调用中,您需要(1)打印people数组,并(2)根据上述人员计算权重,然后在之前在函数中进行递归调用。然后 进行递归调用后,您将需要打印计算出的权重-但是要小心,当您从每个递归调用返回并“展开”递归时,将使用您所使用的行计数器在其极限处并返回零。在递归调用之后,处理数组索引将需要一些计划。

例如,设置递归函数,您可能会想到以下内容:

#include <stdio.h>

#define SIZEP 4 /* constant for rows/cols - otherwise VLA or allocate */

void pyramid (double (*people)[SIZEP], double (*weight)[SIZEP], int row)
{
    if (row < SIZEP) {  /* recursive test condition */
        /* handle all computations/print people here */
        pyramid (people, weight, row + 1);  /* recursive call */
        /* print your weights here (but reverse the indexes) */
    }
}

现在您有了一个大纲,尽管如何处理编写函数的工作与编写任何函数的工作没什么不同。您考虑必须满足的任何特殊条件(例如,第一行,顶部没有重量,只有要计数的人,金字塔边缘阵列的第一和最后一个元素仅在上方承载单个人的重量,等等。)因此,只需将特殊条件并入递归函数的流程中就可以了,但是请记住,您只有一个递归函数,因此函数本身每次调用时都必须处理这些特殊条件中的每一个-它们是否适用于当前行数。

这里的方法非常简单,您要检查是否在金字塔的第一行,然后将权重复制到weight数组中。对于所有其他行,您将需要三个条件(1)处理第一个元素(左边缘)的计算; (2)处理金字塔中的所有内部位置(如果有),并且(3)处理行中的最后一个元素(右边缘)。在进行递归调用之前,您将以相同的方式打印和计算权重。

    if (row < SIZEP) {  /* recursive test condition */

        /* recursion */
        if (row == 0) { /* print people, set weight */
            printf ("%6.2lf\n", people[row][0]);
            weight[row][0] = people[row][0];
        }
        else {
            /* print 1st person, set 1st weight */
            printf ("%6.2lf", people[row][0]);
            weight[row][0] = people[row][0] + weight[row-1][0] / 2.0;
            /* print inner people, set innter weights */
            for (int i = 1; i < row; i++) {
                printf (" %6.2lf", people[row][i]);
                weight[row][i] = people[row][i] + 
                            (weight[row-1][i-1] + weight[row-1][i]) / 2.0;
            }
            /* print last person, set last weight */
            printf (" %6.2lf\n", people[row][row]);
            weight[row][row] = people[row][row] + weight[row-1][row-1] / 2.0;    
        }

        pyramid (people, weight, row + 1);  /* recursive call */
        ...

现在,row == SIZEP会发生什么??您的递归函数开始从递归调用返回。因此,如果您在传递row + 1row == SIZEP的地方进行了最后的递归调用,则在递归调用之后立即开始返回和展开。 row的值在这里是什么? (如果您通过row + 1进行了递归调用,并在测试条件下返回,则row尚未更改,它仍将是最后一行(例如,您的情况是3)。

最后一次递归调用中发生的所有事情是:

void pyramid (double (*people)[SIZEP], double (*weight)[SIZEP], int row)
{
    if (row < SIZEP) {  /* recursive test condition -- FAILED */
    }
}

,现在您可以返回上一个调用,开始返回并使用row展开索引,将索引保留到数组的最后一行。递归调用下面发生的只是打印weight数组。但是,您不想上下颠倒打印它,因此需要处理row的值才能将展开索引映射到相同的0-3而不是3-0

使用一个简单的变量来反转行(例如revrow-我不喜欢键入),并使用从{{1}中减去当前的row值(+1) },并以此作为索引来打印SIZEP,例如

weights

您不必担心传递任何东西,因为递归调用逐渐消失,每次返回后的 ... pyramid (people, weight, row + 1); /* recursive call */ /* return from recursion */ int revrow = SIZEP - (row + 1); /* print weights in reverse order */ if (revrow == 0) /* same logic as computing weights applies */ printf ("\n%6.2lf\n", weight[revrow][0]); else { printf ("%6.2lf", weight[revrow][0]); for (int i = 1; i < revrow; i++) printf (" %6.2lf", weight[revrow][i]); printf (" %6.2lf\n", weight[revrow][revrow]); } } } 的值将是进行递归调用之前该函数中的值。

在一个简短的示例中将其完全放入,您将具有以下内容:

row

使用/输出示例

#include <stdio.h>

#define SIZEP 4 /* constant for rows/cols - otherwise VLA or allocate */

void pyramid (double (*people)[SIZEP], double (*weight)[SIZEP], int row)
{
    if (row < SIZEP) {  /* recursive test condition */

        /* recursion */
        if (row == 0) { /* print people, set weight */
            printf ("%6.2lf\n", people[row][0]);
            weight[row][0] = people[row][0];
        }
        else {
            /* print 1st person, set 1st weight */
            printf ("%6.2lf", people[row][0]);
            weight[row][0] = people[row][0] + weight[row-1][0] / 2.0;
            /* print inner people, set innter weights */
            for (int i = 1; i < row; i++) {
                printf (" %6.2lf", people[row][i]);
                weight[row][i] = people[row][i] + 
                            (weight[row-1][i-1] + weight[row-1][i]) / 2.0;
            }
            /* print last person, set last weight */
            printf (" %6.2lf\n", people[row][row]);
            weight[row][row] = people[row][row] + weight[row-1][row-1] / 2.0;    
        }

        pyramid (people, weight, row + 1);  /* recursive call */

        /* return from recursion */
        int revrow = SIZEP - (row + 1); /* print weights in reverse order */

        if (revrow == 0)    /* same logic as computing weights applies */
            printf ("\n%6.2lf\n", weight[revrow][0]);
        else {
            printf ("%6.2lf", weight[revrow][0]);
            for (int i = 1; i < revrow; i++)
                printf (" %6.2lf", weight[revrow][i]);
            printf (" %6.2lf\n", weight[revrow][revrow]);
        }
    }
}

int main (void) {

    double people[SIZEP][SIZEP] =  {{ 51.18 },
                                    { 55.90, 131.25 },
                                    { 69.05, 133.66, 132.82 },
                                    { 53.43, 139.61, 134.06, 121.63 }},
        weight[SIZEP][SIZEP] = {{ 0 }};

    pyramid (people, weight, 0);

    return 0;
}

考虑一下。递归函数需要稍微不同的思维方式才能使它们有意义。但是,当您意识到自己只是在重复调用传入的同一个函数,然后在递归结束时处理这些调用中的每一个的返回时,它将开始陷入。

在C99 +上使用VLA

除了声明一个整数常量$ ./bin/pyramidrecurse 51.18 55.90 131.25 69.05 133.66 132.82 53.43 139.61 134.06 121.63 51.18 81.49 156.84 109.79 252.82 211.24 108.33 320.92 366.09 227.25 以将SIZEPpeople声明为具有自动存储类型的数组外,您还有两个其他选项可以允许您可以根据用户输入来调整两个数组的大小。首先,如果您使用的是C99 +编译器,则使用可变长度数组。具有自动存储持续时间的数组需要 Integer Constant 作为数组声明的大小,而VLA允许使用变量来调整数组大小。 (警告:不能使用普通的初始化程序,例如weight等初始化VLA,相反,您必须使用{{0}}或循环来手动初始化VLA)

此外,如注释中所述,在将VLA作为参数传递时,必须在数组之前传递size作为参数,这样才能知道数组的大小,例如memset。如果function (int size, vla1[size], vla2[size])没有在数组之前传递,那么sizesize中是未知的,等等。

使用VLA或使用vla1[size]等进行动态分配之间的主要区别在于,如果要使用VLA,则仍必须在声明数组之前获取数组大小的输入。无法调整VLA的大小。 mallocmalloc可以动态地增加数组的存储量,而无需事先知道行数或元素数。您只需分配一些合理预期的大小,如果在完成读取所有输入之前已达到该大小,则可以为该内存块调用realloc并即时分配更多(但确实需要一些)更多的代码行,这并没有什么困难,它只需要几个变量即可跟踪已分配的内存量,已使用的内存量以及reallocused == allocated时更新变量使用新的大小分配,并继续前进。

在下面的示例中,realloc被作为第一个输入(从下面的文件中读取,但是可以很容易地在size上输入),使用{{1 }},使用stdin将VLA设置为全零,然后将其传递给递归size函数。 请注意,并且在将计算和打印移至递归前调用memset时,递归函数已分为三个函数,以简化对实际递归pyramid函数的理解函数和pyramid函数中递归后展开。

(它在功能上没有什么区别,但是将代码“分解”为简单易懂的代码位可以使事情变得平直)

compute

示例输入文件

unwind

使用/输出示例

#include <stdio.h>
#include <string.h>

void compute (int size, double (*people)[size], double (*weight)[size], int row)
{
    if (row == 0) { /* print people, set weight */
        printf ("%6.2lf\n", people[row][0]);
        weight[row][0] = people[row][0];
    }
    else {
        /* print 1st person, set 1st weight */
        printf ("%6.2lf", people[row][0]);
        weight[row][0] = people[row][0] + weight[row-1][0] / 2.0;
        /* print inner people, set inner weights */
        for (int i = 1; i < row; i++) {
            printf (" %6.2lf", people[row][i]);
            weight[row][i] = people[row][i] +
                        (weight[row-1][i-1] + weight[row-1][i]) / 2.0;
        }
        /* print last person, set last weight */
        printf (" %6.2lf\n", people[row][row]);
        weight[row][row] = people[row][row] + weight[row-1][row-1] / 2.0;
    }
}

void unwind (int size, double (*weight)[size], int row)
{
    int revrow = size - (row + 1); /* print weights in reverse order */

    if (revrow == 0)    /* same logic as computing weights applies */
        printf ("\n%6.2lf\n", weight[revrow][0]);
    else {
        printf ("%6.2lf", weight[revrow][0]);
        for (int i = 1; i < revrow; i++)
            printf (" %6.2lf", weight[revrow][i]);
        printf (" %6.2lf\n", weight[revrow][revrow]);
    }
}

void pyramid (int size, double (*people)[size], double (*weight)[size], int row)
{
    if (row < size) {  /* recursive test condition */

        /* computations before recursive call */
        compute (size, people, weight, row);

        /* recursive call */
        pyramid (size, people, weight, row + 1);

        /* return from recursion */
        unwind (size, weight, row);
    }
}

int main (int argc, char **argv) {

    int size;   /* read from user or file */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    if (fscanf (fp, "%d", &size) != 1) {    /* read size */
        fprintf (stderr, "error: invalid format '%s'\n", argv[1]);
        return 1;
    }
    double  people[size][size],     /* declare VLAs, but you */
            weight[size][size];     /* can't initialize VLAs */

    memset (people, 0, size * sizeof *people);  /* zero both arrays */
    memset (weight, 0, size * sizeof *weight);

    for (int i = 0; i < size; i++)
        for (int j = 0; j <= i; j++)
            if (fscanf (fp, "%lf", &people[i][j]) != 1) {
                fprintf (stderr, "error: reading people[%d][%d]\n", i, j);
                return 1;
            }
    fclose (fp);   /* close file */

    pyramid (size, people, weight, 0);  /* compute/print arrays */

    return 0;
}

$ cat dat/pyramidweight.txt 4 51.18 55.90 131.25 69.05 133.66 132.82 53.43 139.61 134.06 121.63 $ ./bin/pyramidrecursefnvla dat/pyramidweight.txt 51.18 55.90 131.25 69.05 133.66 132.82 53.43 139.61 134.06 121.63 51.18 81.49 156.84 109.79 252.82 211.24 108.33 320.92 366.09 227.25 -完成存储难题

如上所述,使用malloc动态分配存储空间的主要优势在于,无需事先知道calloc。意思是,如果用户(或您的文件)不包含malloc, calloc, realloc,则可以简单地动态分配一些合理数量的指针,并为每个指针分配一定数量的size值,并开始读取您的数据。

不要求分配用于保存行值的每个块与之前或之后的行分配相同数量的size个值。 (您可以通过存储在第一行数据中读取的值的数量并与其他每行读取的值进行比较(例如,如果您正在读取一个方阵数据)来强制执行此要求)。但是在这里,由于您有double作为输入,因此可以使用它,它消除了“最初分配多少指针?”的猜测工作,从而简化了过程。 (我们知道,其中double

在进一步讨论之前,我们在关于动态分配的讨论中使用了“ pointer” 一词,而在其他两种情况下,我们使用了“ array” 。这是一个重要的区别。 数组不是指针,指针也不是数组,但是请理解,访问时数组会转换为指针。具体来说:C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)

  

(p3)除非它是size运算符的操作数,否则   size运算符,或一元sizeof运算符,或者是字符串   文字用于初始化数组,该表达式具有类型   “类型为数组” 转换为类型为“类型为指针” 的表达式,该表达式指向数组对象的初始元素,并且   不是左值。

由于您要传递指向指向 _Alignof的指针,因此'&'double的函数参数将需要从指向 people数组的指针,指向指向 weight的指针的指针,例如

double

如果分配...,您必须验证!

动态分配时,double可以并且确实在内存耗尽时失败。当它们失败时,它们各自返回指示失败的void pyramid (int size, double **people, double **weight, int row) 。否则,它们返回您分配给指针的已分配内存块的起始地址,然后可以使用数组符号-或指针符号(例如{{1 }}的数组表示法与malloc, calloc & realloc的指针表示法相同,并且由于数组在访问时会转换为指针,因此您也可以对数组使用这两种表示法。

NULL中有哪些变化?您以people[2]*(people + 2)开头的声明,例如

main()

people作为输入后,分配指针(每个指针weight),例如

    double  **people = NULL,    /* pointer to pointer to double */
            **weight = NULL;

注意:在分配存储空间时,您需要将size乘以size来分配您要分配的任何内容。虽然可以使用类型,例如 /* first allocate size pointers to each people and weight */ people = malloc (size * sizeof *people); if (people == NULL) { /* validate every allocation */ perror ("malloc-people"); exit (EXIT_FAILURE); } weight = malloc (size * sizeof *weight); if (!weight) { /* validate every allocation */ perror ("malloc-weight"); exit (EXIT_FAILURE); } 最好总是使用解引用变量本身来调整类型的大小,例如,如果要分配的sizesizeof,则如果使用sizeof (double*),则people是仅double**。这将确保您永远不会弄错类型大小。对于复杂的类型,很可能会出错,并且通常会猜测类型是否为例如指向指向等的指针数组)

这时,每个分配的指针都有sizeof *people个指针。虽然您可以猜测值的数量,但是请分配该数量的双精度数,如果达到了恰好位于的极限,则分配*people -(但是这需要重新处理从value-at读取的输入一次一次一行,然后解析-如果您很好奇,我会留给您),但是在这里,因为我们知道我们正在建模double*并且我们有size分配了指针,剩下的就是为每个指针分配realloc倍数,例如

array[size][size]

请注意,再次使用array[size]size键入字体大小)

将其放在一起,最终得到:

    /* now allocate a block of size double for each pointer,
    * let's use calloc here to both allocate and set all bytes zero.
    */
    for (int i = 0; i < size; i++) {                /* loop over pointers */
        people[i] = calloc (size, sizeof *people[i]); /* allocate doubles */
        if (!people[i]) {           /* validate every allocation */
            perror ("calloc-people[i]");
            exit (EXIT_FAILURE);
        }

        weight[i] = calloc (size, sizeof *weight[i]); /* allocate doubles */
        if (!weight[i]) {           /* validate every allocation */
            perror ("calloc-weight[i]");
            exit (EXIT_FAILURE);
        }
    }

示例输入和使用/输出

输入和输出相同。

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,sizeof *people[i]是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

sizeof *weight[i]

始终确认已释放已分配的所有内存,并且没有内存错误。

仔细检查一下,如果还有其他问题,请告诉我。