Exercício de Recursão: Ache o menor valor no Array?

Estou fazendo um exercício sobre recursividade em Java. e o objetivo é:

Construir um método recursivo que receba um array como parâmetro e retorne o menor valor encontrado nesse array.?

Fiz o código, porém da

Erro:

StackOverflowException

Código:

public static int menor(int[] a) {
    if (a == null) {
        return -1;
    }
    return menor(a, 1, a[0]);

}

private static int menor(int[] a, int i, int m) {
    if (i >= a.length) {
        return m;
    }
    if (a[i] < m) {
        m = a[i];
        menor(a, i++, m);
    }

    menor(a, i++, m);
    return m;

}

Podem me ajudar?

Existe várias formas, uma delas logo abaixo:

/* package whatever; // don't place package name! */
 
import java.util.*;
import java.lang.*;
import java.io.*;
 
/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
	public static int minor(int[] a, int m)
	{
		if (a.length > 0)
		{
			m = m > a[a.length - 1] ? a[a.length - 1] : m;
			return minor(java.util.Arrays.copyOf(a, a.length - 1), m);
		}
		return m;
	}
	public static void main (String[] args) throws java.lang.Exception
	{
		int[] a = new int[] { 6,5,-5,3,0,1};
		System.out.println(minor(a, Integer.MAX_VALUE));
	}
}

Exemplo rodando IDEONE

1 curtida

Uma solução um pouquinho diferente, que recebe apenas o array como parâmetro:

public static int menor(int[] array) {
    if (array == null || array.length == 0) {
        throw new IllegalArgumentException("Array vazio ou nulo.");
    } else if (array.length == 1) {
        return array[0];
    } else {
        int first = array[0];
        int[] rest = new int[array.length - 1];
        System.arraycopy(array, 1, rest, 0, array.length - 1);
        return Math.min(first, menor(rest));
    }
}
1 curtida

Me parece que a lógica da tua recursão não está muito clara. Basicamente quando for trabalhar com recursão em arrays deve ter em mente que precisa “reduzir” o array até ele chegar ao tamanho zero, e tudo indica que você não está reduzindo teu array, logo, o motivo do StackOverFlow.

O mais comum quando aplicando recursão, em primeira instância você precisa passar um “head” (que pode ser o primeiro elemento do teu array), e o “tail” (restante do array).

A solução do @Dragoon parece ser mais enxuta, mas pra deixar um pouco mais claro e didático, uma função que retorna o tail do array poderia ser algo do tipo:

public static int[] tailOf(int[] arr) {           
    if (arr.length <= 1) {                        
        return new int[] {};                      
    }                                             
    return Arrays.copyOfRange(arr, 1, arr.length);
}                                                 

Dentro do primeiro método, precisa chamar o segundo passando o head (primeiro elemento) e o restante:

return minor(arr[0], tailOf(arr));

O segundo método só precisa receber um inteiro (que representa o menor valor) e um array (tail), e neste caso, pra evitar o overflow, a primeira instrução tem que ser algo do tipo:

public static int minor(int min, int[] tail) {
    // isso significa que quando não houver mais elementos no array, 
    // já logo retorna o menor valor encontrado
    if (tail.length == 0) {
        return min;        
    }
    ...   

Pra finalizar, a lógica pra retornar o menor consiste em comparar o primeiro elemento do tail que foi passado com o menor encontrado. Caso o primeiro elemento seja menor que o menor encontrado, chama a função recursivamente passando o primeiro elemento (que agora é o menor) e o tail do array. Caso contrário, chama a função recursivamente passando o atual menor encontrado e o restante do array.

if (tail[0] < min) {                    
    return minor(tail[0], tailOf(tail));
} else {                                
    return minor(min, tailOf(tail));    

}

Esse é um approach não tão comum em linguagens imperativas, mais entre linguagens funcionais, onde é comum ter suporte a tail call optimization. Mas acho que pra fins didáticos, ajuda bem a entender a lógica da recursão.

2 curtidas
private static int menor(int[] a, int i, int m) {
 if (i >= a.length) {
  return m;
 }
 if (a[i] < m) {
  m = a[i];
  // menor(a, i++, m);
 }

 return menor(a, i + 1, m);
 // return m;
}

O StackOverflowException acontece porque o i não está mudando, repetindo o método infinitamente. O i++ adiciona um depois de executar o método, é necessário adicionar antes, então use i + 1 ou ++i.

int a = 0;
System.out.println(a++); // imprime 0
System.out.println(a); // imprime 1
int b = 0;
System.out.println(++b); // imprime 1
System.out.println(b); // imprime 1
int c = 0;
System.out.println(c + 1); // imprime 1
System.out.println(c); // imprime 0
2 curtidas

Eu torço por mais gente igual a você entrar aqui … Parabéns …

1 curtida

muito massa, parabéns…

1 curtida

Enquanto as soluçoes alternativas propostas funcionam, eu prefiro a abordagem do @diego12, que entendeu o código proposto e apontou os problemas dele, isso deve ajudar mais na aprendizagem.

Também me parece mais eficiente continuar usando o mesmo array ao invés de ficar criando cópias do original (ah menos claro que o método por baixo dos panos apenas faça um alias do original)

É realmente feita uma cópia rasa (no caso, como são primitivos, é copiado tudo). Copiar o array é bem mais ineficiente do que reutilizar. O algoritmo fica O(n^2), ao invés de O(n) que é quando não há cópia. Mas acho que a solução fica mais simples de entender com apenas um parâmetro. Enfim, é só mais uma possível solução.

Concordo que talvez entre elas existe uma forma mais adequada e mais rápida, nós podíamos apontar a melhor, e/ou talvez até juntar e fazer uma que não gere uma novo array. Eu gostei da solução do @Ivbarbosa e acabei vendo a do @diego12, mas, realmente ainda prefiro a que não tenha parâmetro, mas, ai que tá qual seria mais eficiente, alguém ta afim de medir?

Depois eu vou tentar propor uma melhor.

1 curtida

Na verdade a parte de ser mais eficiente, era o menos importante para mim.

O que quis dizer é que para quem abriu o tópico, provavelmente ajuda mais ver onde estava o próprio erro, do que ver uma soluçao totalmente diferente funcionando e nao entender o que aconteceu.

1 curtida

Ok… entendi. vou propor em cima do que ele o fez!

Tem razão, a solução do diego foi pragmática mostrando o erro e de quebra propondo uma solução que não sai da complexidade linear.

1 curtida