递归查找大量(C)中2个最大num的索引

时间:2015-07-06 09:51:02

标签: c search recursion

我试图在考试中解决这个问题。我对它感到震惊。

任何人都可以帮我写一个函数:

void get2(int a[], int n, *i1, *i2)

接收大量a []的整数和他的长度n并保存到指针i1和i2中的两个最大数字的索引(i1 - 最大,i2 - 第二大)。

解决方案必须是递归的,不能包含循环。它由O(n)的复杂性完成。大规模的数字都是不同的。我们无法使用任何辅助函数。

没有任何附加条件。我尝试了一些解决方案,但它们还不够好。 当我递归时,我不知道如何保存索引而不会丢失那些值。

还有一些提示,我应该注意我如何使用*i1*i2在递归步骤之间传递信息,但我试图使用它一些如何,它不起作用对我来说,我尝试过帮助别人?

7 个答案:

答案 0 :(得分:1)

在这个练习中只有一个技巧:当我们处于最外层的递归调用时以及当我们不在时,或当get2的当前调用是第一个时更简单的术语当它不是时。

使用指针和n参数来执行一些基本的 Divide et Impera 策略非常简单,无需进一步说明。

我们需要告诉第一个和后续的调用,因为在第一个调用中,只有那个调用,我们需要初始化指针。

我们无法依赖i1i2值,因为它们未初始化。
只有n具有足够的结构来携带一些信息:当我们从外部调用时,即不是通过重复调用,然后n >= 0 。 然后我们可以执行一次性初始化,然后否定 n。这样,如果n<=0我们知道我们处于递归调用中。

但是我们仍然遇到问题:如果n >= 0那么它是相反的n <= 0n == 0的两个条件重叠 这将使初始化发生两次,并且在递归堆栈结束时进一步最坏的第二次,从而使计算的状态无效。
为了避免它们,n不是简单地否定,而是否定并递减1 ,因此5变为-6
然后我们可以通过再次否定和递减来恢复n的可用值。在下面的代码中,这已完成并存储在m中。

else部分如上所述是直截了当的。我们另外检查第二个最大元素是否不等于第二个if中的最大元素 如果一个索引是-1,则意味着它没有指向任何元素,并且可以无条件地分配。

当函数返回时,除非数组为空,否则i1无法指向-1 如果所有元素都相同,i2可以指向-1

#include <stdio.h>

void get2(int a[], int n, int* i1, int* i2)
{
    /*  Transform from encoded n to actual length */
    int m = -n-1;

    /* Is this the first call? */
    if (n >= 0)
    {
        /* Initialize the pointer to -1, i.e. no elements */
        *i1 = *i2 = -1;

        /* Start the recursion, encode the length */
        get2(a, -n-1, i1, i2);
    }
    /* Here we are in the subsequent calls, we use m and not n */
    else if (m-- > 0)
    {
        /* Assign i1 */
        if (*i1 == -1 || a[m] > a[*i1])
        {
            *i2 = *i1;    /* Don't forget to update i2 too! */
            *i1 = m;
        }
        /* Opportunity to assign i2, check that the second max != max */
        else if ((*i2 == -1 || a[m] > a[*i2]) && a[m] != a[*i1])
        {
            *i2 = m;
        }

        /* Tail recursion, do proper encoding of the n parameter */
        get2(a, -m-1, i1, i2);  
    }
}

可以使用此代码

测试此功能
void test(int a[], int n)
{
    int max, smax, i;

    get2(a, n, &max, &smax);

    printf("------------------------------\n");

    for (i = 0; i < n; i++)
        printf("%d ", a[i]);

    printf("\nMax is ");
    if (max >= 0)
        printf("%d", a[max]);
    else 
        printf("NOT FOUND");

    printf("\nSecond Max is ");
    if (smax >= 0)
        printf("%d\n", a[smax]);
    else 
        printf("NOT FOUND\n");
}

int main()
{
    int v1[] = {};
    int v2[] = {1};
    int v3[] = {1,2};
    int v4[] = {2, 1};
    int v5[] = {2, 2};
    int v6[] = {1,2,3,4};
    int v7[] = {4,3,2,1};
    int v8[] = {2,1,4,3};
    int v9[] = {2,2,2,2};
    int v10[] = {1,2,3,4,3,2,1};
    int v11[] = {1,1,2,3,4,4};

    test(v1, 0);
    test(v2, 1);
    test(v3, 2);
    test(v4, 2);
    test(v5, 2);
    test(v6, 4);
    test(v7, 4);
    test(v8, 4);
    test(v9, 4);
    test(v10, 7);
    test(v11, 6);

    return 0;
}

使用以下输出

------------------------------

