【问题标题】:Why does this binary search implementation cause stack overflow in Ruby but not Java?为什么这种二进制搜索实现会导致 Ruby 中的堆栈溢出而不是 Java?
【发布时间】:2020-11-20 09:09:04
【问题描述】:

红宝石

def binary_search(arr, l, r, x) 
  if r >= 1 then
    mid = l + (r - 1) / 2

    if arr[mid] == x then
      return mid
    end

    if arr[mid] > x then
      return binary_search(arr, l, mid - 1, x)
    end
    return binary_search(arr, mid + 1, r, x)
  end

  return -1
end

Java

int binarySearch(int arr[], int l, int r, int x) 
{ 
    if (r >= l) { 
        int mid = l + (r - l) / 2; 

        // If the element is present at the 
        // middle itself 
        if (arr[mid] == x) 
            return mid; 

        // If element is smaller than mid, then 
        // it can only be present in left subarray 
        if (arr[mid] > x) 
            return binarySearch(arr, l, mid - 1, x); 

        // Else the element can only be present 
        // in right subarray 
        return binarySearch(arr, mid + 1, r, x); 
    } 

    // We reach here when element is not present 
    // in array 
    return -1; 
} 

当 x(目标元素)位于排序数组的右半部分时,发生堆栈溢出,我在 Ruby 中收到此错误

SystemStackError (stack level too deep)

为什么这发生在 ruby​​ 中而不是 java 中?我正在 irb 中运行程序。 java 实现直接来自这里https://www.geeksforgeeks.org/binary-search/

【问题讨论】:

  • 你的问题我不清楚。这两个程序有很大的不同,Ruby 程序有一个 bug 会导致无限递归,所以很明显,它遇到了堆栈溢出。
  • 为什么是if r >= 1?你的意思是if r >= l
  • 你不使用Array#bsearch是因为这只是一个练习吗?

标签: java ruby recursion stack-overflow binary-search


【解决方案1】:

首先,让我们重构您的代码,使其更加清晰。注意:此代码中有 0 个行为变化,主要是重新格式化并稍作重构。

def binary_search(arr, l, r, x) 
  return -1 unless r >= 1

  mid = l + (r - 1) / 2

  case arr[mid] <=> x
  when  0 then mid
  when -1 then binary_search(arr, mid + 1, r, x)
  when  1 then binary_search(arr, l, mid - 1, x)
  end
end

您的二分搜索存在两个主要问题。

首先是“未找到元素”的终止条件。二分搜索的工作方式是将左侧“栅栏”移动到右侧或将右侧“​​栅栏”移动到左侧,具体取决于所需元素在数组中的位置。这样一来,搜索区域就会越来越小。

现在,当两个“栅栏”相遇(甚至相互擦肩而过)时,就没有更多的“搜索区域”了,这意味着没有找到该元素。但是,您不是在检查两个“栅栏”是否相遇 (l == r) 甚至是否相互超越 (l &gt;= r),而是在检查 r 是否与 原始数组的左边界 (r == 1)。

这意味着你将有很多很多无用的递归,直到你最终在找不到元素时放弃。

其实也没那么简单,因为当lr互相经过时,你的中点计算也是错误的,因为现在突然r小于l,或者也就是说,右栅栏在左栅栏的左侧!

嗯......除了你的中点计算无论如何都被打破了。比如l = 10r = 12,那么两者的中点显然是mid = 11,但是根据你的公式,是:

10 + (12 - 1) / 2
10 + (  11  ) / 2
10 + 5
15

哎呀!左右之间的中间实际上是way 到右边的右边!这意味着,根据您的搜索值在数组中的位置,您实际上是在使搜索区域更大而不是更小:您正在将r 移回右侧,这样您就可以再次搜索您已经搜索过的数组的一部分!同样,这意味着您的 Ruby 版本需要比 Java 版本更多的递归。

lr 之间当前距离的公式是r - l。现在我们需要该距离的一半,即(r - l) / 2。如果我们想找到lr之间的中间点,我们需要从l走到r的一半距离,所以我们需要把上面的距离加上ll + (r - l) / 2

固定代码如下所示:

def binary_search(arr, l, r, x) 
  return -1 if r < l

  mid = l + (r - l) / 2

  case arr[mid] <=> x
  when  0 then mid
  when -1 then binary_search(arr, mid + 1, r, x)
  when  1 then binary_search(arr, l, mid - 1, x)
  end
end

【讨论】:

    猜你喜欢
    • 2011-10-22
    • 2014-12-20
    • 1970-01-01
    • 2021-02-19
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    相关资源
    最近更新 更多