--- comments: true --- # 12.2   Divide and conquer search strategy We have learned that search algorithms fall into two main categories. - **Brute-force search**: It is implemented by traversing the data structure, with a time complexity of $O(n)$. - **Adaptive search**: It utilizes a unique data organization form or prior information, and its time complexity can reach $O(\log n)$ or even $O(1)$. In fact, **search algorithms with a time complexity of $O(\log n)$ are usually based on the divide-and-conquer strategy**, such as binary search and trees. - Each step of binary search divides the problem (searching for a target element in an array) into a smaller problem (searching for the target element in half of the array), continuing until the array is empty or the target element is found. - Trees represent the divide-and-conquer idea, where in data structures like binary search trees, AVL trees, and heaps, the time complexity of various operations is $O(\log n)$. The divide-and-conquer strategy of binary search is as follows. - **The problem can be divided**: Binary search recursively divides the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element. - **Subproblems are independent**: In binary search, each round handles one subproblem, unaffected by other subproblems. - **The solutions of subproblems do not need to be merged**: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved. Divide-and-conquer can enhance search efficiency because brute-force search can only eliminate one option per round, **whereas divide-and-conquer can eliminate half of the options**. ### 1.   Implementing binary search based on divide-and-conquer In previous chapters, binary search was implemented based on iteration. Now, we implement it based on divide-and-conquer (recursion). !!! question Given an ordered array `nums` of length $n$, where all elements are unique, please find the element `target`. From a divide-and-conquer perspective, we denote the subproblem corresponding to the search interval $[i, j]$ as $f(i, j)$. Starting from the original problem $f(0, n-1)$, perform the binary search through the following steps. 1. Calculate the midpoint $m$ of the search interval $[i, j]$, and use it to eliminate half of the search interval. 2. Recursively solve the subproblem reduced by half in size, which could be $f(i, m-1)$ or $f(m+1, j)$. 3. Repeat steps `1.` and `2.`, until `target` is found or the interval is empty and returns. The diagram below shows the divide-and-conquer process of binary search for element $6$ in an array. ![The divide-and-conquer process of binary search](binary_search_recur.assets/binary_search_recur.png){ class="animation-figure" }

Figure 12-4   The divide-and-conquer process of binary search

In the implementation code, we declare a recursive function `dfs()` to solve the problem $f(i, j)$: === "Python" ```python title="binary_search_recur.py" def dfs(nums: list[int], target: int, i: int, j: int) -> int: """二分查找:问题 f(i, j)""" # 若区间为空,代表无目标元素,则返回 -1 if i > j: return -1 # 计算中点索引 m m = (i + j) // 2 if nums[m] < target: # 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j) elif nums[m] > target: # 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1) else: # 找到目标元素,返回其索引 return m def binary_search(nums: list[int], target: int) -> int: """二分查找""" n = len(nums) # 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1) ``` === "C++" ```cpp title="binary_search_recur.cpp" /* 二分查找:问题 f(i, j) */ int dfs(vector &nums, int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) / 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int binarySearch(vector &nums, int target) { int n = nums.size(); // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } ``` === "Java" ```java title="binary_search_recur.java" /* 二分查找:问题 f(i, j) */ int dfs(int[] nums, int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) / 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int binarySearch(int[] nums, int target) { int n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } ``` === "C#" ```csharp title="binary_search_recur.cs" /* 二分查找:问题 f(i, j) */ int DFS(int[] nums, int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) / 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return DFS(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return DFS(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int BinarySearch(int[] nums, int target) { int n = nums.Length; // 求解问题 f(0, n-1) return DFS(nums, target, 0, n - 1); } ``` === "Go" ```go title="binary_search_recur.go" /* 二分查找:问题 f(i, j) */ func dfs(nums []int, target, i, j int) int { // 如果区间为空,代表没有目标元素,则返回 -1 if i > j { return -1 } // 计算索引中点 m := i + ((j - i) >> 1) //判断中点与目标元素大小 if nums[m] < target { // 小于则递归右半数组 // 递归子问题 f(m+1, j) return dfs(nums, target, m+1, j) } else if nums[m] > target { // 小于则递归左半数组 // 递归子问题 f(i, m-1) return dfs(nums, target, i, m-1) } else { // 找到目标元素,返回其索引 return m } } /* 二分查找 */ func binarySearch(nums []int, target int) int { n := len(nums) return dfs(nums, target, 0, n-1) } ``` === "Swift" ```swift title="binary_search_recur.swift" /* 二分查找:问题 f(i, j) */ func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { // 若区间为空,代表无目标元素,则返回 -1 if i > j { return -1 } // 计算中点索引 m let m = (i + j) / 2 if nums[m] < target { // 递归子问题 f(m+1, j) return dfs(nums: nums, target: target, i: m + 1, j: j) } else if nums[m] > target { // 递归子问题 f(i, m-1) return dfs(nums: nums, target: target, i: i, j: m - 1) } else { // 找到目标元素,返回其索引 return m } } /* 二分查找 */ func binarySearch(nums: [Int], target: Int) -> Int { // 求解问题 f(0, n-1) dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) } ``` === "JS" ```javascript title="binary_search_recur.js" /* 二分查找:问题 f(i, j) */ function dfs(nums, target, i, j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m const m = i + ((j - i) >> 1); if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ function binarySearch(nums, target) { const n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } ``` === "TS" ```typescript title="binary_search_recur.ts" /* 二分查找:问题 f(i, j) */ function dfs(nums: number[], target: number, i: number, j: number): number { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m const m = i + ((j - i) >> 1); if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ function binarySearch(nums: number[], target: number): number { const n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } ``` === "Dart" ```dart title="binary_search_recur.dart" /* 二分查找:问题 f(i, j) */ int dfs(List nums, int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) ~/ 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int binarySearch(List nums, int target) { int n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } ``` === "Rust" ```rust title="binary_search_recur.rs" /* 二分查找:问题 f(i, j) */ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { // 若区间为空,代表无目标元素,则返回 -1 if i > j { return -1; } let m: i32 = (i + j) / 2; if nums[m as usize] < target { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if nums[m as usize] > target { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ fn binary_search(nums: &[i32], target: i32) -> i32 { let n = nums.len() as i32; // 求解问题 f(0, n-1) dfs(nums, target, 0, n - 1) } ``` === "C" ```c title="binary_search_recur.c" /* 二分查找:问题 f(i, j) */ int dfs(int nums[], int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) / 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int binarySearch(int nums[], int target, int numsSize) { int n = numsSize; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } ``` === "Kotlin" ```kotlin title="binary_search_recur.kt" /* 二分查找:问题 f(i, j) */ fun dfs( nums: IntArray, target: Int, i: Int, j: Int ): Int { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1 } // 计算中点索引 m val m = (i + j) / 2 return if (nums[m] < target) { // 递归子问题 f(m+1, j) dfs(nums, target, m + 1, j) } else if (nums[m] > target) { // 递归子问题 f(i, m-1) dfs(nums, target, i, m - 1) } else { // 找到目标元素,返回其索引 m } } /* 二分查找 */ fun binarySearch(nums: IntArray, target: Int): Int { val n = nums.size // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1) } ``` === "Ruby" ```ruby title="binary_search_recur.rb" [class]{}-[func]{dfs} [class]{}-[func]{binary_search} ``` === "Zig" ```zig title="binary_search_recur.zig" [class]{}-[func]{dfs} [class]{}-[func]{binarySearch} ``` ??? pythontutor "Code Visualization"
Full Screen >