【问题标题】:C recursive program to find the maximum element from arrayC递归程序从数组中找到最大元素
【发布时间】:2020-10-05 15:36:27
【问题描述】:

所以我在训练中有一个任务听起来像这样: 编写一个子程序,它将递归地从数组中找到最大元素,并编写 main 函数来调用它。 我未能完全理解的是递归是什么。我想问你们我的代码是否是递归的。如果不是,我应该进行哪些更改/递归的真正含义是什么?

#include <stdio.h>

int find_maximum(int[], int); 

int main() {
  int c, array[100], size, location, maximum;

  printf("Input number of elements in array\n");
  scanf("%d", &size);

  printf("Enter %d integers\n", size);

  for (c = 0; c < size; c++)
    scanf("%d", &array[c]);

  location = find_maximum(array, size);
  maximum  = array[location];

  printf("Maximum element location = %d and value = %d.\n", location + 1, maximum);
  return 0;
}

int find_maximum(int a[], int n) {
  int c, max, index;

  max = a[0];
  index = 0;

  for (c = 1; c < n; c++) {
    if (a[c] > max) {
       index = c;
       max = a[c];
    }
  }

  return index;
}

感谢大家的宝贵时间!

【问题讨论】:

  • 当您搜索“递归”的定义时,最直接的三个结果是什么?
  • 您对作业的描述不明确。是非得找最大值(但不关心位置),还是非得找保持最大值的位置。两者都是有效的要求,但它们是不同的。显示的代码找到了位置。

标签: c recursion


【解决方案1】:

非常适合递归的问题可以分解为更小、更简单的子问题。这是赋予递归强大功能的原因之一。在尝试使用递归解决问题时,通常最好尝试将问题分解为更简单的子问题以找到解决方案。

您可能会注意到,在查找存储在数组中的最大值 value 时,它要么是第一个元素的值,要么是其余元素的最大值。这将问题分为两部分:如果第一个元素大于任何其余元素,则完成;否则,您必须继续查看下一个元素是否大于其余元素。在代码中,这可能看起来像:

int max_in(size_t rest_sz, int *rest)
{
    int curr_val = rest[0];
    if (rest_sz == 1) {
        return curr_val;
    }

    int max_in_rest = max_in(rest_sz-1, rest+1);

    return curr_val > max_in_rest ? curr_val : max_in_rest;
}

这里有一个基本情况:如果rest_sz为1,则无需再看;第一个元素 (curr_val = rest[0]) 的值是最大值,并返回该值。如果不满足基本情况,则继续执行函数。 max_in_rest 是递归函数调用 max_in(rest_sz-1, rest+1) 的结果。这里rest_sz-1 表示rest+1 指示的数组部分中剩余的元素数。在新的函数调用中,基本情况再次遇到,最终这种情况将成立,因为rest_sz 每次递归调用都会递减。发生这种情况时,将返回当前堆栈帧中curr_val 的值;请注意,此值是数组中 last 元素的值。然后,当函数返回给它的调用者时,该帧中的max_in_rest 将获得返回值,然后将curr_valmax_in_rest 中的较大者返回给前一个调用者,以此类推,直到最终返回控制权到main()

使用铅笔和纸来绘制每个函数调用、其变量的值以及返回的内容,这将有助于准确理解此递归的工作原理。

你可以用同样的方法来解决寻找数组最大值的索引的问题。在这种情况下,如果第一个元素的值大于任何剩余元素的值,那么最大元素的索引就是第一个元素的索引;否则最大元素的索引是剩余元素最大值的索引。在代码中,这可能看起来像:

size_t find_max_r(int arr[], int *rest, size_t rest_sz, size_t curr_ndx)
{
    if (rest_sz == 1) {
        return curr_ndx;
    }

    int curr_val = arr[curr_ndx];
    size_t max_in_rest_ndx = find_max_r(arr, rest+1, rest_sz-1, curr_ndx+1);
    int max_in_rest = arr[max_in_rest_ndx];

    return curr_val >= max_in_rest ? curr_ndx : max_in_rest_ndx;
}

