This blog teaches you how to use searching algorithms to find data in a collection in Java. You will learn about linear search, binary search, and interpolation search, and see some code examples.
1. Introduction
Searching is one of the most common operations performed on data structures. Searching algorithms are methods that help you find a specific element or a set of elements in a collection of data. In this blog, you will learn about some of the most popular searching algorithms in Java and how to implement them in your code.
But why do you need to learn about searching algorithms? Well, there are many reasons, such as:
- Searching algorithms can help you optimize the performance and efficiency of your code by reducing the time and space complexity of your operations.
- Searching algorithms can help you solve various problems that involve finding, sorting, or filtering data in different scenarios.
- Searching algorithms can help you understand the underlying logic and principles of data structures and algorithms, which are essential for any programmer.
So, what are the searching algorithms that you will learn in this blog? Here is a brief overview:
- Linear search: This is the simplest and most basic searching algorithm that works by checking each element of the collection sequentially until it finds the target element or reaches the end of the collection.
- Binary search: This is a more efficient and faster searching algorithm that works by dividing the collection into two halves and comparing the target element with the middle element of each half. It then repeats this process on the half that contains the target element until it finds it or determines that it does not exist.
- Interpolation search: This is an improved version of binary search that works by estimating the position of the target element based on the distribution of the data in the collection. It then uses this position to narrow down the search range and find the target element faster.
Are you ready to dive into the world of searching algorithms in Java? Let’s get started!
2. Linear Search
Linear search is the simplest and most basic searching algorithm that works by checking each element of the collection sequentially until it finds the target element or reaches the end of the collection. Linear search is also known as sequential search, as it follows a sequential order of the elements.
How does linear search work? Let’s see an example. Suppose you have an array of 10 numbers and you want to find the number 7 in the array. How would you do it? You would start from the first element of the array and compare it with the number 7. If they are equal, you have found the target element and you can stop the search. If they are not equal, you move on to the next element and repeat the process. You keep doing this until you either find the number 7 or reach the end of the array.
The linear search algorithm takes 6 comparisons to find the number 7 in the array. If the number 7 was not in the array, it would take 10 comparisons to determine that it does not exist.
How can you implement linear search in Java? Here is a simple code snippet that shows how to perform linear search on an array of integers:
// Linear search method public static int linearSearch(int[] array, int target) { // Loop through the array from the beginning for (int i = 0; i < array.length; i++) { // Compare the current element with the target element if (array[i] == target) { // Return the index of the element if found return i; } } // Return -1 if the element is not found return -1; } // Main method public static void main(String[] args) { // Create an array of numbers int[] numbers = {3, 5, 9, 2, 8, 10, 11, 7, 4, 6}; // Define the target element int target = 7; // Call the linear search method and print the result int result = linearSearch(numbers, target); if (result == -1) { System.out.println("The element " + target + " is not in the array."); } else { System.out.println("The element " + target + " is found at index " + result + "."); } }
The output of the code is:
The element 7 is found at index 7.
What are the advantages and disadvantages of linear search? Here are some points to consider:
- Advantages:
- Linear search is easy to implement and understand.
- Linear search does not require any prior sorting or ordering of the collection.
- Linear search can work on any type of collection, such as arrays, lists, or linked lists.
- Disadvantages:
- Linear search is inefficient and slow, especially for large collections.
- Linear search has a high time complexity of O(n), where n is the number of elements in the collection.
- Linear search does not take advantage of any property or pattern of the data, such as sortedness or distribution.
Linear search is a simple and straightforward searching algorithm that can be useful for small or unsorted collections. However, if you want to improve the performance and efficiency of your search, you might want to consider other searching algorithms, such as binary search or interpolation search. In the next sections, you will learn how to use these algorithms and compare them with linear search.
3. Binary Search
Binary search is a more efficient and faster searching algorithm than linear search that works by dividing the collection into two halves and comparing the target element with the middle element of each half. Binary search then repeats this process on the half that contains the target element until it finds it or determines that it does not exist. Binary search requires that the collection is sorted in ascending or descending order before applying the algorithm.
How does binary search work? Let’s see an example. Suppose you have an array of 10 numbers sorted in ascending order and you want to find the number 7 in the array. How would you do it? You would start by finding the middle element of the array, which is 8. You would then compare it with the number 7. Since 7 is less than 8, you would discard the right half of the array and focus on the left half. You would then find the middle element of the left half, which is 5. You would compare it with the number 7. Since 7 is greater than 5, you would discard the left half of the left half and focus on the right half of the left half. You would then find the middle element of the right half of the left half, which is 7. You would compare it with the number 7. Since they are equal, you have found the target element and you can stop the search.
The binary search algorithm takes 3 comparisons to find the number 7 in the array. If the number 7 was not in the array, it would take 4 comparisons to determine that it does not exist.
How can you implement binary search in Java? Here is a simple code snippet that shows how to perform binary search on an array of integers using an iterative approach:
// Iterative binary search method public static int iterativeBinarySearch(int[] array, int target) { // Define the left and right boundaries of the search range int left = 0; int right = array.length - 1; // Loop until the search range is exhausted while (left <= right) { // Find the middle element of the search range int mid = (left + right) / 2; // Compare the middle element with the target element if (array[mid] == target) { // Return the index of the element if found return mid; } else if (array[mid] < target) { // Discard the left half of the search range and update the left boundary left = mid + 1; } else { // Discard the right half of the search range and update the right boundary right = mid - 1; } } // Return -1 if the element is not found return -1; } // Main method public static void main(String[] args) { // Create a sorted array of numbers int[] numbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Define the target element int target = 7; // Call the iterative binary search method and print the result int result = iterativeBinarySearch(numbers, target); if (result == -1) { System.out.println("The element " + target + " is not in the array."); } else { System.out.println("The element " + target + " is found at index " + result + "."); } }
The output of the code is:
The element 7 is found at index 5.
What are the advantages and disadvantages of binary search? Here are some points to consider:
- Advantages:
- Binary search is more efficient and faster than linear search, especially for large collections.
- Binary search has a low time complexity of O(log n), where n is the number of elements in the collection.
- Binary search takes advantage of the sortedness of the data, as it eliminates half of the search range in each iteration.
- Disadvantages:
- Binary search requires that the collection is sorted before applying the algorithm, which can be costly if the collection is not already sorted.
- Binary search does not work well on dynamic or frequently updated collections, as the sorting process has to be repeated every time the collection changes.
- Binary search does not work on collections that do not have random access, such as linked lists, as it requires accessing the middle element in constant time.
Binary search is a powerful and efficient searching algorithm that can be used to find elements in sorted collections. However, binary search has some limitations and assumptions that might not suit every scenario. In the next sections, you will learn how to use a variation of binary search called interpolation search and compare it with binary search.
3.1. Iterative Binary Search
In the previous section, you learned how to implement binary search using an iterative approach. In this section, you will learn how to implement binary search using a recursive approach. Recursive binary search is a variation of binary search that uses recursion to divide the collection into smaller subarrays and search for the target element.
How does recursive binary search work? Let’s see an example. Suppose you have the same array of 10 numbers sorted in ascending order and you want to find the number 7 in the array. How would you do it? You would start by defining a helper method that takes the array, the target element, and the left and right boundaries of the search range as parameters. You would then call this method with the initial values of the parameters, which are the array, the target element, and the indices of the first and last elements of the array. The helper method would then perform the following steps:
- Check if the search range is valid, i.e., if the left boundary is less than or equal to the right boundary. If not, return -1 to indicate that the element is not found.
- Find the middle element of the search range by adding the left and right boundaries and dividing by 2.
- Compare the middle element with the target element. If they are equal, return the index of the element. If the middle element is less than the target element, discard the left half of the search range and call the helper method recursively with the updated left boundary. If the middle element is greater than the target element, discard the right half of the search range and call the helper method recursively with the updated right boundary.
Here is a visual representation of the recursive binary search algorithm:
Recursive binary search example
As you can see, the recursive binary search algorithm takes the same number of comparisons as the iterative binary search algorithm to find the number 7 in the array. However, the recursive binary search algorithm uses a different technique to achieve the same result.
How can you implement recursive binary search in Java? Here is a simple code snippet that shows how to perform recursive binary search on an array of integers:
// Recursive binary search helper method public static int recursiveBinarySearchHelper(int[] array, int target, int left, int right) { // Check if the search range is valid if (left > right) { // Return -1 if the element is not found return -1; } // Find the middle element of the search range int mid = (left + right) / 2; // Compare the middle element with the target element if (array[mid] == target) { // Return the index of the element if found return mid; } else if (array[mid] < target) { // Discard the left half of the search range and update the left boundary left = mid + 1; // Call the helper method recursively with the updated left boundary return recursiveBinarySearchHelper(array, target, left, right); } else { // Discard the right half of the search range and update the right boundary right = mid - 1; // Call the helper method recursively with the updated right boundary return recursiveBinarySearchHelper(array, target, left, right); } } // Recursive binary search method public static int recursiveBinarySearch(int[] array, int target) { // Define the left and right boundaries of the search range int left = 0; int right = array.length - 1; // Call the helper method with the initial values of the parameters return recursiveBinarySearchHelper(array, target, left, right); } // Main method public static void main(String[] args) { // Create a sorted array of numbers int[] numbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Define the target element int target = 7; // Call the recursive binary search method and print the result int result = recursiveBinarySearch(numbers, target); if (result == -1) { System.out.println("The element " + target + " is not in the array."); } else { System.out.println("The element " + target + " is found at index " + result + "."); } }
The output of the code is:
The element 7 is found at index 5.
What are the advantages and disadvantages of recursive binary search? Here are some points to consider:
- Advantages:
- Recursive binary search is more elegant and concise than iterative binary search, as it uses fewer variables and lines of code.
- Recursive binary search is easier to understand and debug, as it follows a clear logic and structure.
- Recursive binary search can be adapted to other problems that involve dividing and conquering, such as finding the maximum or minimum element in an array.
- Disadvantages:
- Recursive binary search is more memory intensive than iterative binary search, as it uses the call stack to store the recursive calls.
- Recursive binary search can cause stack overflow errors if the recursion depth is too high or the collection is too large.
- Recursive binary search can be slower than iterative binary search, as it involves function calls and returns that add to the execution time.
Recursive binary search is a variation of binary search that uses recursion to divide and conquer the collection and search for the target element. Recursive binary search has some advantages and disadvantages over iterative binary search, depending on the situation and preference. In the next section, you will learn how to use another variation of binary search called recursive binary search and compare it with binary search.
3.2. Recursive Binary Search
In the previous section, you learned how to implement binary search using an iterative approach. In this section, you will learn how to implement binary search using a recursive approach. Recursive binary search is a variation of binary search that uses recursion to divide the collection into smaller subarrays and search for the target element.
How does recursive binary search work? Let’s see an example. Suppose you have the same array of 10 numbers sorted in ascending order and you want to find the number 7 in the array. How would you do it? You would start by defining a helper method that takes the array, the target element, and the left and right boundaries of the search range as parameters. You would then call this method with the initial values of the parameters, which are the array, the target element, and the indices of the first and last elements of the array. The helper method would then perform the following steps:
- Check if the search range is valid, i.e., if the left boundary is less than or equal to the right boundary. If not, return -1 to indicate that the element is not found.
- Find the middle element of the search range by adding the left and right boundaries and dividing by 2.
- Compare the middle element with the target element. If they are equal, return the index of the element. If the middle element is less than the target element, discard the left half of the search range and call the helper method recursively with the updated left boundary. If the middle element is greater than the target element, discard the right half of the search range and call the helper method recursively with the updated right boundary.
Here is a visual representation of the recursive binary search algorithm:
Recursive binary search example
As you can see, the recursive binary search algorithm takes the same number of comparisons as the iterative binary search algorithm to find the number 7 in the array. However, the recursive binary search algorithm uses a different technique to achieve the same result.
How can you implement recursive binary search in Java? Here is a simple code snippet that shows how to perform recursive binary search on an array of integers:
// Recursive binary search helper method public static int recursiveBinarySearchHelper(int[] array, int target, int left, int right) { // Check if the search range is valid if (left > right) { // Return -1 if the element is not found return -1; } // Find the middle element of the search range int mid = (left + right) / 2; // Compare the middle element with the target element if (array[mid] == target) { // Return the index of the element if found return mid; } else if (array[mid] < target) { // Discard the left half of the search range and update the left boundary left = mid + 1; // Call the helper method recursively with the updated left boundary return recursiveBinarySearchHelper(array, target, left, right); } else { // Discard the right half of the search range and update the right boundary right = mid - 1; // Call the helper method recursively with the updated right boundary return recursiveBinarySearchHelper(array, target, left, right); } } // Recursive binary search method public static int recursiveBinarySearch(int[] array, int target) { // Define the left and right boundaries of the search range int left = 0; int right = array.length - 1; // Call the helper method with the initial values of the parameters return recursiveBinarySearchHelper(array, target, left, right); } // Main method public static void main(String[] args) { // Create a sorted array of numbers int[] numbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Define the target element int target = 7; // Call the recursive binary search method and print the result int result = recursiveBinarySearch(numbers, target); if (result == -1) { System.out.println("The element " + target + " is not in the array."); } else { System.out.println("The element " + target + " is found at index " + result + "."); } }
The output of the code is:
The element 7 is found at index 5.
What are the advantages and disadvantages of recursive binary search? Here are some points to consider:
- Advantages:
- Recursive binary search is more elegant and concise than iterative binary search, as it uses fewer variables and lines of code.
- Recursive binary search is easier to understand and debug, as it follows a clear logic and structure.
- Recursive binary search can be adapted to other problems that involve dividing and conquering, such as finding the maximum or minimum element in an array.
- Disadvantages:
- Recursive binary search is more memory intensive than iterative binary search, as it uses the call stack to store the recursive calls.
- Recursive binary search can cause stack overflow errors if the recursion depth is too high or the collection is too large.
- Recursive binary search can be slower than iterative binary search, as it involves function calls and returns that add to the execution time.
Recursive binary search is a variation of binary search that uses recursion to divide and conquer the collection and search for the target element. Recursive binary search has some advantages and disadvantages over iterative binary search, depending on the situation and preference. In the next section, you will learn how to use another variation of binary search called interpolation search and compare it with binary search.
4. Interpolation Search
Interpolation search is an improved version of binary search that works by estimating the position of the target element based on the distribution of the data in the collection. Interpolation search is also known as probe search, as it probes the collection at different positions to find the target element.
How does interpolation search work? Let’s see an example. Suppose you have a sorted array of 10 numbers and you want to find the number 7 in the array. How would you do it? You would start by calculating the position of the target element using the following formula:
position = low + (high – low) * (target – array[low]) / (array[high] – array[low])
This formula assumes that the data is uniformly distributed in the array, meaning that the difference between adjacent elements is constant. In this case, the formula gives the position as 6. You would then compare the element at position 6 with the target element. If they are equal, you have found the target element and you can stop the search. If they are not equal, you would update the low or high boundary depending on whether the target element is smaller or larger than the element at position 6. You would then repeat the process until you either find the target element or determine that it does not exist.
Here is a visual representation of the interpolation search algorithm:
Interpolation search example
As you can see, the interpolation search algorithm takes 2 comparisons to find the number 7 in the array. If the number 7 was not in the array, it would take 3 comparisons to determine that it does not exist.
How can you implement interpolation search in Java? Here is a simple code snippet that shows how to perform interpolation search on a sorted array of integers:
// Interpolation search method public static int interpolationSearch(int[] array, int target) { // Define the low and high boundaries of the search range int low = 0; int high = array.length - 1; // Loop until the target element is found or the search range is exhausted while (low <= high && target >= array[low] && target <= array[high]) { // Calculate the position of the target element using the formula int position = low + (high - low) * (target - array[low]) / (array[high] - array[low]); // Compare the element at the position with the target element if (array[position] == target) { // Return the index of the element if found return position; } else if (array[position] < target) { // Update the low boundary if the target element is larger low = position + 1; } else { // Update the high boundary if the target element is smaller high = position - 1; } } // Return -1 if the element is not found return -1; } // Main method public static void main(String[] args) { // Create a sorted array of numbers int[] numbers = {2, 4, 6, 7, 9, 11, 13, 15, 17, 19}; // Define the target element int target = 7; // Call the interpolation search method and print the result int result = interpolationSearch(numbers, target); if (result == -1) { System.out.println("The element " + target + " is not in the array."); } else { System.out.println("The element " + target + " is found at index " + result + "."); } }
The output of the code is:
The element 7 is found at index 3.
What are the advantages and disadvantages of interpolation search? Here are some points to consider:
- Advantages:
- Interpolation search is faster and more efficient than binary search for uniformly distributed data, as it reduces the number of comparisons by estimating the position of the target element.
- Interpolation search has a low time complexity of O(log log n) on average, where n is the number of elements in the collection.
- Interpolation search can work on any sorted collection, such as arrays, lists, or linked lists.
- Disadvantages:
- Interpolation search is not effective for data that is not uniformly distributed, as the position formula may give inaccurate results and increase the number of comparisons.
- Interpolation search has a high time complexity of O(n) in the worst case, where n is the number of elements in the collection.
- Interpolation search requires prior sorting of the collection, which may add extra time and space complexity.
Interpolation search is a smart and fast searching algorithm that can be useful for large and uniformly distributed collections. However, if the data is not uniformly distributed or the collection is not sorted, you might want to consider other searching algorithms, such as linear search or binary search. In the next section, you will learn how to compare these algorithms and choose the best one for your problem.
5. Comparison of Searching Algorithms
In the previous sections, you learned about three different searching algorithms in Java: linear search, binary search, and interpolation search. You also learned how to implement them in your code and what are their advantages and disadvantages. But how do you choose the best searching algorithm for your problem? How do you compare these algorithms and evaluate their performance and efficiency?
In this section, you will learn how to compare these searching algorithms based on some criteria, such as time complexity, space complexity, and suitability for different types of data. You will also learn how to use some tools and techniques to measure and analyze the performance of these algorithms in practice.
Let’s start by reviewing the time complexity of these algorithms. Time complexity is a measure of how fast an algorithm runs as the size of the input increases. It is usually expressed using the big O notation, which indicates the worst-case scenario of the algorithm. Here are the time complexities of the three searching algorithms:
- Linear search: O(n), where n is the number of elements in the collection.
- Binary search: O(log n), where n is the number of elements in the collection.
- Interpolation search: O(log log n) on average, where n is the number of elements in the collection. O(n) in the worst case.
As you can see, linear search has the highest time complexity, which means that it is the slowest algorithm among the three. Binary search has a lower time complexity, which means that it is faster than linear search. Interpolation search has the lowest time complexity on average, which means that it is the fastest algorithm among the three. However, interpolation search also has a high time complexity in the worst case, which means that it can be slower than binary search in some situations.
But time complexity is not the only factor that affects the performance of an algorithm. Another factor is space complexity, which is a measure of how much memory an algorithm uses as the size of the input increases. It is also usually expressed using the big O notation, which indicates the worst-case scenario of the algorithm. Here are the space complexities of the three searching algorithms:
- Linear search: O(1), which means that it uses a constant amount of memory regardless of the size of the input.
- Binary search: O(1) for the iterative version, which means that it uses a constant amount of memory regardless of the size of the input. O(log n) for the recursive version, which means that it uses a logarithmic amount of memory depending on the size of the input.
- Interpolation search: O(1), which means that it uses a constant amount of memory regardless of the size of the input.
As you can see, linear search and interpolation search have the same space complexity, which means that they use the same amount of memory. Binary search has a different space complexity depending on whether it is implemented iteratively or recursively. The iterative version uses the same amount of memory as linear search and interpolation search, while the recursive version uses more memory than the other two algorithms.
Another factor that affects the performance of an algorithm is the type and distribution of the data in the collection. Some algorithms work better on certain types of data than others. For example, binary search and interpolation search require the collection to be sorted in ascending or descending order, while linear search does not. Interpolation search also requires the data to be uniformly distributed, while linear search and binary search do not. Here are some examples of different types of data and how they affect the performance of the three searching algorithms:
- Sorted and uniformly distributed data: This is the best case scenario for interpolation search, as it can find the target element in a few comparisons by estimating its position accurately. Binary search is also efficient on this type of data, as it can find the target element in a logarithmic number of comparisons by dividing the collection into two halves. Linear search is the least efficient on this type of data, as it has to check every element in the collection until it finds the target element or reaches the end of the collection.
- Sorted and non-uniformly distributed data: This is the worst case scenario for interpolation search, as it can fail to estimate the position of the target element correctly and increase the number of comparisons. Binary search is still efficient on this type of data, as it does not depend on the distribution of the data and can find the target element in a logarithmic number of comparisons by dividing the collection into two halves. Linear search is still the least efficient on this type of data, as it has to check every element in the collection until it finds the target element or reaches the end of the collection.
- Unsorted and uniformly distributed data: This type of data is not suitable for binary search and interpolation search, as they require the collection to be sorted in order to work. Linear search is the only algorithm that can work on this type of data, as it does not depend on the order or distribution of the data and can find the target element by checking every element in the collection.
- Unsorted and non-uniformly distributed data: This type of data is also not suitable for binary search and interpolation search, as they require the collection to be sorted in order to work. Linear search is still the only algorithm that can work on this type of data, as it does not depend on the order or distribution of the data and can find the target element by checking every element in the collection.
As you can see, the type and distribution of the data in the collection can have a significant impact on the performance and efficiency of the searching algorithms. Therefore, it is important to choose the appropriate algorithm for your problem based on the characteristics of your data.
But how can you measure and analyze the performance of these algorithms in practice? One way is to use some tools and techniques that can help you test and compare the algorithms on different types of data and scenarios. Some of these tools and techniques are:
- Benchmarking: This is a method of measuring the running time and memory usage of an algorithm on a specific input or a set of inputs. You can use benchmarking tools, such as JMH, to perform benchmarking tests on your code and generate reports and statistics that can help you evaluate the performance of your algorithm.
- Profiling: This is a method of analyzing the behavior and characteristics of an algorithm during its execution. You can use profiling tools, such as VisualVM, to monitor and inspect your code and identify the bottlenecks and inefficiencies of your algorithm.
- Debugging: This is a method of finding and fixing the errors and bugs in your code. You can use debugging tools, such as Eclipse, to run and test your code and locate and correct the mistakes and problems in your algorithm.
By using these tools and techniques, you can improve the quality and performance of your code and ensure that your algorithm works correctly and efficiently.
In this section, you learned how to compare the three searching algorithms in Java based on some criteria, such as time complexity, space complexity, and suitability for different types of data. You also learned how to use some tools and techniques to measure and analyze the performance of these algorithms in practice. In the next and final section, you will learn how to conclude your blog and summarize the main points and takeaways of your tutorial.
6. Conclusion
Congratulations! You have reached the end of this blog on searching algorithms in Java. You have learned how to use three different searching algorithms to find a specific element or a set of elements in a collection of data. You have also learned how to compare these algorithms based on some criteria, such as time complexity, space complexity, and suitability for different types of data. You have also learned how to use some tools and techniques to measure and analyze the performance of these algorithms in practice.
Here are some of the main points and takeaways of this blog:
- Searching algorithms are methods that help you find a specific element or a set of elements in a collection of data.
- Linear search is the simplest and most basic searching algorithm that works by checking each element of the collection sequentially until it finds the target element or reaches the end of the collection.
- Binary search is a more efficient and faster searching algorithm that works by dividing the collection into two halves and comparing the target element with the middle element of each half. It then repeats this process on the half that contains the target element until it finds it or determines that it does not exist.
- Interpolation search is an improved version of binary search that works by estimating the position of the target element based on the distribution of the data in the collection. It then uses this position to narrow down the search range and find the target element faster.
- Time complexity is a measure of how fast an algorithm runs as the size of the input increases. It is usually expressed using the big O notation, which indicates the worst-case scenario of the algorithm.
- Space complexity is a measure of how much memory an algorithm uses as the size of the input increases. It is also usually expressed using the big O notation, which indicates the worst-case scenario of the algorithm.
- Type and distribution of data are factors that affect the performance and efficiency of an algorithm. Some algorithms work better on certain types of data than others.
- Benchmarking, profiling, and debugging are tools and techniques that can help you test and compare the algorithms on different types of data and scenarios.
We hope that you enjoyed this blog and learned something new and useful. Searching algorithms are important and fundamental concepts in data structures and algorithms, and they can help you solve various problems that involve finding, sorting, or filtering data in different scenarios. By understanding and implementing these algorithms in Java, you can improve your coding skills and enhance your performance and efficiency.
Thank you for reading this blog and happy coding!