Как мне закончить рекурсивный бинарный поиск в фортране? - PullRequest
2 голосов
/ 09 ноября 2019

Я пытаюсь найти наименьший индекс, содержащий значение i в отсортированном массиве. Если это значение i отсутствует, я хочу вернуть -1. Я использую рекурсивную подпрограмму двоичного поиска. Проблема в том, что я не могу действительно остановить эту рекурсию, и я получаю много ответов (один правильный, а остальные неправильные). И иногда я получаю сообщение об ошибке «Ошибка сегментации: 11» и не получаю никаких результатов.

Я пытался удалить этот вызов random_number, поскольку у меня уже есть отсортированный массив в моей основной программе, но он не работал.

 program main
  implicit none
  integer, allocatable      :: A(:)
  real                      :: MAX_VALUE
  integer                   :: i,j,n,s, low, high
  real                      :: x

  N= 10                !size of table
  MAX_VALUE = 10

  allocate(A(n))

  s = 5          ! searched value
  low = 1        ! lower limit
  high = n       ! highest limit


  !generate random table of numbers (from 0 to 1000)
  call Random_Seed
  do i=1, N
     call Random_Number(x)  !returns random x >= 0 and <1
   A(i)= anint(MAX_VALUE*x)
  end do

 call bubble(n,a)
 print *,' '
 write(*,10) (a(i),i=1,N)
 10 format(10i6)

 call bsearch(A,n,s,low,high)

 deallocate(A)

end program main

Подпрограмма сортировки:

subroutine sort(p,q)

    implicit none
    integer(kind=4), intent(inout)      :: p, q
    integer(kind=4)                  :: temp

    if (p>q) then
       temp = p
       p = q
       q = temp
    end if
    return
end subroutine sort

Подпрограмма пузыря:

subroutine bubble(n,arr)

 implicit none
 integer(kind=4), intent(inout)        :: n
 integer(kind=4), intent(inout)        :: arr(n)
 integer(kind=4)                       :: sorted(n)
 integer                               :: i,j

do i=1, n
   do j=n, i+1, -1
      call sort(arr(j-1), arr(j))
   end do
end do
return

end subroutine bubble

recursive subroutine bsearch(b,n,i,low,high)

   implicit none
   integer(kind=4)       ::    b(n)
   integer(kind=4)       ::    low, high
   integer(kind=4)       ::    i,j,x,idx,n
   real(kind=4)          ::    r

   idx = -1

   call random_Number(r)
   x = low + anint((high - low)*r)

   if (b(x).lt.i) then
   low = x + 1
   call bsearch(b,n,i,low,high)

   else if (b(x).gt.i) then
      high = x - 1
      call bsearch(b,n,i,low,high)
   else
   do j = low, high
    if (b(j).eq.i) then
       idx = j
       exit
    end if
    end do
  end if

 ! Stop if high = low
    if (low.eq.high) then
    return
   end if

   print*, i, 'found at index ', idx
   return

   end subroutine bsearch

Цель состоит в том, чтобы получить те же результаты, что и мой линейный поиск. Но я получаю любой из этих ответов.

Сортированная таблица:

     1     1     2     4     5     5     6     7     8    10

       5 found at index            5
       5 found at index           -1
       5 found at index           -1

или, если значение не найдено

   2     2     3     4     4     6     6     7     8     8  

   Segmentation fault: 11

1 Ответ

0 голосов
/ 14 ноября 2019

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

1) значение присутствует и найдено, нежелательный вывод
Сначала рассмотрим первыйпример, где массив b содержит значение i=5, которое вы ищете (значение и индекс указаны с || в первых двух строках блока кода ниже). Используя нотацию Rn для обозначения n '-го уровня рекурсии, L и H для нижней и верхней границ и x для текущей оценки индекса, данный прогон вашего кода можетвыглядят примерно так:

b(x): 1     1     2     4    |5|    5     6     7     8    10
x:    1     2     3     4    |5|    6     7     8     9    10
R0:   L                                         x          H
R1:   Lx                                  H
R2:         L                       x     H

    5 found at index            5
    5 found at index           -1
    5 found at index           -1

В R0 и R1 тесты b(x).lt.i и b(x).gt.i в bsearch работают так, как задумано, чтобы уменьшить интервал поиска. В R2 выполняется петля do в ветви else, idx присваивается правильное значение, и оно печатается - как предполагалось. Однако , теперь встречается оператор return, который возвращает управление вызывающему программному модулю - в этом случае это первый R1 (!), Где выполнение возобновится после if-else if-else блокировать, выводя таким образом сообщение на экран с начальным значением idx=-1. То же самое происходит по возвращении из R0 в основную программу. Это объясняет (нежелательный) вывод, который вы видите.

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