Max is NOT FOUND
Second Max is NOT FOUND
------------------------------
1
Max is 1
Second Max is NOT FOUND
------------------------------
1 2
Max is 2
Second Max is 1
------------------------------
2 1
Max is 2
Second Max is 1
------------------------------
2 2
Max is 2
Second Max is NOT FOUND
------------------------------
1 2 3 4
Max is 4
Second Max is 3
------------------------------
4 3 2 1
Max is 4
Second Max is 3
------------------------------
2 1 4 3
Max is 4
Second Max is 3
------------------------------
2 2 2 2
Max is 2
Second Max is NOT FOUND
------------------------------
1 2 3 4 3 2 1
Max is 4
Second Max is 3
------------------------------
1 1 2 3 4 4
Max is 4
Second Max is 3

答案 1 :(得分:0)

我会给你另一个提示,而不是为你编写实际的代码:

你有4个参数,使用所有参数将相关数据传递给递归调用。我的意思是 - 前2个参数可以描述我们感兴趣的数组部分,指针存储我们找到的最大整数,每次找到更大的整数时都会修改它们。

这里有一个很可能抓住你的陷阱 - 那些是int *,它是指向整数的指针。函数接收的只是内存中的地址。如果将该指针设置为指向任何内容,则调用者将不会接收该数据。您可以直接写入该地址而不是设置指针,如下所示:

int myInteger;
int *ptr = &myInteger;
*ptr = 123;

答案 2 :(得分:0)

这是一种非线程安全的方法:使用static变量跟踪数组的原始开始。最大元素的地址存储在指针中,指针用作指针的指针,最后通过使用静态变量辅助的一些指针算法将它们替换为元素的索引。

void get2(int a[], int n, int *one, int *two) {
    static int *arr_start = 0;
    if (n > 0) {
        if (arr_start == 0) {
            arr_start = a;
        }
        if (a[0] > **((int **)one)) {
            *one = a;
        } else if (a[0] > **((int **)two)) {
            *two = a;
        }
        get2(a + 1, n - 1, one, two);
    } else {
        *one = *((int **)one) - arr_start;
        *two = *((int **)two) - arr_start;
    }
}

修改

不使用static变量:使用第二个指针作为我们是否已经分配空间以跟踪数组开始和索引的指示符。使用分配的空间来跟踪初始数组大小和两个最大的索引,然后在最后将它们分配给正确的指针:

#include <stdlib.h>

void get2(int a[], int n, int *one, int *two) {
    int **pp_one = (int **)one;
    if (*two != NULL) {
         *pp_one = malloc(sizeof(int) * 3);
         (*pp_one)[0] = n;
         (*pp_one)[1] = 0;
         (*pp_one)[2] = 0;
    }
    if (n > 0) {
        if (a[0] > (*pp_one)[1]) {
            (*pp_one)[1] = a;
        } else if (a[0] > (*pp_one)[2]) {
            (*pp_one)[2] = a;
        }
        get2(a + 1, n - 1, one, two);
    } else {
        int *t = *one;
        *two = (*pp_one)[1] - (*pp_one)[0];
        *one = (*pp_one)[2] - (*pp_one)[0];
        free(t);
    }
}

答案 3 :(得分:0)

再试一次你的函数原型,

void get2(int a[], int n, int *i1, int *i2)
{
    if(n <= 0)
      return;

    if(*i1 == -1 && n < 2){//initial a[] is null or with only one element
      *i1 = n-1; 
      return;
    }

    if(*i1 == -1 && n >=2){
      if(a[n-1] > a[n-2]){
        *i1 = n-1;
        *i2 = n-2;
      }else{
        *i1 = n-2;
        *i2 = n-1;
      }
      return get2(a, n-2, i1, i2);
    }

    int max= a[*i1], less = a[*i2];
    if(a[n-1] > max){
        *i2 = *i1;
        *i1 = n-1;
    }else if (a[n-1] > less){
        *i2 = n-1;
    }
    get2(a, n-1, i1, i2);
}

测试如下,

int main()
{
   int a[] = { 1, 2, 3, 4, 7, 8, 6, 5, 0};
   int maxIndex = -1, lessIndex= -1;
   get2(a,sizeof(a)/sizeof(int),&maxIndex,&lessIndex);
   printf("maxIndex=%d(value:%d), lessIndex=%d(value:%d)\n",  
                maxIndex,a[maxIndex],lessIndex, a[lessIndex]);
}

输出,

  

maxIndex = 5(值:8),lessIndex = 4(值:7)

答案 4 :(得分:0)

感谢您的脑筋急转弯。这听起来像一个有趣的问题。即使它是作业,我决定解决它。

如果没有以某种方式预先初始化索引,这会有点烦人,但它是可行的。听起来像人为限制造成人为的疼痛。

void
get2(int a[], int n, int *i1, int *i2) {
        if (n == 0) {
                *i1 = *i2 = -1;
                return;
        }
        get2(&a[1], n - 1, i1, i2);

        if (*i1 != -1)
                (*i1)++;
        if (*i2 != -1)
                (*i2)++;

        if (*i1 == -1) {
                *i1 = 0;
        } else if (*i2 == -1 || a[*i2] < a[0]) {
                *i2 = 0;
                if (a[*i1] < a[0]) {
                        *i2 = *i1;
                        *i1 = 0;
                }
        }
}

这里我们只是递归,直到我们没有剩下的数组来查看,然后初始化索引,在回来的路上我们找出我们感兴趣的索引。在返回所有递归的路上我们更新索引i1和i2如果它们被初始化以正确指向我们在此迭代中的数组,那么如果i1尚未初始化,那么我们知道n == 0并且必须是最大的元素。在之后的迭代中,我们知道数组中唯​​一有趣的元素是元素0,如果它不大于[* i2],那么它也不会大于[* i1]。如果是(或者我们只是初始化i2),我们比较看看是否需要交换i1和i2。

“但是等等!”,你可能会大叫。 “你没有进行尾递归,如果数组很大,你就会用完堆栈。”很高兴你提到它,事实上,C没有说出编译器必须对尾递归做优化的事情所以我想说任何解决方案都必须解决这个问题。这是一个解决方案,它遵循相同的原则(在我们初始化索引之前用完数组),在O(log n)堆栈空间中解决了这个问题:

void
get2(int a[], int n, int *i1, int *i2) {
        /*
         * Reset the indices at the end of the recursion.
         */
        switch (n) {
        case 0:
                *i1 = -1;
                *i2 = -1;
                return;
        case 1:
                *i1 = 0;
                *i2 = -1;
                return;
        }
        /*
         * Number of elements in the lower and upper halves of the array.
         * Notice the '+ (n & 1)' which is there to add one element to the
         * upper half of the array if n is odd.
         *
         * The asserts document the invariants.
         */

        int lower_half = n / 2, upper_half = (n / 2) + (n & 1);
        int li1, li2, ui1, ui2;

        assert(lower_half >= 1);
        assert(upper_half >= 1);
        assert(lower_half + upper_half == n);

        get2(&a[0], n - upper_half, &li1, &li2);
        get2(&a[lower_half], n - lower_half, &ui1, &ui2);
        ui1 += lower_half;
        if (ui2 != -1)
                ui2 += lower_half;

        assert(li1 != -1);
        assert(ui1 != -1);
        assert(li2 == -1 || a[li2] < a[li1]);
        assert(ui2 == -1 || a[ui2] < a[ui1]);
        if (a[li1] < a[ui1]) {
                *i1 = ui1;
                if (ui2 == -1 || a[li1] > a[ui2])
                        *i2 = li1;
                else
                        *i2 = ui2;
        } else if (a[li1] > a[ui1]) {
                *i1 = li1;
                if (li2 == -1 || a[ui1] > a[li2])
                        *i2 = ui1;
                else
                        *i2 = li2;
        } else {
                *i1 = li1;
                if (li2 == -1 || a[ui2] > a[li2])
                        *i2 = ui2;
                else
                        *i2 = li2;
        }
}

我不打算解释它,评论和断言应足以显示正在发生的事情。我很确定最后ifs的大块可以写得更有效率,但我没有打扰。

它可能不完全正确,它在第一次尝试时使用了从前面的答案中获得的一些测试用例,我没有做更多的测试。可能会有一些我没有考虑过的边缘情况,但总体思路有效。

答案 5 :(得分:0)

这是我得到的一个很好的答案。接下来的几招只是为了让足够的角色用这张图片发布我的答案。这需要30个字符,所以我会写任何东西。

它的辉煌就像它一样简单。不是吗))? *这个代码需要一点修正,还有一个条件,如果其中一个指针为NULL,则不循环。 Here is a nice answer I got

答案 6 :(得分:0)

这是我对它的看法。这个实现非常简洁。

void get2(int a[], int n, int *i1, int *i2)
{
    if (n == 0) return;

    if (*i1 == -1 || a[n-1] > a[*i1]) {
        *i2 = *i1;
        *i1 = n-1;
    } else if (*i2 == -1 || a[n-1] > a[*i2]) {
        *i2 = n-1;
    }
    get2(a, n-1, i1, i2);
}

测试:

#define NUM 10

int main()
{
    int a[NUM];
    int i, i1, i2;

    srand(time(NULL));
    for (i=0;i<NUM;i++) {
        a[i] = rand() & 0xFFFF;
        fprintf(stderr,"%d ",a[i]);
    }
    fprintf(stderr,"\n");

    i1 = i2 = -1;
    get2(a,NUM,&i1,&i2);

    fprintf(stderr,"i1=%d, i2=%d\n", i1, i2);
    fprintf(stderr,"a[i1]=%d, a[i2]=%d\n", a[i1], a[i2]);
}