The code is a simple binary search program.
I tried tracing the program but it only made me more confused. I can't figure out why the nested if has data, min, midpoint - 1, & target
vs. the bottom else if statement has data, midpoint + 1, max, target.
public static boolean binarySearch(int[] data, int min, int max, int target){
boolean found = false;
int midpoint = (min + max) / 2; // determine the midpoint
if (data[midpoint] == target)
found = true;
else if (data[midpoint] > target)
{
if (min <= midpoint - 1)
found = binarySearch(data, min, midpoint - 1, target);
}
else if (midpoint + 1 <= max)
found = binarySearch(data, midpoint + 1, max, target);
return found;
}
The array data is already sorted from smallest to largest
Therefore it finds if the value at the midpoint is greater than the target, then the target would appear in the values before midpoint. So we recursively call the method only on the left of the midpoint i.e. all the values from the min till the value before the midpoint.
Similarly if the midpoint is less than the target, the target could be found after the midpoint, therefore we recursively call the method only on the right of the midpoint i.e. all the values from the value after the midpoint to the end.
Each time time we don't include the midpoint as that is checked in the line
if (data[midpoint] == target)
e.g
Array 3 6 8 10 13 14 20. target = 14
the midpoint would be = 10 9index 4). Checking target and midpoint, we'd see that the target is greater than the midpoint and falls on the right.
So we now check for target in 13 14 20 --- from midpoint+1 (index 5) till the end.
The midpoint would be 14. And the above if statement would return true.
Binary search searches the left half (min... mid-1) and the right half (mid+1...max) recursively.
You are checking mid against target, so that is why it isn't included in that range.
You really should have a base-case for if (min >= max) return false; to prevent going out of bounds of the array.
Here is a cleaner implementation as I find it easier to understand
public static boolean binSearch(int[] data, int target) {
return _binSearch(data, target, 0, data.length);
}
private static boolean _binSearch(int[] data, int target, int low, int high) {
int mid = (low + high) / 2;
if (low >= high) return false;
if (data[mid] == target) return true;
boolean foundLeft = _binSearch(data, target, low, mid);
boolean foundRight = !foundLeft && _binSearch(data, target, mid+1, high);
return foundLeft || foundRight;
}
You divide your data into a half smaller than midpoint, with a range (min, mid-1) and bigger than midpoint, with a range (mid+1, max).
if you have an input of {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}, and min = 0; max= 10, then
int midpoint = (0 + 10) / 2; // which is 5
Iff data[midpoint] is not what you were looking for, need need to look for everything left or right (but not midpoint itself, thats why there is a -1 and a +1 in there..)
So the range for the left half is (0, 4) and the range for the right half is (6, 10).
If you are looking for, lets say 12, we'd go left, because data[midpoint] == 13 && 13 > 12:
int midpoint = (0 + 4) / 2; // which is 2
since data[2] < 10, we'd go right, with min = 3; max = 4; and so on..
It seems the code incorrectly compares the current midpoint with the min and max index. Instead
if (min <= midpoint - 1)
:
else if (midpoint + 1 <= max)
it should use
if (min < midpoint - 1)
:
else if (midpoint + 1 < max)
Try the below attempt to correct it:
public static boolean binarySearch(int[] data, int min, int max, int target){
if (max > min) {
int midpoint = (min + max) / 2; // determine the midpoint
if (data[midpoint] == target) {
return true;
}
if (data[midpoint] > target) { // use lower half
return binarySearch(data, min, midpoint-1, target);
}
else { // use upper half
return binarySearch(data, midpoint+1, max, target);
}
}
return false;
}
See DEMO
Related
Here is my code, where a is the array to find the maximum of (each element is distinct). From my perspective, if a[mid] is less than a[mid-1] then the position of the maximum should be in the range of [lo, mid-1]. However, when I implement my idea, the program is endless. The solution is to use [lo, mid] as the next iteration.
My question is: Why shouldn't I use mid-1 instead of mid in Line 9?
First edit: Some people ask why didn't I just sort and choose the first element? Since the problem is to find a key in a bitonic array (where elements ascend first then descend). My solution is to find the maximum of the array, separate it into two parts and respectively use BinarySearch to find the key. If I sort the array I will destroy the original order.
Second edit: add more code in detail and the expected output.
public static int getMaxIndex(int[] a) {
int hi = a.length - 1;
int lo = 0;
while (lo < hi && a[lo] <= a[lo+1]) {
int mid = lo + (hi - lo) / 2;
if (a[mid] > a[mid-1]) {
lo = mid;
} else {
hi = mid - 1; // Should be mid, not mid-1, why?
}
}
return lo;
}
public static void main(String[] args) {
int[] a = {1, 2, 3, 5, 7, 9, 8, 6, 4}; // Bitonic array
System.out.println(getMaxIndex(a)); // Should be 5
}
bitonic array
The numbers are strictly increasing and after an index, the numbers are strictly decreasing
eg: [15, 20, 26, 5, 1]
If the array contains only unique elements, then maximum can be found using following conditions
iterate while left index < right index
compute middle index (towards left incase no single middle index)
if the value at middle is smaller than next element, then move towards right
else move left towards left
arr[mid] < arr[mid + 1] will never throw out of bounds exception as left < right invariant is maintained on the loop. hence mid(index) will always be lesser than right(index) and hence there is atleast one index after mid
int left = 0, right = arr.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] < arr[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return arr[left]; // or arr[right]
15 20 26 5 1
l=0, r=4 l m r
m=2
a[m] > a[m+1] l r
so, r=m
l=0, r=2 l m r
m=1
a[m] < a[r+1] l,r
m=l+1
Now exit while loop l == r and return value at l or r
Analysis of OP's code
A simple reproducible example is [1, 2] - this will cause lo to be stuck to same index and causes infinite loop.
Any dataset that reduces to lo + 1 == hi and arr[lo] < arr[hi] will set mid to lo (lo + (hi - lo) / 2 will be lo) and hence the assignment lo = mid causes an infinite loop
The problem you had was that you were not always changing mid. In your example, at one point lo is 4 and pointing to 7 in the array while hi is 5 and points to 9. In the situation where lo and hi are just 1 apart, mid is set to lo. Here you are comparing a[mid] > a[mid-1]. Problem 1, mid-1 is out of range and in rare cases gives an ArrayIndexOutOfBoundsException. Second, if the condition is true, you set lo to mid, which is the value it already had. You are getting nowhere. You are in an infinite loop.
I tried your method with
System.out.println(getMaxIndex(new int[] { 3 , 8 })); // Expecting 1
I got java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 2
The solution is in the answer by Horse.
Here, the word bitonic means, array is sorted in increasing order, and after some point (may be) it starting to decrease.
Likewise, you have to find the pivot point where array is starting to decrease (if it is exist), if you can able to find the point, that's it, this pivotal point is your answer.
so i am attaching some input/output sequence to make it more clear:
{1, 2, 3, 5, 7, 9, 8, 6, 4}; --> should return 5 (index at which element is maximum)
{15, 20, 26, 5, 1} --> should return 2 (index at which element is maximum)
{0, 1, 2, 3} --> should return 3 (index at which element is maximum)
now you have to modify your code like following:
public static int getMaxIndex(int[] arr) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if(mid == 0 || mid == arr.length-1) {
// if no such pivotal point exist in sequence/array
return mid;
}else if(arr[mid] > arr[mid+1] && arr[mid] > arr[mid-1]){
// it's the pivotal point, where element at left side smaller and element at right side too
return mid;
}
if (arr[mid] < arr[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return -1;
}
I want to find a target value 4 firstly appeared place in a sequence [1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6]. when I use java.util.Arrays.binaySearch, it returns index is 9, but I expected 7.
I look java.util.Arrays.binaySearch source code
and I found some comments:
If the array contains multiple elements with the specified value, there is no guarantee which one will be found.
So how to implement a lower_bound binary search algorithm in Java, which returns the target value firstly appeared place.
Note: The lower_bound concept comes from C++, but I don't understand C++ well.
I think the implementation below will do the job correctly:
int firstOccurrence(int[] sequence, int x) {
int min = 0;
int max = sequence.length - 1;
int result = -1;
while (min <= max)
{
// find the mid value and compare it with x
int mid = min + ((max - min) / 2);
// if x is found, update result and search towards left
if (x == sequence[mid]) {
result = mid;
max = mid - 1;
} else if (x < sequence[mid]) {
// discard right half
max = mid - 1;
} else {
// discard left half
min = mid + 1;
}
}
// return the leftmost index equal to x or -1 if not found
return result;
}
Edit:
Change the way to compute mid to avoid overflow with larger sums
// Previously, can overflow since we add two integer
int mid = (min + max) / 2;
// Now
int mid = min + ((max - min) / 2);
// Another way using the unsigned right shift operator
int mid = (low + high) >>> 1;
// The left operands value (low + high) is moved right
// by the number of bits specified (2 in this case) by the right operand and
// shifted values are filled up with zeros.
// The >>> treats the value as unsigned
Building on this answer to another binary search question:
How can I simplify this working Binary Search code in C?
This is a search that is equivalent to lower_bound from C++. It returns the number of elements smaller than the value you want to find. That would be
the index of the first occurrence, or where one would be inserted if there is no occurrence:
int numSmaller(int[] seq, int valueToFind)
{
int pos=0;
int limit=seq.length;
while(pos<limit)
{
int testpos = pos+((limit-pos)>>1);
if (seq[testpos]<valueToFind)
pos=testpos+1;
else
limit=testpos;
}
return pos;
}
Note that we only need to do one comparison per iteration.
The linked answer highlights several advantages of writing a binary search this way.
It think it will help you
public static boolean binarysearch(int[] data, int target, int low, int high){
if(low>high){
System.out.println("Target not found");
return false;}
else{
int mid=(low+high)/2;
if(target==data[mid])
return true;
else if(target<data[mid])
return binarysearch(data, target, low, high);
else
return binarysearch(data, target, low, high);
}
}
I'm trying to build a chess AI. My negamax function with alpha-beta pruning (ABP) runs much slower (about 8 times) than separate min and max functions also with ABP, though the moves returned are equal.
My board evaluation function always returns a value with respect to the red player, i.e. the higher the better for red. For Negamax only, this value is multiplied by -1 for the black player when returning at depth 0.
My Negamax function:
int alphaBeta(Board board, int depth, int alpha, int beta) {
if (depth <= 0 || board.isGameOver()) { // game over == checkmate/stalemate
int color = board.getCurrPlayer().getAlliance().isRed() ? 1 : -1;
return BoardEvaluator.evaluate(board, depth) * color;
}
int bestVal = Integer.MIN_VALUE + 1;
for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = board.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) { // allowed == legal && non-suicidal
int val = -alphaBeta(transition.getNextBoard(), depth - 1, -beta, -alpha);
if (val >= beta) {
return val; // fail-soft
}
if (val > bestVal) {
bestVal = val;
alpha = Math.max(alpha, val);
}
}
}
return bestVal;
}
The root call:
-alphaBeta(transition.getNextBoard(), searchDepth - 1,
Integer.MIN_VALUE + 1, Integer.MAX_VALUE); // +1 to avoid overflow when negating
My min and max functions:
int min(Board board, int depth, int alpha, int beta) {
if (depth <= 0 || board.isGameOver()) {
return BoardEvaluator.evaluate(board, depth);
}
int minValue = Integer.MAX_VALUE;
for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = board.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) {
minValue = Math.min(minValue, max(transition.getNextBoard(), depth - 1, alpha, beta));
beta = Math.min(beta, minValue);
if (alpha >= beta) break; // cutoff
}
}
return minValue;
}
int max(Board board, int depth, int alpha, int beta) {
if (depth <= 0 || board.isGameOver()) {
return BoardEvaluator.evaluate(board, depth);
}
int maxValue = Integer.MIN_VALUE;
for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = board.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) {
maxValue = Math.max(maxValue, min(transition.getNextBoard(), depth - 1, alpha, beta));
alpha = Math.max(alpha, maxValue);
if (alpha >= beta) break; // cutoff
}
}
return maxValue;
}
The root calls for red and black players respectively:
min(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
max(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
I'm guessing there's a bug with the cutoff in the Negamax function although I followed the pseudocode from here. Any help is appreciated, thanks!
EDIT: alphaBeta() is called about 6 times more than min() and max() combined, while the number of beta cutoffs is only about 2 times more.
Solved. I should have posted my full code for the root calls as well -- didn't realise I wasn't passing in the new value for beta. Alpha/beta was actually being updated in the root method for separate min-max.
Updated root method for Negamax:
Move bestMove = null;
int bestVal = Integer.MIN_VALUE + 1;
for (Move move : MoveSorter.simpleSort(currBoard.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = currBoard.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) {
int val = -alphaBeta(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE + 1, -bestVal);
if (val > bestVal) {
bestVal = val;
bestMove = move;
}
}
}
return bestMove;
Apologies for the lack of information provided in my question -- I didn't expect the bug to be there.
This question already has answers here:
What causes a java.lang.ArrayIndexOutOfBoundsException and how do I prevent it?
(26 answers)
Find maximum value in an array by recursion
(5 answers)
Closed 6 years ago.
when i dissect the logic of my code, it makes sense to me and looks like it should work. I need to find and return the smallest number in an array using recursion. here is my code
public static int findMin(int[] numbers, int start, int last)
{
int min = numbers[start]; // sets first value in array to minimum
if(numbers[start]<numbers[last]&& numbers[start]<min)
{ // if 1st value < last value in array and 1st value smaller than min, set min to first value
min = numbers[start];
}
else if(numbers[start]>numbers[last]&& numbers[last] < min)
{ // if 1st value > last value and last value < min, set min to last value
min = numbers[last];
}
else
{ // if 1st and last value are equal returns 1st value
return numbers[start];
}
// recursively calls... or not
findMin(numbers, start+1, last-1);
return min;
}
inputs used are 33
-55, -44, 12312, 2778, -3, -2, 53211, -1, 44, 0
output getting:
The minimum number is 0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at Assignment9.countEven(Assignment9.java:72)
at Assignment9.countEven(Assignment9.java:87)
at Assignment9.main(Assignment9.java:34)
Expected: -55
i am assuming my recursive call is incorrectly placed. Please help, thank you.
This should do the trick
public static int findMin(int[] numbers, int start, int last)
{
if(start == last) return numbers[0];
return Math.min(numbers[start],findMin(numbers, start+1, last));
}
If for some reason you cannot use the Math.min you can always use:
public static int findMin(int[] numbers, int start, int last)
{
if(start == last) return numbers[0];
int min = findMin(numbers, start+1, last);
return (numbers[start] <= min) ? numbers[start] : min;
}
The major problems with or solution are:
-> you are not correctly checking the stop case;
-> you are not using the min value calculated in each recursive call;
-> and you are not checking for the cases that you go over the array limits.
First glance, it looks like you are not saving the result from your recursive call, you're just calling it as if it was a void method.
Instead of
findMin(numbers, start+1, last-1);
return min;
Try:
min = findMin(numbers, start+1, last-1);
return min;
Why don't you simple try this:
public static int findMin(int[] numbers, int start, int last) {
if(start == last){
return numbers[start];
}
if(numbers[start] <= numbers[last]) {
last -= 1;
} else {
start += 1;
}
return findMin(numbers, start, last);
}
OR you can use Divide and Conquer strategy as suggested by Qriss:
public static int findMin(int[] numbers){
return findMinHelper(numbers, 0, numbers.length);
}
public static int findMinHelper(int[] numbers, int left, int right){
int min1 = 0, min2 = 0;
if (left == right - 1){
return numbers[left];
} else {
min1 = findMinHelper(numbers, left, (right + left) / 2);
min2 = findMinHelper(numbers, (right + left) / 2, right);
}
return (min1 < min2) ? min1 : min2;
}
I have a little recursive algorithm which should guess a number. I call the method guessNumber and give a number and a lower value than the number and a higher value than the number. If the value is higher than the middle of area of numbers then it makes the same process for the new area of numbers. But it the value is lower it makes the same with the lower area of numbers. And if none of these cases is true then it returns the max value (it could be the min value because they're the same at this point). But why gives that an StackOverflowError? I don't see why the programm can't end. Any help would be appreaciated. Thank you.
public class Starter {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Starter s = new Starter();
System.out.println(s.guessNumber(18, 1, 100));
}
public int guessNumber(int num, int min, int max) {
int middle = (max - (min - 1)) / 2;
if (num > middle) {
guessNumber(num, middle + 1, max);
} else if (num < middle) {
guessNumber(num, min, middle);
}
return max;
}
}
Now I don't get an error anymore with this code:
public int guessNumber(int num, int min, int max) {
int middle = (max + min) / 2;
if (num > middle) {
return guessNumber(num, middle + 1, max);
} else if (num < middle) {
return guessNumber(num, min, middle);
}
return max;
}
But the number isn't correct. If i call it this way guessNumber(18, 1, 100) the expected output should be 18 but i get 19. And if i call it this way guessNumber(34, 1, 100) then the ouput is 37
First of all, you forgot to return the value of the recursive calls.
Second of all, your calculation of middle is wrong. For example, if min is 10 and max is 20, your calculation will assign 5 to middle, instead of 15.
public int guessNumber(int num, int min, int max) {
if (min == max) {
return min;
}
int middle = (max + min) / 2;
if (num > middle) {
return guessNumber(num, middle + 1, max);
} else {
return guessNumber(num, min, middle);
}
}