跳至主要內容

3.4 二分法


定义

二分查找也称折半查找(Binary Search),是一种在有序数组中查找特定元素的算法。它的工作原理是首先确定数组的中间元素,然后将待查找的元素与中间元素进行比较。如果待查找元素等于中间元素,则查找成功;如果待查找元素小于中间元素,则在数组的前半部分继续进行二分查找;如果待查找元素大于中间元素,则在数组的后半部分继续进行二分查找。重复这个过程,直到找到目标元素或者确定目标元素不在数组中。

使用场景

二分查找法通常用于以下场景:

  1. 有序数组的查找:在已经排序好的数组中查找特定元素时,二分查找法能够以较快的速度找到目标元素。

  2. 搜索范围逐渐减小的情况:当搜索范围可以逐步缩小到一个较小的范围时,二分查找法非常有效。这种情况下,每次查找可以将搜索范围减半,因此算法的时间复杂度较低。

  3. 需要频繁查找的场景:如果需要对相同的有序数据集进行多次查找,二分查找法的优势会更加显著。虽然对数据集进行排序的初始成本较高,但一旦排序完成,后续的查找操作会更加高效。

  4. 时间要求严格的应用:在需要快速响应的应用场景中,如实时系统或大规模数据处理,二分查找法可以提供高效的搜索速度,满足实时性要求。

总之,二分查找法适用于对有序数据集进行快速搜索的场景,特别是对大型数据集和需要频繁查找的情况下,它能够提供较高的搜索效率。

时间复杂度

二分查找法的时间复杂度为 O(log n),其中 n 是数组的元素个数。相比于线性查找等一些其他搜索算法,二分查找法通常更加高效,特别是在大型数据集上。

代码实现

以下是一个简单的 Java 代码示例,实现了二分查找法:

public class BinarySearch {

    // 二分查找函数
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            // 如果目标值等于中间元素,则返回中间元素的索引
            if (arr[mid] == target) {
                return mid;
            }
            // 如果目标值小于中间元素,则在左半部分继续查找
            else if (target < arr[mid]) {
                right = mid - 1;
            }
            // 如果目标值大于中间元素,则在右半部分继续查找
            else {
                left = mid + 1;
            }
        }
        // 如果未找到目标元素,则返回 -1
        return -1;
    }

    public static void main(String[] args) {
        int[] arr = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
        int target = 12;
        int index = binarySearch(arr, target);
        if (index != -1) {
            System.out.println("目标元素 " + target + " 在数组中的索引为:" + index);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中。");
        }
    }
}

这段代码中,binarySearch 方法接受一个有序数组 arr 和目标元素 target 作为参数,在数组中查找目标元素的索引。如果找到目标元素,则返回其索引;如果未找到,则返回 -1。main 方法演示了如何调用 binarySearch 方法进行查找。

算法变种

二分查找算法有几种常见的变种,它们针对不同的应用场景做了一些优化或扩展:

  1. 递归实现

除了使用迭代方式实现二分查找外,也可以使用递归的方式实现。递归实现相对简洁,但可能会带来额外的函数调用开销。下面是一个使用递归实现的二分查找算法的 Java 代码示例:

public class RecursiveBinarySearch {

    // 递归实现的二分查找函数
    public static int binarySearch(int[] arr, int target, int left, int right) {
        if (left > right) {
            return -1; // 未找到目标元素,返回 -1
        }

        int mid = left + (right - left) / 2;

        if (arr[mid] == target) {
            return mid; // 找到目标元素,返回索引
        } else if (target < arr[mid]) {
            return binarySearch(arr, target, left, mid - 1); // 在左半部分继续递归查找
        } else {
            return binarySearch(arr, target, mid + 1, right); // 在右半部分继续递归查找
        }
    }

    public static void main(String[] args) {
        int[] arr = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
        int target = 12;
        int index = binarySearch(arr, target, 0, arr.length - 1);
        if (index != -1) {
            System.out.println("目标元素 " + target + " 在数组中的索引为:" + index);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中。");
        }
    }
}

这个代码示例中,binarySearch 方法通过递归实现二分查找。它接受一个有序数组 arr、目标元素 target,以及搜索范围的左右边界 leftright。在每次递归中,它计算数组中间元素的索引 mid,然后与目标元素进行比较。如果目标元素等于中间元素,则返回中间元素的索引;如果目标元素小于中间元素,则在左半部分继续递归查找;如果目标元素大于中间元素,则在右半部分继续递归查找。递归的基本情况是当搜索范围的左边界大于右边界时,表示未找到目标元素,返回 -1。

  1. 查找第一个等于给定值的元素

当有序数组中存在重复元素时,如果需要找到第一个等于给定值的元素,可以稍作修改。在找到等于给定值的元素时,不立即返回,而是继续向数组左边搜索,直到找到第一个等于给定值的元素。下面是 Java 代码示例:

public class FirstOccurrenceBinarySearch {