只有一点点信息可以跟踪这个时间。在这里,如果满足基本情况,并且rest_sz为1,那么没有理由再看下去,当前索引curr_ndx是最大值的索引。否则,find_max_r() 被递归调用,rest 递增以指向数组的剩余元素,rest_sz 适当递减。这一次,curr_ndx 正在跟踪相对于原始数组的当前索引,并将这个值传递给每个函数调用;此外,指向原始数组第一个元素arr 的指针被传递到每个函数调用中,因此索引值curr_ndx 可以访问原始数组中的值。

同样,当达到基本情况时,数组中的当前位置将是数组的末尾,因此在 return 语句中要比较的第一个元素将朝向数组的末尾,向前移动的数组。注意这里使用&gt;=,而不是&gt;,这样会返回第一个最大值的索引;如果您想要 last 最大值的索引,只需将其更改为 &gt;

这是一个完整的程序。注意使用辅助函数find_max()调用递归函数find_max_r(),它允许调用者使用与贴出的代码使用的签名相同的函数(除了使用size_t类型,这真的是数组索引的正确类型):

#include <stdio.h>

int max_in(size_t sz, int *rest);
size_t find_max(size_t sz, int arr[]);
size_t find_max_r(int arr[], int *rest, size_t rest_sz, size_t curr_ndx);

int main(void)
{
    int array[] = { 2, 7, 1, 8, 2, 5, 1, 8 };
    size_t array_sz = sizeof array / sizeof array[0];

    int max_val = max_in(array_sz, array);
    printf("Maximum value is: %d\n", max_val);

    size_t max_ndx = find_max(array_sz, array);
    printf("Maximum value index: %zu\n", max_ndx);

    return 0;
}

int max_in(size_t rest_sz, int *rest)
{
    int curr_val = rest[0];
    if (rest_sz == 1) {
        return curr_val;
    }

    int max_in_rest = max_in(rest_sz-1, rest+1);

    return curr_val > max_in_rest ? curr_val : max_in_rest;
}

size_t find_max(size_t sz, int arr[])
{
    int *rest = arr;
    return find_max_r(arr, rest, sz, 0);
}

size_t find_max_r(int arr[], int *rest, size_t rest_sz, size_t curr_ndx)
{
    if (rest_sz == 1) {
        return curr_ndx;
    }

    int curr_val = arr[curr_ndx];
    size_t max_in_rest_ndx = find_max_r(arr, rest+1, rest_sz-1, curr_ndx+1);
    int max_in_rest = arr[max_in_rest_ndx];

    return curr_val >= max_in_rest ? curr_ndx : max_in_rest_ndx;
}

程序输出:

Maximum value is: 8
Maximum value index: 3

