Java中的线性和二进制搜索算法

99 阅读7分钟

Java中的线性和二进制搜索算法

在编程语言的世界里,数据结构和算法是所有工程师必须具备的解决问题的技能。在Java或其他语言中,当遇到未排序和已排序数组的问题时,就需要用到线性和二进制搜索。

在这篇文章中,我将分享在解决面试问题时利用这些方法的方法。

主要收获

最后,读者将了解以下概念。

  1. 什么是Java中的数据结构和算法。
  2. 何时以及如何使用线性搜索算法。
  3. 在排序的Java数组问题中二进制搜索的概念。
  4. 面试问题的解决方案。

什么是Java中的数据结构和算法

在计算机科学中,数据结构与数据的结构方式、存储方式、流动模式和引用方法有关。

在编程语言中,有许多结构化数据的方法。其中之一是数组或列表;这就是我们讨论的重点。

有许多方法可以引用通过数组存储的数据。

算法是为解决数据结构的问题而制定的方法或逐步规则。

例如,堆栈和队列是描述现实世界应用中数据通过数组或列表流动的算法。

堆栈使用的是后进先出的算法,这被称为LIFO

这意味着数据被堆叠在彼此的上面。因此,在调用时,最后一个将是第一个出现的。

然而,Queue使用先入先出的类比,也就是所谓的FIFO

把它想象成一个公共队列,第一个进来的人将是第一个被关注的人。

还有其他的数据结构和算法,如链表、哈希图等等。

然而,我们将研究存储在数组中的数据的二进制和线性搜索算法。

线性搜索算法

线性搜索可用于遍历数组问题,如搜索一个特定的元素,寻找一个最大或最小的元素等。

请注意,线性搜索算法主要是在数组,特别是没有经过升序或降序排序的情况下才有用。

使用线性搜索算法需要对整个数组进行循环,N 次。这意味着程序将以未知的次数跑完数组中的所有元素。

这种算法的时间复杂度应是O(N) 。时间复杂度说的是循环跑完数组的确切长度所需的时间。

从根本上说,线性搜索算法的空间复杂度在大多数情况下总是O(1) 。也就是说,常数为1,因为循环会运行一次,没有其他内存空间被消耗。

请注意,对于线性搜索算法来说,在给定的数组中不存在其他的数组循环。

让我们用线性搜索算法找到下面问题的答案。

编写一个程序来寻找未知长度的任何给定数组中的最大整数。

在这个问题中,我们必须做一个可重复使用的函数,它将接受一个数组作为参数。

下面是解决方案。

    public class MaxNum {
        public static void main(String[] args) {
            int[] arr = {33, -1, 0, 89, 9, 62, 8, 2, 97};
            int max = maxNum(arr);
            System.out.println(max);
        }

        static int maxNum(int[] arr){
            // returns -1 if length of array is zero
            if(arr.length == 0){
                return -1;
            }
            // making the first element constant for comparison
            int temp = arr[0];

            // loop running  through the entire array
            for (int i = 0; i < arr.length; i++) {

                // checking the specific element greater than constant
                if(arr[i] > temp) {
                    temp = arr[i]; // setting the max element to the constant
                }
            }
            // returning the max at the end
            return temp;

        }
    }

在上面的程序中,我们有一个maxNum ,该函数有一个参数,返回任何给定数组的最大元素。

调用上面的数组将向控制台打印97 ,作为提供的数组的最大整数。

Java中的二进制搜索

二进制搜索是一种搜索算法,用于解决整数排序数组的问题。

要利用这种算法,必须知道给定数组的顺序是升序还是降序。

为了利用二进制搜索算法,最好的方法是在每个时间点将数组分成两个空间。

这是通过数组的start,middle, 和end 索引实现的。

当循环运行时,根据问题的直觉,数组的左、中、右三面都得到了比较。

请注意,这个算法通过将给定的数组划分为两个空间来运行,直到它只剩下一个元素进行比较,即当开始、中间和结束索引都指向一个特定的元素。

此外,这种搜索算法的最佳情况被称为O(1) 。这意味着问题在第一次划分时就得到了解决。

每个二进制搜索算法的时间复杂度据说是O(logN) ,而这又对应于它的最坏情况。

这种情况是如何发生的呢?

第一次划分是(N) = N / 2^0,
第二次划分是(N / 2) = N / 2^1,
第三次划分是(N / 4) = N / 2^2,
.
.
.
最后一次划分,N / 2^k.

请注意,在最后一次除法时,将只剩下一个元素。因此,我们可以说N / 2^k = 1 ,其中k 是要进行的未知除数。

N / 2^k = 1 ,我们有N = 2^k

N = 2^k
,我们有k = logN / log2

忽略常数log2 ,则k = logN

现在让我们看几个问题来详细解释这些概念。

面试问题的解决方案

  1. 写一个程序,在一个数组中找到目标整数的上限。其中上限数字是大于或等于给定目标的最小数字。用升序数组类[2, 4, 6, 7, 9, 10, 16, 18, 21] ,测试你的算法,目标为11

下面是解决方案。

    public class CeilingSolutions {
        public static void main(String[] args) {
            // given array for testing
            int[] arr = {2, 4, 6, 7, 9, 10, 16, 18, 21};

            int target = 12;

            int ans = ceiling(arr, target);

            System.out.println(ans); // prints 16
        }
    //    return smallest number >= target
        static int ceiling(int[] ar, int target){
            int start = 0;
            int end = ar.length - 1;

            // while start index is not greater than end index
            while(start <= end){
                int mid = start + (end - start) / 2;

                // if target is not found in ascending array, return -1
                if(target > ar[ar.length - 1] ) {
                    return -1;
                }

                // if target is greater than element at mid, shift end index to element before mid
                if(target < ar[mid]){
                    end = mid -1;
                }
                // else if target is less than element at mid, shift start index to element after mid
                else if(target > ar[mid]){
                    start = mid + 1;
                }
                // else mid element is the answer, return mid
                else {
                    return mid;
                }
            }
            // case when it remains only one element --> worst case where start > end
            return ar[start];

        }
    }

解释。

在第一次划分中,给定的数组将被划分为两个空间,其中中间的元素将是9 ,索引4=(0+8)/2

当起始索引小于或等于结束索引时,将进行以下检查。

在第一个条件中,如果没有找到目标,它将返回减一。

现在,给定的目标11 大于中间元素,所以它将把起始索引转移到索引5,即4+1 。那么搜索空间就是从索引5到8,而中间元素将在索引6=(5+8)/2

同样,目标11 ,小于索引6的中间元素。因此,终点索引将转移到索引5 =6-1

这里的搜索空间仍然是一个元素,即10

然而,开始、中间和结束索引都指向同一个元素10 ;在索引5。

这被认为是最坏的情况,因为搜索算法在每次迭代时都通过划分空间来完成所有的空间。

但是目标11 ,大于索引5的元素,这仍然是搜索的唯一元素。

这将促使起始索引向前移动到索引6,通过这样做,已经违反了while循环的条件。

然后,循环将停止运行,返回语句返回起始索引的元素是大于11 的最小的整数。

最后,当函数被调用时,它返回16 作为数组中11 的最高整数。

  1. 找出目标8 在数组[40, 35, 15, 7, 6, 3, 1, 0, -3] 中的下限整数。

本题使用了二进制搜索的概念,因为所给的数组是按降序排列的。

我们将使用与上述问题解决方案相同的策略。唯一的区别是决定开始和结束索引的条件。

请注意,底限整数是指小于或等于目标的最大整数。

下面是问题的解决方案。

    public class FloorSolutions {
        public static void main(String[] args) {
            int[] givenArray = {40, 35, 15, 7, 6, 3, 1, 0, -3};

            int target  = 8;

            int ans = floor(givenArray, target);

            System.out.println(ans); // prints 7
        }


        static int floor(int[] arr, int target) {
            int start = 0;

            int end = arr.length - 1;

            while (start <= end) {

                int mid = start + (end - start) / 2;

                if (target > arr[mid]) {
                    end = mid - 1;
                } else if (target < arr[mid]) {
                    start = mid + 1;
                } else {
                    return mid;
                }
            }

            return arr[start];
        }
    }

结论

线性搜索和二进制搜索是处理未排序和已排序数组的搜索算法。

每当有问题需要用到其中任何一种时,都要参考这个概念。