Non-recursive binary search - java

I'm trying to write this bSearch() method. My professor provided some pseudocode for it; however, I'm having trouble figuring out how to implement some of it. I have coded most of it; however, I have two lines that are still not quite right. I have bolded the sections in which I am have difficulty with. Thank you!
private int bSearch(Item SearchItem)
{
int low = Integer.MIN_VALUE;
int high = Integer.MAX_VALUE;
int foundPosition = -1;
int middle;
Item midPos;
while (low <= high && **we should continue looping**)
{
middle = (low + high) / 2;
midPos = MyStore.get(middle);
if (SearchItem.equals(midPos) == true)
{
foundPosition = middle;
**quit while loop**
}
else if (SearchItem.compareTo(middle) < midPos)
{
high = middle - 1;
}
else
{
low = middle + 1;
}
}
return foundPosition;
}

Welcome to SO. You've made a decent start but here are some suggestions.
you really don't need to store foundPosition - just return the position inside the loop or -1 if the item isn't found
ideally you would be able to get the lower (generally 0) and upper range from the collection myStore. This makes more sense than using min and max integer values.
you don't need to compare booleans to true - just use the expression in your statement
compareTo returns a 0, negative or positive depending on result
That would leave you with something like the following:
private int bSearch(Item searchItem) {
int low = 0;
int high = myStore.size() - 1;
while (low <= high) {
int middle = (low + high) / 2;
int comparison = searchItem.compareTo(myStore.get(middle));
if (comparison < 0) {
low = middle + 1;
} else if (comparison > 0) {
high = middle - 1;
} else {
return middle;
}
}
return -1;
}
Note that I've changed capitalisation and formatting to something considered more standard on this site (and elsewhere).

Related

Largest number of times square root can be calculated on numbers between 2 intervals