    // 查找第一个等于给定值的元素的索引
    public static int findFirstOccurrence(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        int result = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) {
                result = mid; // 记录当前找到的等于给定值的元素索引
                right = mid - 1; // 继续在左半部分查找第一个等于给定值的元素
            } else if (target < arr[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 2, 3, 4, 4, 4, 5, 6};
        int target = 4;
        int index = findFirstOccurrence(arr, target);
        if (index != -1) {
            System.out.println("第一个等于给定值的元素 " + target + " 在数组中的索引为:" + index);
        } else {
            System.out.println("数组中不存在等于给定值的元素 " + target);
        }
    }
}

在这个示例中,findFirstOccurrence 方法用于查找数组中第一个等于给定值的元素的索引。它采用了类似于标准二分查找的思想,但在找到等于给定值的元素时,不直接返回,而是继续在左半部分查找第一个等于给定值的元素。通过不断更新 result 变量,最终可以得到第一个等于给定值的元素的索引。

  1. 查找最后一个等于给定值的元素

与查找第一个等于给定值的元素类似,只是在找到等于给定值的元素时,继续向数组右边搜索,直到找到最后一个等于给定值的元素。下面是 Java 代码示例:

public class LastOccurrenceBinarySearch {

    // 查找最后一个等于给定值的元素的索引
    public static int findLastOccurrence(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        int result = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) {
                result = mid; // 记录当前找到的等于给定值的元素索引
                left = mid + 1; // 继续在右半部分查找最后一个等于给定值的元素
            } else if (target < arr[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 2, 3, 4, 4, 4, 5, 6};
        int target = 4;
        int index = findLastOccurrence(arr, target);
        if (index != -1) {
            System.out.println("最后一个等于给定值的元素 " + target + " 在数组中的索引为:" + index);
        } else {
            System.out.println("数组中不存在等于给定值的元素 " + target);
        }
    }
}

在这个示例中,findLastOccurrence 方法用于查找数组中最后一个等于给定值的元素的索引。它同样采用了二分查找的思想,在找到等于给定值的元素时,不直接返回,而是继续在右半部分查找最后一个等于给定值的元素。通过不断更新 result 变量,最终可以得到最后一个等于给定值的元素的索引。

  1. 查找第一个大于等于给定值的元素

当有序数组中不存在等于给定值的元素时,查找第一个大于等于给定值的元素。以下是 Java 代码示例:

public class FirstGreaterThanOrEqualBinarySearch {

    // 查找第一个大于等于给定值的元素的索引
    public static int findFirstGreaterThanOrEqual(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        int result = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] >= target) {
                result = mid; // 记录当前找到的大于等于给定值的元素索引
                right = mid - 1; // 继续在左半部分查找第一个大于等于给定值的元素
            } else {
                left = mid + 1;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 5, 5, 5, 7, 8, 9};
        int target = 6;
        int index = findFirstGreaterThanOrEqual(arr, target);
        if (index != -1) {
            System.out.println("第一个大于等于给定值的元素 " + target + " 在数组中的索引为:" + index);
        } else {
            System.out.println("数组中不存在大于等于给定值的元素 " + target);
        }
    }
}

在这个示例中,findFirstGreaterThanOrEqual 方法用于查找数组中第一个大于等于给定值的元素的索引。它同样采用了二分查找的思想,在找到大于等于给定值的元素时,不直接返回,而是继续在左半部分查找第一个大于等于给定值的元素。通过不断更新 result 变量,最终可以得到第一个大于等于给定值的元素的索引。

  1. 查找最后一个小于等于给定值的元素

当有序数组中不存在等于给定值的元素时,查找最后一个小于等于给定值的元素。以下是 Java 代码示例:

public class LastLessThanOrEqualBinarySearch {

    // 查找最后一个小于等于给定值的元素的索引
    public static int findLastLessThanOrEqual(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        int result = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] <= target) {
                result = mid; // 记录当前找到的小于等于给定值的元素索引
                left = mid + 1; // 继续在右半部分查找最后一个小于等于给定值的元素
            } else {
                right = mid - 1;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 5, 5, 5, 7, 8, 9};
        int target = 6;
        int index = findLastLessThanOrEqual(arr, target);
        if (index != -1) {
            System.out.println("最后一个小于等于给定值的元素 " + target + " 在数组中的索引为:" + index);
        } else {
            System.out.println("数组中不存在小于等于给定值的元素 " + target);
        }
    }
}

在这个示例中,findLastLessThanOrEqual 方法用于查找数组中最后一个小于等于给定值的元素的索引。它同样采用了二分查找的思想,在找到小于等于给定值的元素时,不直接返回,而是继续在右半部分查找最后一个小于等于给定值的元素。通过不断更新 result 变量,最终可以得到最后一个小于等于给定值的元素的索引。

参考

https://www.pdai.tech/md/algorithm/alg-core-devide-two.htmlopen in new window

上次编辑于: