JAVA - Binary search - return index of key - java

I have the following problem I need to solve, but is strugling a bit. Would really appreciate it if someone can help.
In short in comes down to the following:
If the search key is in the array - it returns the smallest index i for which a[i] is equal to the key
If the search key is not in the array but greater - it returns the smallest index i as -i where a[i] is greater than the key
If the search key is not in the array but smaller - it returns -j where j is the index of the last element in the array
I have the code of searching for the key, but I'm not sure how to return the indexes as mentioned above...
import java.util.Arrays;
public class BinarySearchIndex2 {
// a working recursive version
public static int search(String key, String[] a) {
return search(key, a, 0, a.length);
}
public static int search(String key, String[] a, int lo, int hi) {
// possible key indices in [lo, hi)
if (hi <= lo) return -1;
int mid = lo + (hi - lo) / 2;
int cmp = a[mid].compareTo(key);
if (cmp > 0) return search(key, a, lo, mid);
else if (cmp < 0) return search(key, a, mid+1, hi);
else return mid;
}
public static void main(String[] args) {
String key = args[0];
int sizeoflist = StdIn.readInt();
String[] a = new String[sizeoflist];
int counter = 0; //counter for while loop to fill array a
while (!StdIn.isEmpty()){
a[counter] = StdIn.readString();
counter++;
}
Arrays.sort(a); // sort the words (if needed)
if ( search(key, a) < 0) ; /* System.out.println();*/
else if ( search(key, a) > 0 ) ;
else if ( search(key, a) = 0 ) ;
}
}
Would be really glad if someone can help me with this matter...
Thanks!