I wrote a simple program to calculate the maximum number of times square root can be calculated on a number , input is an interval from num1 to num2
eg:
if the input is (1,20), answer is 2, since square root of 16 is 4 , and square root of 4 is 2 .
int max = 0;
for (int i = num1; i <= num2; i++) {
boolean loop = true;
int count = 0;
int current = i;
if (i == 1) {
count++;
} else {
while (loop) {
double squareRoot = Math.sqrt(current);
if (isCurrentNumberPerfectSquare(squareRoot)) {
count++;
current = (int) squareRoot;
} else {
loop = false;
}
}
}
if (count > max) {
max = count;
}
}
return max;
static boolean isCurrentNumberPerfectSquare(double number) {
return ((number - floor(number)) == 0);
}
I get the answer, but was wondering wether this can be improved using some mathematical way ?
Any suggestions ?
To avoid more confusion here my final answer to this topic.
A combination of both previously mentioned approaches.
What 'Parameswar' is looking for is the largest perfect square formed by the lowest base.
Step 1 -
To get that calculate the largest possible perfect square based on your num2 value.
If it is outside your range, you have no perfect square within.
Step 2 -
If it is within your range, you have to check all perfect square formed by a lower base value with a higher number of times.
Step 3 -
If you find one that is within your range, replace your result with the new result and proceed to check lower values. (go back to Step 2)
Step 4 -
Once the value you check is <= 2 you have already found the answer.
Here some sample implementation:
static class Result {
int base;
int times;
}
static boolean isCurrentNumberPerfectSquare(double number) {
return ((number - Math.floor(number)) == 0);
}
private static int perfectSquare(int base, int times) {
int value = base;
for (int i = times; i > 0; i--) {
value = (int) Math.pow(base, 2);
}
return value;
}
private static Result calculatePerfectSquare(int perfectSquare) {
Result result = new Result();
result.base = (int) Math.sqrt(perfectSquare);
result.times = 1;
while (result.base > 2 && isCurrentNumberPerfectSquare(Math.sqrt(result.base))) {
result.base = (int) Math.sqrt(result.base);
result.times += 1;
}
System.out.println(perfectSquare + " -> " + result.base + " ^ " + result.times);
return result;
}
static int maxPerfectSquares(int num1, int num2) {
int largestPerfectSqr = (int) Math.pow(Math.floor(Math.sqrt(num2)), 2);
if (largestPerfectSqr < num1) {
return 0;
}
Result result = calculatePerfectSquare(largestPerfectSqr);
int currentValue = result.base;
while (currentValue > 2) {
// check lower based values
currentValue--;
int newValue = perfectSquare(currentValue, result.times + 1);
if (newValue >= num1 && newValue < num2) {
result = calculatePerfectSquare(newValue);
currentValue = result.base;
}
}
return result.times;
}
Edit - My assumption is incorrect. Refer to the answer provided by "second".
You can remove the outer loop, num2 can be directly used to determine the number with the maximum number of recursive square roots.
requiredNumber = square(floor(sqrt(num2)));
You just need to check to see if the requiredNumber exists in the range [num1, num2] after finding it.
So the refactoring code would look something like this,
int requiredNumber = Math.pow(floor(Math.sqrt(num2)),2);
int numberOfTimes=0;
if(requiredNumber>=num1) {
if (requiredNumber == 1) {
numberOfTimes=1;
} else{
while (isCurrentNumberPerfectSquare(requiredNumber)) {
numberOfTimes++;
}
}
}
Edit 4: for a more optimal approach check my other answer.
I just leave this here if anybody wants to try to follow my thought process ;)
Edit 3:
Using prime numbers is wrong, use lowest non perfect square instead
Example [35,37]
Edit 2:
Now that I think about it there is a even better approach, especially if you assume that num1 and num2 cover a larger range.
Start with the lowest prime number 'non perfect square' and
calculate the maximum perfect square that fits into your range.
If you have found one, you are done.
If not continue with the next prime number 'non perfect square'.
As a example that works well enough for smaller ranges:
I think you can improve the outerloop. There is no need to test every number.
If you know the smallest perfect square, you can just proceed to the next perfect square in the sequence.
For example:
[16, 26]
16 -> 4 -> 2 ==> 2 perfect squares
No neeed to test 17 to 24
25 -> 5 ==> 1 perfect square
and so on ...
#Chrisvin Jem
Your assumption is not correct, see example above
Edit:
Added some code
static int countPerfectSquares(int current) {
int count = 0;
while (true) {
double squareRoot = Math.sqrt(current);
if (isCurrentNumberPerfectSquare(squareRoot)) {
count++;
current = (int) squareRoot;
} else {
return count;
}
}
}
static boolean isCurrentNumberPerfectSquare(double number) {
return ((number - Math.floor(number)) == 0);
}
static int numPerfectSquares(int num1, int num2) {
int max = 0;
if (num1 == 1) {
max = 1;
}
int sqr = Math.max(2, (int) Math.floor(Math.sqrt(num1)));
int current = (int) Math.pow(sqr, 2);
if (current < num1) {
current = (int) Math.pow(++sqr, 2);
}
while (current <= num2) {
max = Math.max(countPerfectSquares(current), max);
current = (int) Math.pow(++sqr, 2);
}
return max;
}

Error in binary search Java

I've been given this binary search to figure out what's wrong with it. There is a line commented out and so far the only thing I can think of is removing that line as I don't think it is needed. Apart from that, I cannot think of anything missing - is there something really obvious that is wrong?
public boolean search(int val) {
int low = 0;
int high = size-1;
int middle = -1;
boolean found = false;
while (!found && low < high) {
middle = low + (high-low)/2;
if (a[middle] == val)
found = true;
else if (a[middle] < val)
low = middle + 1;
else // (a[middle] > val)
high = middle - 1;
}
return found;
}
Let's take the case when you have 2 values in your array: [0, 1] and you look for value 1. Let's run the code:
int low = 0;
int high = size-1; // high = 1
int middle = -1;
boolean found = false;
while (!found && low < high) {
middle = low + (high-low)/2; // middle = 0 + (1-0) / 2 = 0
if (a[middle] == val) // FALSE (because a[0] = 0)
found = true;
else if (a[middle] < val) // TRUE (because a[0] = 0 and 0 < 1)
low = middle + 1; // low = 0 + 1 = 1
else // (a[middle] > val)
high = middle - 1;
}
return found;
Because low = 1, you get out of the loop since you have a condition low < high and your return false, even though 1 is present in your array.
The problem comes from the fact that middle = low + (high-low)/2; uses int and will be rounded down.
Lets go in and make your code easier to read...
middle = low + (high-low)/2;
if (a[middle] == val) {
found = true;
break;
}
if (a[middle] < val) {
low = middle + 1;
} else {
high = middle - 1;
}
I think now it becomes clearer, what that comment is really saying - it simply expresses that this is the case when a[middle] > val.
Take the array you are search to be [1,2].
Walk the code:
Start: low = 0, high = 1.
Step1: middle = 0 - no match (1 < 2) so low = 1.
Loop check - low < high? NO - stop searching.

Median of medians java implementation

I implemented Median of medians selection algorithm based on algs4 quickselect using the Wikipedia article, but my code doesn't work well:
1) it is said that median of medians finds kth largest element. However, my code finds kth smallest element.
2) my implementation runs 1-20 times slower than quickselect, but the median of medians algorithm should be asymptotically faster.
I've checked everything several times, but I cannot find the issue.
public class MedianOfMedians {
public static Comparable medianOfMedians(Comparable[] nums, int k) {
return nums[select(nums, 0, nums.length - 1, k)];
}
private static int select(Comparable[] nums, int lo, int hi, int k) {
while (lo < hi) {
int pivotIndex = pivot(nums, lo, hi);
int j = partition(nums, lo, hi, pivotIndex);
if (j < k) {
lo = j + 1;
} else if (j > k) {
hi = j - 1;
} else {
return j;
}
}
return lo;
}
private static int pivot(Comparable[] list, int left, int right) {
// for 5 or less elements just get median
if (right - left < 5) {
return partition5(list, left, right);
}
// otherwise move the medians of five-element subgroups to the first n/5 positions
for (int i = left; i <= right; i += 5) {
// get the median of the i'th five-element subgroup
int subRight = i + 4;
if (subRight > right) {
subRight = right;
}
int median5 = partition5(list, i, subRight);
exch(list, median5, (int) (left + Math.floor((i - left) / 5d)));
}
// compute the median of the n/5 medians-of-five
return select(list,
left,
(int) (left + Math.ceil((right - left) / 5d) - 1),
(int) (left + (right - left) / 10d));
}
private static int partition5(Comparable[] list, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for (int j = i; j > lo; j--) {
if (less(list[j - 1], list[j])) {
exch(list, j, j - 1);
}
}
}
return (hi + lo) / 2;
}
private static int partition(Comparable[] a, int lo, int hi, int pivotIndex) {
exch(a, lo, pivotIndex);
int i = lo;
int j = hi + 1;
Comparable v = a[lo];
while (true) {
while (less(a[++i], v) && i != hi) { }
while (less(v, a[--j]) && j != lo) { }
if (j <= i) break;
exch(a, i, j);
}
exch(a, j, lo);
return j;
}
private static void exch(Comparable[] nums, int i, int j) { }
private static boolean less(Comparable v, Comparable w) { }
}
JUnit test:
public class MedianOfMediansTest {
private final static int TESTS_COUNT = 100;
#org.junit.Test
public void test() {
// generate TESTS_COUNT arrays of 10000 entries from 0..Integer.MAX_VALUE
Integer[][] tests = generateTestComparables(TESTS_COUNT, 10000, 10000, 0, Integer.MAX_VALUE);
for (int i = 0; i < tests.length; i++) {
Integer[] array1 = Arrays.copyOf(tests[i], tests[i].length);
Integer[] array2 = Arrays.copyOf(tests[i], tests[i].length);
Integer[] array3 = Arrays.copyOf(tests[i], tests[i].length);
long time = System.nanoTime();
final int a = (Integer) MedianOfMedians.medianOfMedians(array1, 0);
long nanos_a = System.nanoTime() - time;
time = System.nanoTime();
final int b = (Integer) Quick.select(array2, 0);
long nanos_b = System.nanoTime() - time;
time = System.nanoTime();
Arrays.sort(array3);
final int c = array3[0];
long nanos_c = System.nanoTime() - time;
System.out.println("MedianOfMedians: " + a + " (" + nanos_a + ") " +
"QuickSelect: " + b + " (" + nanos_b + ") " +
"Arrays.sort: " + c + " (" + nanos_c + ")");
System.out.println(((double) nanos_a) / ((double) nanos_b));
Assert.assertEquals(c, a);
Assert.assertEquals(b, a);
}
}
public static Integer[][] generateTestComparables(int numberOfTests,
int arraySizeMin, int arraySizeMax,
int valueMin, int valueMax) {
Random rand = new Random(System.currentTimeMillis());
Integer[][] ans = new Integer[numberOfTests][];
for (int i = 0; i < ans.length; i++) {
ans[i] = new Integer[randInt(rand, arraySizeMin, arraySizeMax)];
for (int j = 0; j < ans[i].length; j++) {
ans[i][j] = randInt(rand, valueMin, valueMax);
}
}
return ans;
}
public static int randInt(Random rand, int min, int max) {
return (int) (min + (rand.nextDouble() * ((long) max - (long) min)));
}
}
1) it is said that median of medians finds kth largest element.
However, my code finds kth smallest element.
This is not strictly true. Any selection algorithm can find either smallest or largest element because that's essentially the same task. It depends on how you compare elements and how you partition them (and you can always do something like length - 1 - result later). Your code indeed seems to find the kth smallest element, which is by the way the most typical and intuitive way of implementing a selection algorithm.
2) my implementation runs 1-20 times slower than quickselect, but the
median of medians algorithm should be asymptotically faster.
Not just asymptotically faster. Asymptotically faster in the worst case. In the average case, both are linear, but MoM has higher constant factors. Since you generate your tests randomly, you are very unlikely to hit the worst case. If you used randomized quickselect, then for any input it's unlikely to hit the worst case, otherwise the probability will depend on the pivot selection algorithm used.
With that in mind, and the fact that median of medians has high constant factors, you should not expect it to perform better than quickselect! It might outperform sorting, though, but even then—those logarithmic factors in sorting aren't that large for small inputs (lg 10000 is about 13-14).
Take my MoM solution for a LeetCode problem, for example. Arrays.sort sometimes outperforms MoM for arrays with 500 million elements. In the best case it runs about twice faster, though.
Therefore, MoM is mostly of theoretical interest. I could imagine a practical use case when you need 100% guarantee of not exceeding some time limit. Say, some real-time system on an aircraft, or spacecraft, or nuclear reactor. The time limit is not very tight, but exceeding it even by one nanosecond is catastrophic. But it's an extremely contrived example, and I doubt that it's actually the way it works.
Even if you can find a practical use case for MoM, you can probably use something like Introselect instead. It essentially starts with quickselect, and then switches to MoM if things don't look good. But testing it would be a nightmare—how would you come up with a test that actually forces the algorithm to switch (and therefore test the MoM part), especially if it's randomized?
Your code looks fine overall, but I'd make some helper methods package-private or even moved them to another class to test separately because such things are very hard to get right. And you may not notice the effect if the result is right. I'm not sure that your groups-of-five code is 100% correct, for example. Sometimes you use right - left where I'd expect to see element count, which should be right - left + 1.
Also, I would replace those ceil/floor calls with pure integer arithmetic equivalents. That is, Math.floor((i - left) / 5d)) => (i - left) / 5, Math.ceil((right - left) / 5d) => (right - left + 4) / 5 (this is the part where I don't like the right - left thing, by the way, but I'm not sure if it's wrong).

BinarySearch Implementation does not work in certain cases

I'm a java beginner and I'm trying to code an implementation of the binary search algorithm.
This is my code:
protected int doSearch(List<Integer> list, int key) throws SearchException{
int min = 0;
int max = list.size()-1;
while(max > min){
int mid = min + ((max-min)/2);
if(list.get(mid)==key){
return mid;
}
else if(list.get(mid) < key){
min = mid +1 ;
}
else{
max = mid - 1;
}
}
throw new SearchException("");
}
I tried to copy it from this link http://en.wikipedia.org/wiki/Binary_search_algorithm and tried to get it working for lists.
The input list is [1, 2, 3, 4, 5, 7, 9]
If I search for the key 2 the output is 1 which is fine, but if I try for example 1 the SearchException is fired.
I can't explain why. I tried to debug the code by reproducing it on paper, but it worked on the paper.
Thank you!
You're currently inconsistent about whether max is an inclusive lower bound, as suggested here:
int max = list.size()-1;
...
max = mid - 1;
or an exclusive lower bound, as suggested here:
while (max > min)
You can make it work either way, so long as you're consistent. Personally I'd suggest using an exclusive upper bound, as that's consistent with things like list.size() and computer science in general. So if mid is too large, you need to change max to equal mid. Your code will look like this:
int max = list.size(); // Note change here
while(max > min) {
int mid = min + ((max - min) / 2);
if (list.get(mid) == key) {
return mid;
} else if (list.get(mid) < key) {
min = mid +1 ;
} else {
max = mid; // Note change here
}
}
(I've fiddled with the formatting to make it easier to read IMO as well. See whether you prefer it or not.)
Easiest fix is to change:
while (max > min) {
to:
while (max >= min) {
You throw the exception in the above because you don't handle that case.

Count the number of occurrences of a number in a sorted array

My teacher gave me the next task:
On a sorted array, find the number of occurrences of a number.
The complexity of the algorithm must be as small as possible.
This is what I have thought of:
public static int count(int[] a, int x)
{
int low = 0, high = a.length - 1;
while( low <= high )
{
int middle = low + (high - low) / 2;
if( a[middle] > x ) {
// Continue searching the lower part of the array
high = middle - 1;
} else if( a[middle] < x ) {
// Continue searching the upper part of the array
low = middle + 1;
} else {
// We've found the array index of the value
return x + SearchLeft(arr, x, middle) + SearchRight(arr, x, middle);
}
}
return 0;
}
SearchLeft and SearchRight iterate the array, until the number doesn't show.
I'm not sure if I have achieved writing the faster code for this problem, and I would like see other opinions.
Edit: After some help from comments and answers, this is my current attempt:
public static int count(int[] array, int value)
{
return SearchRightBound(array, value) - SearchLeftBound(array, value);
}
public static int SearchLeftBound(int[] array, int value)
{
int low = 0, high = array.length - 1;
while( low < high )
{
int middle = low + (high - low) / 2;
if(array[middle] < value) {
low = middle + 1;
}
else {
high = middle;
}
}
return low;
}
public static int SearchRightBound(int[] array, int value)
{
int low = 0, high = array.length - 1;
while( low < high )
{
int middle = low + (high - low) / 2;
if(array[middle] > value) {
high = middle;
}
else {
low = middle + 1;
}
}
return low;
}
SearchLeft and SearchRight iterate the array, until the number doesn't show.
That means if the entire array is filled with the target value, your algorithm is O(n).
You can make it O(log n) worst case if you binary-search for the first and for the last occurrence of x.
// search first occurrence
int low = 0, high = a.length - 1;
while(low < high) {
int middle = low + (high-low)/2;
if (a[middle] < x) {
// the first occurrence must come after index middle, if any
low = middle+1;
} else if (a[middle] > x) {
// the first occurrence must come before index middle if at all
high = middle-1;
} else {
// found an occurrence, it may be the first or not
high = middle;
}
}
if (high < low || a[low] != x) {
// that means no occurrence
return 0;
}
// remember first occurrence
int first = low;
// search last occurrence, must be between low and a.length-1 inclusive
high = a.length - 1;
// now, we always have a[low] == x and high is the index of the last occurrence or later
while(low < high) {
// bias middle towards high now
int middle = low + (high+1-low)/2;
if (a[middle] > x) {
// the last occurrence must come before index middle
high = middle-1;
} else {
// last known occurrence
low = middle;
}
}
// high is now index of last occurrence
return (high - first + 1);
Well this is essentially binary search + walking towards the boundaries of the solution interval. The only way you could possibly speed this is up is maybe cache the last values of low and high and then use binary search to find the boarders as well, but this will really only matter for very large intervals in which case it's unlikely that you jumped right into it.

Categories