b(x): 2     2     3     4     4     6     6     7     8     8
x:    1     2     3     4     5     6     7     8     9     10
R0:   L                 x                                   H
R1:                           L                       x     H
R2:                           L     x           H
R3:                          LxH
R4:                           H     xL
.
.
.

Segmentation fault: 11

В R0-R2 интервал поиска снова сокращается, как и предполагалось. Однако в R3 логика не работает. Поскольку значение поиска i отсутствует в массиве b, один из тестов .lt. или .gt. всегда будет иметь значение .true., что означает, что тест для low .eq. high для завершения поиска никогда не будет достигнут,С этого момента логика больше не действует (например, high может быть меньше low), и код будет продолжать углублять уровень рекурсии, пока стек вызовов не станет слишком большим и не произойдет ошибка сегментации.

Они объяснили основные логические недостатки в коде. Возможной неэффективностью является использование do -loop для поиска самого низкого индекса, содержащего искомое значение. Рассмотрим случай, когда значение, которое вы ищете, например, i=8, и что оно появляется в последней позиции в вашем массиве, как показано ниже. Предположим далее, что по случайности первое предположение для его позиции - x = high. Это подразумевает, что ваш код будет немедленно переходить к do -циклу, где по сути выполняется линейный поиск почти по всему массиву, чтобы найти окончательный результат idx=9. Несмотря на правильность, предполагаемый двоичный поиск скорее становится линейным поиском, что может привести к снижению производительности.

b(x): 2     2     3     4     4     6     6     7    |8|    8
x:    1     2     3     4     5     6     7     8    |9|    10
R0:   L                                                     xH

    8 found at index            9

Устранение проблем

По крайней мере, вы должны переместить low .eq. highвыполнить тест до начала процедуры bsearch, чтобы рекурсия прекратилась до того, как могут быть определены недопустимые границы (затем вам потребуется дополнительный тест, чтобы увидеть, было ли найдено значение поиска или нет). Кроме того, уведомляйте об успешном поиске сразу после его появления, т. Е. После теста на равенство в вашем do -цикле, или только что упомянутого дополнительного теста. Это по-прежнему не учитывает неэффективность возможного линейного поиска.

Учитывая все это, вам, вероятно, лучше ознакомиться с алгоритмами нахождения "крайнего левого" индекса (например, на Wikipedia или посмотрите на проверенную и протестированную реализацию - оба примера здесь используют итерацию вместо рекурсии, возможно, другое улучшение, но применяются те же принципы) и адаптируют ее к Fortran, который может выглядетькак это (только показывая новый код, ... ссылается на существующий код в ваших примерах):

module mod_search
  implicit none
  contains
    ! Function that uses recursive binary search to look for `key` in an
    ! ordered `array`. Returns the array index of the leftmost occurrence
    ! of `key` if present in `array`, and -1 otherwise
    function search_ordered (array, key) result (idx)
      integer, intent(in) :: array(:)
      integer, intent(in) :: key
      integer :: idx

      ! find left most array index that could possibly hold `key`
      idx = binary_search_left(1, size(array))

      ! if `key` is not found, return -1
      if (array(idx) /= key) then
        idx = -1
      end if

      contains

        ! function for recursive reduction of search interval
        recursive function binary_search_left(low, high) result(idx)
          integer, intent(in) :: low, high
          integer             :: idx
          real                :: r

          if (high <= low ) then
            ! found lowest possible index where target could be
            idx = low
          else
            ! new guess
            call random_number(r)
            idx = low + floor((high - low)*r)
            ! alternative: idx = low + (high - low) / 2

            if (array(idx) < key) then
              ! continue looking to the right of current guess
              idx = binary_search_left(idx + 1, high)
            else
              ! continue looking to the left of current guess (inclusive)
              idx = binary_search_left(low, idx)
            end if
          end if
        end function binary_search_left

    end function search_ordered

    ! Move your routines into a module
    subroutine sort(p,q)
       ...
    end subroutine sort

    subroutine bubble(n,arr)
      ...
    end subroutine bubble
end module mod_search

! your main program
program main
  use mod_search, only : search_ordered, sort, bubble    ! <---- use routines from module like so
  implicit none
  ...

  ! Replace your call to bsearch() with the following:
  ! call bsearch(A,n,s,low,high)
  i = search_ordered(A, s)
  if (i /= -1) then
    print *, s, 'found at index ', i
  else
    print *, s, 'not found!'
  end if
  ...
end program main

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

! i = search_ordered(a, s)  ! <---- comment out this line
j = minloc(abs(a-s), dim=1) ! <---- replace with these two
i = merge(j, -1, a(j) == s)

, где j, возвращаемое из minloc, будет самым низким индексом в массиве a, где s может быть найдено, и merge используется для возврата j, когда a(j) == s и -1 в противном случае.

...