Java - сделать бинарный поиск рекурсивным - PullRequest
1 голос
/ 08 декабря 2010

Итак, в течение нескольких недель я пытался сделать следующий код в рекурсивном методе ...

public static int binarySearch(Comparable[] objArray,Comparable item)
{       
    int lower=0;
    int upper=objArray.length -1;
    int i=-1;    // if -1 is returned the search failed;
    int compareResult;
    boolean found= false;
    while ((lower<=upper)&& (!found))
       {
        i=(lower+upper)/2;
        compareResult=item.compareTo( objArray[i]);
        if (compareResult<0)
           {
            upper=i-1;
           }
         else
            if (compareResult>0)
               {
                lower=i+1;
               }
         else
            {
                found=true;    //item found in spot i
            }

       }// end of while
       if (found==false) return -1; else return i;

}

Я знаю, что должен переопределить int и использовать пару if,но без цикла while я не понимаю, как добраться до финального рекурсивного кода, который мне нужен.Какие-либо предложения?Спасибо

-D

Ответы [ 5 ]

5 голосов
/ 08 декабря 2010

Основная проблема, которая мешает вам видеть рекурсию, состоит в том, что аргументы вашего метода - это просто массив и компаратор.Если бы вы передали начальный и конечный индексы (что обычно делали на более старом языке), вы бы увидели возможность рекурсии.

Я обычно не люблю выдавать псевдокод для таких вопросов, предпочитаявместо этого, чтобы дать вам подсказки относительно структуры и характера алгоритма.Однако вы не сможете соединить 2 + 2, когда ваши методы определены таким образом.Поэтому я собираюсь сделать здесь исключение:

Вместо этого (что у вас есть):

public static int binarySearch(Comparable[] objArray,Comparable item)
{       
    int lower=0;
    int upper=objArray.length -1;

У вас есть это (это псевдокод, похожий на Java, очень упрощенный)и без проверки ошибок вам нужно проработать фактические детали синтаксиса):