String.compareTo performs a lexicographical comparison. This means that it can decide that "50">"100", whereas clearly 50<100 is what you would expect. This will affect your Arrays.sort call so your Array is already messed up.
Is it a must to have public static int search(String key, String[] a) as your API?
If you can change it to be public static int search(int key, int[] a) it will make your API work (assuming you don't have any bugs I missed).
Hope this was what you were referring to.
Edit: some fine tuning to the problem analysis.

The important point is here:
if (hi <= lo) return -1;
This occurs when you got an sub-array to search of size zero, which means that the element is not there. Now think: what does the specification say about the return value here?

Related

Infinite loop in binary search variant (Java)

My aim is to input a key and an array, and then output the number of values in that array which are less than or equal to the key, using binary search.
This is my code:
import java.util.*;
import java.io.*;
public class search {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int key = scan.nextInt();
int size = scan.nextInt();
int [] array = new int [size];
for (int i = 0;i < size ;i++ ) {
array[i] = scan.nextInt();
}
Arrays.sort(array);
System.out.println(binary(key, array));
}
public static int binary (int key, int [] array){
int lo = 0;
int hi = array.length - 1;
while (lo < hi){
int mid = (lo + hi) / 2;
if (array[mid] <= key){
lo = mid;
}
else {
hi = mid - 1;
}
}
return lo + 1;
}
}
With the data key = 5, array = {2,4,6,7}, the program works fine. But the moment there are three values that are less than or equal to the key, it goes haywire. For example, key = 5, array = {2,4,5,6} produces an infinite loop. I've found the reason for this but I don't see how to get around it.
Basically the mid value keeps getting calculated as the same value. What can I do to get around this? If the code is inherently wrong, then that means the the set solution for a USACO problem was wrong.
The sample solution seems fine. The problem with your code is that mid = (lo + hi) / 2 rounds toward lo, which is a problem since the update cases are lo = mid and hi = mid - 1. When hi == lo + 1, in the first case, no progress is made. You should round up as the sample does: mid = (lo + hi + 1) / 2 (warning: may overflow on long arrays; check your constraints).
The sample also checks whether the first value is less than the key.
Your Algorithm seems fine. But i see there is a problem with assigning the value for mid.
int mid = (hi+lo)/2
think of a case where your
hi=4
mid = 3
now the new value for mid will be (3+4)/2 = 3 (integer division)
so the loop will continue running without breaking.
In this case what you can do is increment the value of mid, by checking this condition.
But more effectively its seems better check the value of mid is repeating, Then break the loop.
At last check whether the value of array[hi] is the same as your array[mid]
Hope this would help..
Have a nice day.. :)
EDIT
A better Way of doing would be
Change your while loop to
while(mid<hi+1){
}
then check for equality of the values after the loop..
OR
Simply set
mid = (mid+hi+1)/2
In your loop, you must make your intervals ever smaller and you must also exclude the element you've just looked at. You do that when you go left, but not when you go right.
You also miss out on intervals of length 1, because you use inclusive lower and upper bounds. Your loop condition should be lo <= hi.
Finally, you return one too many: The results should be 0 when the key is smaller than the first element and it should be the array length when it is greater than the last element.
So:
static int binary(int key, int[] array)
{
int lo = 0;
int hi = array.length - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (array[mid] <= key) {
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return lo;
}
In my opinion, it is better to use an exclusive upper bound, as Java usually does. (For example, an array of length n has elements at indoces 0 to n - 1, the upper bound n is outside the valid range.) If nothing else, it is consitent with other Java code. So:
static int binary(int key, int[] array)
{
int lo = 0;
int hi = array.length;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (array[mid] <= key) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}

Inplace Quicksort in Java

For refreshing some Java I tried to implement a quicksort (inplace) algorithm that can sort integer arrays. Following is the code I've got so far. You can call it by sort(a,0,a.length-1).
This code obviously fails (gets into an infinite loop) if both 'pointers' i,j point each to an array entry that have the same values as the pivot. The pivot element v is always the right most of the current partition (the one with the greatest index).
But I just cannot figure out how to avoid that, does anyone see a solution?
static void sort(int a[], int left, int right) {
if (right > left){
int i=left, j=right-1, tmp;
int v = a[right]; //pivot
int counter = 0;
do {
while(a[i]<v)i++;
while(j>0 && a[j]>v)j--;
if( i < j){
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
} while(i < j);
tmp = a[right];
a[right] = a[i];
a[i] = tmp;
sort(a,left,i-1);
sort(a,i+1,right);
}
}
When preforming a Quicksort I strongly suggest making a separate method for partitioning to make the code easier to follow (I'll show an example below). On top of this a good way of avoiding worst case run time is shuffling the array you're sorting prior to preforming the quick sort. Also I used the first index as the partitioning item instead of the last.
For example:
public static void sort (int[] a)
{
StdRandom.shuffle(a);
sort(a, 0, a.length - 1);
}
private static void sort(int[] a, int lo, int hi)
{
if (hi <= lo) return;
int j = partition(a, lo, hi) // the addition of a partitioning method
sort(a, lo, j-1);
sort(a, j+1, hi);
}
private static int partition(int[] a, int lo, int hi)
{
int i = lo, j = hi + 1, tmp = 0;
int v = a[lo];
while (true)
{
while (a[i++] < v) if (i == hi) break;
while (v < a[j--]) if (j == lo) break;
if (i >= j) break;
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
tmp = a[lo];
a[lo] = a[j];
a[j] = temp;
return j;
}
On top of this if you want a really good example on how Quicksort works (as a refresher) see here.
This should work (will check for correctness in a bit, it works!):
EDIT: I previously made a mistake in error checking. I forgot to add 2 more conditions, here is the amended code.
public static void main (String[] args) throws java.lang.Exception
{
int b[] = {10, 9, 8, 7, 7, 7, 7, 3, 2, 1};
sort(b,0,b.length-1);
System.out.println(Arrays.toString(b));
}
static void sort(int a[], int left, int right) {
if (right > left){
int i=left, j=right, tmp;
//we want j to be right, not right-1 since that leaves out a number during recursion
int v = a[right]; //pivot
do {
while(a[i]<v)
i++;
while(a[j]>v)
//no need to check for 0, the right condition for recursion is the 2 if statements below.
j--;
if( i <= j){ //your code was i<j
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
i++;
j--;
//we need to +/- both i,j, else it will stick at 0 or be same number
}
} while(i <= j); //your code was i<j, hence infinite loop on 0 case
//you had a swap here, I don't think it's needed.
//this is the 2 conditions we need to avoid infinite loops
// check if left < j, if it isn't, it's already sorted. Done
if(left < j) sort(a,left,j);
//check if i is less than right, if it isn't it's already sorted. Done
// here i is now the 'middle index', the slice for divide and conquer.
if(i < right) sort(a,i,right);
}
}
This Code in the IDEOne online compiler
Basically we make sure that we also swap the value if the value of i/j is the same as the pivot, and break out of the recursion.
Also there was a check in the pseudocode for the length, as if we have an array of just 1 item it's already sorted (we forgot the base case), I thought we needed that but since you pass in the indexes and the entire array, not the subarray, we just increment i and j so the algorithm won't stick at 0 (they're done sorting) but still keep sorting an array of 1. :)
Also, we had to add 2 conditions to check if the array is already sorted for the recursive calls. without it, we'll end up sorting an already sorted array forever, hence another infinite loop. see how I added checks for if left less than j and if i less than right. Also, at that point of passing in i and j, i is effectively the middle index we split for divide and conquer, and j would be the value right before the middle value.
The pseudocode for it is taken from RosettaCode:
function quicksort(array)
if length(array) > 1
pivot := select any element of array
left := first index of array
right := last index of array
while left ≤ right
while array[left] < pivot
left := left + 1
while array[right] > pivot
right := right - 1
if left ≤ right
swap array[left] with array[right]
left := left + 1
right := right - 1
quicksort(array from first index to right)
quicksort(array from left to last index)
Reference: This SO question
Also read this for a quick refresher, it's implemented differently with an oridnary while loop
This was fun :)
Heres some simple code I wrote that doesn't initialize to many pointers and gets the job done in a simple manner.
public int[] quickSort(int[] x ){
quickSortWorker(x,0,x.length-1);
return x;
}
private int[] quickSortWorker(int[] x, int lb, int ub){
if (lb>=ub) return x;
int pivotIndex = lb;
for (int i = lb+1 ; i<=ub; i++){
if (x[i]<=x[pivotIndex]){
swap(x,pivotIndex,i);
swap(x,i,pivotIndex+1);
pivotIndex++;
}
}
quickSortWorker(x,lb,pivotIndex-1);
quickSortWorker(x,pivotIndex+1,ub);
return x;
}
private void swap(int[] x,int a, int b){
int tmp = x[a];
x[a]=x[b];
x[b]=tmp;
}

Reversing the order of sub-array using Recursion

I am trying to reverse the order of a sub-array between the indices of start and end strictly using recursion. For example, if the subarray is 1,2,3,4 , it will become 4,3,2,1.
However, I am getting the following runtime error:
java.lang.ArrayIndexOutOfBoundsException: -1
at finalExam.reverse(finalExam.java:13)
at finalExam.reverse(finalExam.java:17)
I am not sure how to fix this problem.
Thanks.
double[] reverse (double[] a, int start, int end) {
if (start == end) {return a;}
else {
a[start] = a[end];
a[end] = a[start];}
return reverse (a, start+1, end-1);
}
(Since you mention the exam is over). Here are the problems with your code:
Your check should be start >= end
Your code for swapping two numbers is incorrect.
Here is the correct solution:
public static double[] reverse (double[] a, int start, int end) {
if (start >= end) {
return a;
}
else {
// this code will swap two elements
double temp = a[start];
a[start] = a[end];
a[end] = temp;
}
return reverse (a, start+1, end-1);
}

Quicksort doesnt work in some cases

As I was testing my quicksort I noticed another problem. Sometimes it arranges the array in alphabetical order and sometimes it does not. For example if I have p, o, j, l as my array it sorts it to j, o, l, p which is wrong because l should be before o However if I add a to the array it sorts to a, j, l, o, p which is correct. Why is this happening?
Code:
private ArrayList<String> sort(ArrayList<String> ar, int lo, int hi){
if (lo < hi){
int splitPoint = partition(ar, lo, hi);
sort(ar, lo, splitPoint);
sort(ar, splitPoint +1, hi);
}
return ar;
}
private int partition(ArrayList<String> ar, int lo, int hi){
String pivot = ar.get(lo);
lo--;
hi++;
while (true){
lo++;
hi--;
while (lo<hi && ar.get(lo).compareTo(pivot) < 0){
lo++;
}
while (hi>lo && ar.get(hi).compareTo(pivot) >= 0){
hi--;
}
if (lo<hi){
swap(ar, lo, hi);
}else {
return hi;
}
}
}
private ArrayList<String> swap(ArrayList<String> ar, int a, int b){
String temp = ar.get(a);
ar.set(a, ar.get(b));
ar.set(b, temp);
return ar;
}
As far as I can see, you are not changing the position of pivot element. You are just swapping hi with lo.
At the end, you should place the pivot at its correct position and then return it.
I think there is a danger in the last iteration of your loop.
Suppose you had an array 2,1.
Your pivot is 2.
The lo index increases until it finds an element above the pivot of 1 or meets hi.
In this case it will meet hi and both lo and hi will be pointing to 1.
At this point your partition function returns and reports that the array has been partitioned into:
{2},{1}
But this is incorrect because 1 is < than the pivot so should have been in the first partition.
Perhaps when lo meets hi you should perform an extra test to see whether the element at hi should be included in the left or right partition?

Finding index of array where min value occurs

This one is making my head spin. Just when I think I got it, I realize something's not right. I have to use recursion for this assignment. Any hints?
/**
* Uses recursion to find index of the shortest string.
* Null strings are treated as infinitely long.
* Implementation notes:
* The base case if lo == hi.
* Use safeStringLength(paths[xxx]) to determine the string length.
* Invoke recursion to test the remaining paths (lo +1)
*/
static int findShortestString(String[] paths, int lo, int hi) {
int min=lo;
if (lo==hi)
return min;
if (safeStringLength(paths[lo]) < safeStringLength(paths[lo+1])){
min=lo;
return Math.min(min, findShortestString(paths, lo+1, hi));
}
else{
min=lo+1;
return Math.min(min, findShortestString(paths, lo+1, hi));
}
}
I think got something here:
static int findShortestString(String[] paths, int lo, int hi)
{
if (lo==hi)
return lo;
int ShortestIndexSoFar = findShortestString(paths, lo+1, hi);
if(safeStringLength(paths[ShortestIndexSoFar]) < safeStringLength(paths[lo]))
return ShortestIndexSoFar;
else
return lo;
}
static int safeStringLength(String str)
{
if(str == null)
return Integer.MAX_VALUE;
return str.length();
}
Explaining why this works:
Here's a sample:
[0] ab
[1] abcd
[2] a
[3] abc
[4] ab
Obviously, index 2 is the shortest one.
Think bottoms up. Read the following starting from the bottom, upwards.
I make it sound like each function is talking to the function above it in the chain.
And each line is lead by the function parameters that were passed.
"[0] ab (paths, 0, 4): return 2, coz he's shorter than me or anyone before us"
"[1] abcd (paths, 1, 4): return 2, coz he's shorter than me or anyone before us"
"[2] a (paths, 2, 4): return 2, I'm shorter than anyone before me"
"[3] abc (paths, 3, 4): return 4, coz he's shorter than me or anyone before us"
"[4] ab (paths, 4, 4): return 4, I'm the shortest; I don't know any better"
So in the code, you see that exactly happening.
When we define ShortestIndexSoFar, this is where each function will know the shortest of all the paths beyond it.
And right after it is where the function itself checks if its index has a shorter path than the shortest of all the ones below.
Keep trickling the shortest one upward, and the final guy will return the shortest index.
That makes sense?
Since this is homework, here's a hint to help you learn:
The signature of the findShortestString method suggests that you should be using a binary search. Why would I say that? Why would it be a good idea to do that? All of the other solutions suffer from a practical problem in Java ... what would that be?
To people other than the OP ... PLEASE DON'T GIVE THE ANSWERS AWAY ... e.g. by correcting your answers!
Why not just get the length of each element and sort the returned length to get the ordering? Like this.
int[] intArray = {10, 17, 8, 99, 1}; // length of each element of string array
Arrays.sort(intArray);
one solution will be like this
public static final int findShortestString(final String[] arr, final int index, final int minIndex) {
if(index >= arr.length - 1 ) {
return minIndex;
}
if(-1 == safeStringLength(arr[index])) {
return index;
}
int currentMinIncex = minIndex;
if(safeStringLength(arr[minIndex]) > safeStringLength(arr[index+1])){
currentMinIncex = index + 1;
}
return findShortestString(arr, index + 1, currentMinIncex);
}
public static final int safeStringLength(final String string) {
if( null == string) return -1;
return string.length();
}
A simple solution:
/**
* Uses recursion to find index of the shortest string.
* Null strings are treated as infinitely long.
* Implementation notes:
* The base case if lo == hi.
* Use safeStringLength(paths[xxx]) to determine the string length.
* Invoke recursion to test the remaining paths (lo +1)
*/
static int findShortestString(String[] paths, int lo, int hi) {
if (lo==hi)
return lo;
if (paths[lo] == null)
return findShortestString(paths, lo+1, hi);
int bestIndex = findShortestString(paths, lo+1, hi);
if (safeStringLength[lo] < safeStringLength[bestIndex])
return lo;
return bestIndex;
}
Calculating min on the result of running findShortestString isn't meaningful. The best way to start this kind of problem is to consider just a single recursive step, you can do this by considering what happens with an array of only two strings to compare.
What you want to do is check the length of the first string against the length of the second. The real trick, though, is that you want to test the length of the second by calling the function recursively. This is straight forward enough, but requires determining the end-case of your recursion. You did this successfully, it's when lo == hi. That is, when lo == hi the shortest known string is lo (it's the only known string!).
Ok, so back to comparing just two strings. Given that you know that you want to compare the length of two strings stored in paths, you might do something like this (without recursion):
if(safeStringLength(paths[0]) < safeStringLength(paths[1])){
return 0; // paths[0] is shorter
}else{
return 1; // paths[1] is shorter
}
But you want to recurse -- and in the recurse step you need to somehow generate that 1 of paths[1]. We already figured out how to do that, when lo == hi, we return lo. Thus the recursion step is "compare the current lowest known string length to the string length of the best known index" -- wait, we have a function for that! it's findShortestString. Thus we can modify what's written above to be slightly more concise, and add in the base case to get:
static int findShortestString(String[] paths, int lo, int hi) {
// base case, no comparisons, the only known string is the shortest one
if(lo == hi){
return lo;
}
int bestIndex = findShortestString(paths, lo+1, hi);
return safeStringLength(paths[lo]) < safeStringLength(paths[bestIndex]) ?
lo : bestIndex;
}
static int findShortestString(String[] paths, int lo, int hi)
{
if (lo==hi)
return lo;
int ShortestIndexDown = findShortestString(paths, lo, (hi + lo)/2);
int ShortestIndexUp = findShortestString(paths, (lo+hi)/2+1, hi);
return SafeStringLength(paths[ShortestIndexDown]) < SafeStringLength(paths[ShortestIndexUp])?ShortestIndexDown:ShortestIndexUp;
}
static int safeStringLength(String str)
{
if(str == null)
return Integer.MAX_VALUE;
return str.length();
}

Categories

Resources