【讨论】:

    【解决方案2】:

    考虑将数组中的最大数计算为第一个元素的最大值和数组其余元素的最大值。类似于:max(first_elem, max(remaining_elems))。

    实际的recursive 函数:find_max 非常简单,如果数组中只有一个元素,则返回该元素。否则,我们得到数组的第一个元素和其余元素的最大值。

    #include <stdio.h>
    
    // function to find the max of 2 numbers
    int max(int x, int y)
    {
        return (x > y) ? x : y;
    }
    
    // the recursive function
    int find_max(int *p, int n)
    {
        if (n == 1) return *p;
    
        return max(*p, find_max(p + 1, n - 1));
    }
    
    int main(void)
    {
        int arr[] = {23, 3, 11, -98, 99, 45};
        printf("max: %d\n", find_max(arr, sizeof arr / sizeof arr[0]));
    }
    

    【讨论】:

    • 注意,这段代码找到最大值,但没有记录找到最大值的位置(一个位置)。问题中的代码是迭代的,但会找到保持最大值的位置。
    【解决方案3】:

    不,您的代码不使用递归。递归是指一个函数调用自身,或者调用另一个函数导致再次调用自身。

    您可以像这样更改您的代码,使其具有可以确定数组最大值的递归、无状态函数。

    int find_maximum(int a[], int n) {
       return find_maximum_r(a, 0, n);
    } 
    
    int find_maximum_r(int a[], int index, int n) {
      if (index + 1 == n) {
        return a[index];
      }
    
      int maxRight = find_maximum_r(a, index + 1, n);
      return a[index] > maxRight ? a[index] : maxRight;
    }
    

    【讨论】:

    • 谢谢@idmean。但在 main 函数中一切正常,对吧?
    • 一切正常。请注意,我的示例函数返回最大值而不是最大值的索引。
    【解决方案4】:

    不,只有当您直接或间接调用函数 find_maximum 时,您的代码才是递归的。

    由于您的函数不仅要获取最大值,还要获取数组中的位置,所以我稍微修改了接口以返回引用(即指向值的指针),以便我们可以推断位置直接从元素指针中减去数组元素。这样,我可以直接将数组指针和数组大小传递给函数,然后将数组分成两半,并对两半应用相同的函数(可以证明如果某个元素是最大值数组,它必须大于或等于每一半的最大值)出于同样的原因,我修改了您的 main() 函数中定义的一些变量,以允许使用引用:

    max.c

    #include <stdio.h>
    #include <assert.h>
    
    int *find_maximum(int a[], int n); /* return a reference pointer to the maximum value */
    
    int main() {
      int c, array[100], size, *location, /* location must be a pointer */
      maximum;
    
      printf("Input number of elements in array\n");
      scanf("%d", &size);
      assert(size >= 1);
    
      printf("Enter %d integers\n", size);
    
      for (c = 0; c < size; c++)
        scanf("%d", &array[c]);
    
      location = find_maximum(array, size);
      maximum  = *location; /* access to the value is granted by pointer dereference */
      printf("Maximum element location = %td and value = %d.\n", 
             location - array, /* pointer difference gives the array position */ 
             maximum);
      return 0;
    } /* main */
    
    /* somewhat efficient recursive way of a divide and conquer method 
     * to get the maximum element reference. */
    int *find_maximum(int a[], int n)
    {
        if (n == 1) return a; /* array of 1 element */
        int *left  = find_maximum(a,       n/2), /* left half begins at a
                                                  * and has n/2 elements */
            *right = find_maximum(a + n/2, (n+1)/2); /* right half begins 
                                                      * at a + n/2, and
                                                      * has (n+1)/2 
                                                      * elements */
        return *left > *right 
               ? left 
               : right;
    } /* find_maximum */
    

    如您所见,我必须除以 2,但由于我有 任何 长度的数组,我必须小心不要在下一步中遗漏任何元素。这就是在函数递归调用的右半部分使用(n+1)/2 元素数组的原因。我在前半部分包含n/2 元素(向下取整),我必须在右半部分包含(n+1)/2 元素(向上取整),以确保我在两半中包含所有数组元素。

    【讨论】:

    • 在引用 C 代码时要谨慎使用“引用”一词——双关语。有些人对这个术语的使用相当热心,因为它在 C++ 中具有单独的含义,并且通常不清楚人们是否认识到其中的区别。您还可以使用n - n/2 来获取数组上半部分的元素数量(而不是(n + 1) / 2)。没有实际区别——计算成本是相同的(加法或减法;除法)。
    • 嗨@JonathanLeffler,我知道。我跟着你,作为 C/C++ 的一个很好的参考,我完全理解你的意图,但是在这种情况下使用参考是正确的,因为它准确地代表了函数被假定返回的内容。在 C++ 中,返回类型确实应该是 int &amp;,并且应该避免使用许多用于取消引用它的星号......但我们谈论的是 C,而不是 C++(我之前采取了仔细检查问题标签的预防措施按照这种方式)。不管怎么说,还是要谢谢你! (感谢您的版本,因为我也不是天生的英语作家)
    • @JonathanLeffler,关于n - n/2 方法,我同意你的观点,它确实是一样的(而且确实更清楚)。写的时候根本没有想到,所以觉得有点晦涩难懂,这也是在cmets中解释的原因。
    • 请注意location - array 的类型为ptrdiff_t,因此应使用%td 转换说明符而不是%d 来打印此值,同时避免UB。另外,我没有看到这里使用的“分而治之”的方法比我的方法更有效。在 6 个元素的输入数组上,您的版本需要 11 个递归调用,而我的需要 6 个。
    • @DavidBowling,好吧,这不是一场比赛,所以如果我的方法不比你的更有效,也不会受到惩罚。尽管如此,我还是说过这样的话。无论如何,关于格式字符串的断言是正确的,但我接受过 K&R 古代的训练,没有时间研究它们(如果需要,请随时编辑答案以更正它)。效率增益来自这样一个事实,即这是一个除以二的方法,因此,它值得 log(n) 堆栈资源,而不是像头/尾方法这样的线性方法。在大型阵列上,这更好,至少在堆栈消耗方面。
    【解决方案5】:

    首先,递归的意思是——函数调用自己。

    你写的不是递归函数。我将发布使用递归查找数组中最大或最大元素的最简单方法。

    #include<stdio.h>  
      
    #define N 5  
      
    int biggest(int num[], int n, int big)  
    {  
        if(n < 0)  
            return big;  
        else  
        {  
            if(big < num[n])  
                big = num[n];  
      
            return biggest(num, --n, big);  
        }  
    }  
      
    int main()  
    {  
        int a[N], i;  
      
        printf("Enter %d integer number\n", N);  
        for(i = 0; i < N; i++)  
            scanf("%d", &a[i]);  
      
        printf("Biggest Element in the array: %d\n", biggest(a, N - 1, a[0]));  
      
        return 0;  
    } 
    

    来源:C Program To Find Biggest Element of An Array using Recursion

    【讨论】:

      【解决方案6】:

      不,它不是递归函数

      要了解递归这个链接非常有用https://www.khanacademy.org/computing/computer-science/algorithms/recursive-algorithms/a/recursion/

      创建一个递归函数来解决你的问题试试这个

      你可以试试这个伪代码声明你的数组全局和一个 max=0 全局和大小全局

      int find_maximum(int i)
      {
      if (i == size )
          return max;
      else if ( max < array[i])
          max =array [i];
      return find_maximum(i+1);
      }
      

      其中 i 是数组索引

      【讨论】:

      • 像这样使用全局变量将数据传递给函数是个坏主意。给定数组int array[] = { -4, -7, -19, -24, -3, -9 };,您的代码将错误地返回 0 作为最大值。我也不相信您提供的link — 套娃很有趣,但很难清楚地说明递归。
      【解决方案7】:

      不,您的程序肯定不是递归的。按照定义,递归函数调用自身时必须带有终止条件。

      请阅读关于 C 语言中 recursion 的 TutorialsPoint。

      @JonathanLeffler 评论的更新: 请注意参考中的输出会溢出。

      【讨论】:

      • 递归函数绝对不必有终止条件。
      • @idmean:它不会陷入无限递归导致运行时堆栈溢出吗?
      • “我肯定会提到有那个break case”——不过,通常称为base case
      • 请注意,链接到讨论Recursion 的TutorialsPoint 网站包含一个'factorial()' 示例和声明'Factorial of 15 is 2004310016' .正确答案是 1307674368000,它太大而无法放入 32 位整数。 (1307674368000 模 2^31 == 2004310016)。该示例遇到整数溢出。对于那些试图从该网站学习的人来说,这并不是一个好兆头。请找到更好的参考网站并更新您的答案。
      • 问题是,一旦一个网站发布了一个严重错误的答案并且没有注意到溢出,那么该网站所说的一切都会变得可疑。您的替代方案会产生有效的输出——但不能防止溢出。它变得混乱。我的自动选择可能是维基百科,但每个人都有自己的选择。检查您引用的内容是个好主意,以合理地确定它是正确的。也许您对 TutorialsPoint 的体验比我的更积极。
      猜你喜欢
      • 1970-01-01
      • 2015-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-16
      • 2018-11-18
      • 2016-03-27
      • 2019-10-16
      相关资源
      最近更新 更多