public static int binarySearch(Comparable[] objArray,Comparable item)
{       
    return bSearch(objArray, item, 0, objArray.length -1);
    ....

private static int bSearch(Comparable[] objArray,Comparable item, int lower, int upper)
{
   if( lower > upper /* not found */){ return -1; };

   int pivot = ((int)((upper - lower) / 2)) + lower;

   int cmp = objArray[pivot].compareTo(item);

   if( cmp == 0 ){ return pivot; }
   else if (cmp < 0 ){ return bSearch(objArray,item, lower, pivot - 1); }
   else{ return bSearch(objArray,item, pivot+1, upper); }
}

Опять же, вам нужно проработать фактический синтаксис и проверку параметров, но кроме этого, это типичный псевдокод длярекурсивный бинарный поиск.Вы должны были это увидеть и поработать над этим до тошноты не позднее, чем на курсе второкурсников.

Рекурсия происходит следующим образом:

  1. Если ваш массив отсортирован,и

  2. Если вы начнете сравнение в середине, то есть стержень,

  3. И сравнение не удастся,

  4. Тогда

  5. Вы знаете, что вам нужно искать только одну половину (либо верхнюю половину, если цель> разворот, либо нижнюю половину, если цель <разворот). </p>

Таким образом, вы берете половину, по которой собираетесь искать, и снова выполняете те же самые шаги на ней .Что может быть лучше, чем вызывать точно такой же алгоритм, точно такую ​​же функцию только для этой части, для этого подмножества вашего ввода?

Это рекурсия.


Редактировать: Разработкав предыдущем примере, когда вы хотите / должны реализовать что-то рекурсивное в Java, лучше иметь утилитарный метод , который принимает структуру данных в целом (скажем, только массив и объект сравнения, как выв вашем примере).

Затем пусть вызовет внутренний метод, который выполняет рекурсию (и который должен иметь дело с передачей параметров). Причина в том, что ваши вызывающие программыне нужно передавать нижний и верхний индексы (так как они могут быть вычислены из объекта массива.)

В других языках нижнего уровня, таких как C, Pascal или Ada, вы иногда не можете вычислить такие аргументы и должныобъявите ваши рекурсивные функции с параметрами, явно переданными вашими вызывающими.

Поэтому, почему я разделил рекурсию вотдельная функция bSearch в приведенном выше псевдокоде.

3 голосов
/ 08 декабря 2010

Хорошо, подумайте, как вы могли бы выполнить рекурсию ... после того, как вы провели одно сравнение, вы сузили область поиска, верно?Таким образом, вам нужно указать это в аргументах рекурсивного метода.

Попробуйте передать в качестве аргументов текущие известные нижнюю и верхнюю границы вместо циклического оператора while и измените тест условия while на начальный тест напосмотрим, закончили ли вы.

2 голосов
/ 09 декабря 2010

Если вы уже поняли рекурсию, пропустите это.

Рекурсивность работает так:

  • У вас есть одно (или несколько) базовых условий. Они будут использоваться, чтобы знать, когда остановиться.

  • У вас есть входные параметры. Один (или многие) из этого входного параметра будет использоваться для условия остановки.

  • У вас есть какая-то операция, и эта операция будет «решена» путем вызова той же функции с новыми параметрами (потому что использование одной и той же функции будет повторяться вечно)

Итак, имея в виду эту информацию, вы можете сделать что-то вроде этого псевдо:

boolean contains( element ,  list)  { 
   //Q. what is the base condition? 
   //A. to know if there are elements in the list right?

   if list.isEmpty()?  return false; // not found in an empty list

   // Q. What other base condition we may try? 
   // A. If the list contains only one element, we may see 
   // if it is our element.
   if list.size() == 1 
        return list.firstElement() == element // true if they are false otherwise

   // Finally we may try as base condition to test 
   // if our element is in the middle of the list
   if list.middleElement() == element ?  return true // we found it


   // Ok, none of our base contidion worked.
   // Now we have to perform some operation 
   // Here's the recursion magic:
   // Solve your problem by invoking this same function ( re-course it ) 
   // with different arguments. In this sample, we 
   // split the original list in two.

    return  contains( element , list.firstHalf() )  
            or contains( element , list.secondHalf() )

   // you may read it as: 
   // "this list contains element if it is in the first half or it is in the second half 
}

Разделив список на две части, поиск сузится до точки, в которой либо список содержит один элемент, либо он пуст. Когда это произойдет, наши базовые условия остановят рекурсию.

Как видите, намного проще, если вы сначала определите базовые условия, а затем определите, как изменить параметры.

Важно сначала попробовать это в псевдокоде или карандашом и бумагой, прежде чем пытаться делать это в коде. Как вы видите в моем примере, я вызывал вспомогательные методы (firstElement(), middleElement(), firstHalf(), secondHalf()), потому что они не имеют отношения к проблеме. Сначала нужно понять, как решить проблему, а затем перейти к конкретным языковым проблемам.

Надеюсь, это поможет вам лучше понять рекурсию.

Если вы еще не попробовали эту ссылку

1 голос
/ 09 декабря 2010

Вам необходимо узнать об особой технике рекурсии под названием «Разделяй и властвуй» .

По сути, если вы можете разделить задачу на две меньшие идентичные проблемы, то вы можете легко применить рекурсию, если в конечном итоге ваши подразделения сведут проблему к чему-то, что легко решается.

В этом случае вы ищете массив для поиска элемента. Тогда решение «разделяй и властвуй» будет выглядеть так:

  1. Если у меня есть отсортированный массив, я могу сравнить искомый элемент со средним элементом в этом массиве. Если он не совпадает, я выбираю меньший массив в направлении (выше или ниже), который соответствует тому, где я хочу посмотреть, и я повторю процесс.
  2. Если у меня есть массив размером один, то мне нужно только сравнить элемент с тем, который я ищу, если его там нет, то элемент не существует в этом массиве.
  3. Если у меня есть массив нулевого размера, то элемент, который я ищу, не существует в этом массиве.

Условия выхода всегда должны проверяться в первую очередь. Это предотвращает рекурсию там, где она не подходит, и гарантирует, что при каждой рекурсивной оценке вы будете уверены, что остановились первыми, если это было уместно.

boolean exists(Comparable[] X, Comparable item) {

   if (X.length == 0) return false;
   if (X.length == 1) return (X[0].compareTo(item) == 0);
   int pivot = X.length / 2; // integer math assures an integer result
   if (X[pivot].compareTo(item) == 0) return true;
   if (X[pivot].compareTo(item) < 0) {
     Compareable[] right = new Comparable[X.length-pivot-1];
     System.arrayCopy(X, pivot+1, right, 0, right.length);
     return exists(right, item);
   } else {
     Compareable[] left = new Comparable[X.length-pivot-1];
     System.arrayCopy(X, 0, left, 0, left.length);
     return exists(left, item);
   }

}

Это псевдокод, не оптимизированный, но он должен заставить вас думать так, как вам нужно. Испытайте технику на нескольких самостоятельно созданных проблемах; потому что это настолько важная концепция, что вы не хотите основывать свое понимание только на задаче домашнего задания (или двух). Вы должны хорошо понимать эту идею; это снова будет видно и в школе, и на работе.

1 голос
/ 08 декабря 2010

Представьте себе:

Вы получаете меньший массив для поиска в зависимости от некоторых условий.Но целью вашей функции также является поиск элемента в массиве.Таким образом, вместо того, чтобы работать над этим самостоятельно, позвольте новому вызову той же самой функции обработать это.Вы просто беспокоитесь о возвращаемом значении.Если это -1, вы знаете, что элемент был найден, и вы также возвращаете -1.Если что-то еще возвращается, верните и это, так как это индекс найденного элемента.

...