Refactor the articles related to searching algorithm. Add the chapter of binary search. Add the section of searching algorithm revisited. (#464)
@ -8,8 +8,7 @@ include_directories(./include)
 | 
				
			|||||||
add_subdirectory(include)
 | 
					add_subdirectory(include)
 | 
				
			||||||
add_subdirectory(chapter_computational_complexity)
 | 
					add_subdirectory(chapter_computational_complexity)
 | 
				
			||||||
add_subdirectory(chapter_array_and_linkedlist)
 | 
					add_subdirectory(chapter_array_and_linkedlist)
 | 
				
			||||||
add_subdirectory(chapter_sorting)
 | 
					 | 
				
			||||||
add_subdirectory(chapter_tree)
 | 
					 | 
				
			||||||
add_subdirectory(chapter_stack_and_queue)
 | 
					add_subdirectory(chapter_stack_and_queue)
 | 
				
			||||||
 | 
					add_subdirectory(chapter_binary_search)
 | 
				
			||||||
add_subdirectory(chapter_heap)
 | 
					add_subdirectory(chapter_heap)
 | 
				
			||||||
add_subdirectory(chapter_searching)
 | 
					add_subdirectory(chapter_searching)
 | 
				
			||||||
							
								
								
									
										1
									
								
								codes/c/chapter_binary_search/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					add_executable(linear_search linear_search.c)
 | 
				
			||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
add_executable(time_complexity time_complexity.c)
 | 
					add_executable(time_complexity time_complexity.c)
 | 
				
			||||||
add_executable(worst_best_time_complexity worst_best_time_complexity.c)
 | 
					add_executable(worst_best_time_complexity worst_best_time_complexity.c)
 | 
				
			||||||
add_executable(leetcode_two_sum leetcode_two_sum.c)
 | 
					 | 
				
			||||||
add_executable(space_complexity space_complexity.c)
 | 
					add_executable(space_complexity space_complexity.c)
 | 
				
			||||||
@ -1,3 +1,2 @@
 | 
				
			|||||||
add_executable(binary_search binary_search.c)
 | 
					add_executable(binary_search binary_search.c)
 | 
				
			||||||
add_executable(linear_search linear_search.c)
 | 
					add_executable(leetcode_two_sum leetcode_two_sum.c)
 | 
				
			||||||
add_executable(hashing_search hashing_search.c)
 | 
					 | 
				
			||||||
@ -8,6 +8,7 @@ include_directories(./include)
 | 
				
			|||||||
add_subdirectory(chapter_computational_complexity)
 | 
					add_subdirectory(chapter_computational_complexity)
 | 
				
			||||||
add_subdirectory(chapter_array_and_linkedlist)
 | 
					add_subdirectory(chapter_array_and_linkedlist)
 | 
				
			||||||
add_subdirectory(chapter_stack_and_queue)
 | 
					add_subdirectory(chapter_stack_and_queue)
 | 
				
			||||||
 | 
					add_subdirectory(chapter_binary_search)
 | 
				
			||||||
add_subdirectory(chapter_hashing)
 | 
					add_subdirectory(chapter_hashing)
 | 
				
			||||||
add_subdirectory(chapter_tree)
 | 
					add_subdirectory(chapter_tree)
 | 
				
			||||||
add_subdirectory(chapter_heap)
 | 
					add_subdirectory(chapter_heap)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								codes/cpp/chapter_binary_search/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					add_executable(binary_search binary_search.cpp)
 | 
				
			||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
add_executable(leetcode_two_sum leetcode_two_sum.cpp)
 | 
					 | 
				
			||||||
add_executable(space_complexity space_complexity.cpp)
 | 
					add_executable(space_complexity space_complexity.cpp)
 | 
				
			||||||
add_executable(time_complexity time_complexity.cpp)
 | 
					add_executable(time_complexity time_complexity.cpp)
 | 
				
			||||||
add_executable(worst_best_time_complexity worst_best_time_complexity.cpp)
 | 
					add_executable(worst_best_time_complexity worst_best_time_complexity.cpp)
 | 
				
			||||||
@ -1,3 +1,3 @@
 | 
				
			|||||||
add_executable(binary_search binary_search.cpp)
 | 
					 | 
				
			||||||
add_executable(hashing_search hashing_search.cpp)
 | 
					add_executable(hashing_search hashing_search.cpp)
 | 
				
			||||||
 | 
					add_executable(leetcode_two_sum leetcode_two_sum.cpp)
 | 
				
			||||||
add_executable(linear_search linear_search.cpp)
 | 
					add_executable(linear_search linear_search.cpp)
 | 
				
			||||||
@ -6,7 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using NUnit.Framework;
 | 
					using NUnit.Framework;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace hello_algo.chapter_searching;
 | 
					namespace hello_algo.chapter_binary_search;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class binary_search
 | 
					public class binary_search
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -6,7 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using NUnit.Framework;
 | 
					using NUnit.Framework;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace hello_algo.chapter_computational_complexity;
 | 
					namespace hello_algo.chapter_searching;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class leetcode_two_sum
 | 
					public class leetcode_two_sum
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
// Created Time: 2022-12-05
 | 
					// Created Time: 2022-12-05
 | 
				
			||||||
// Author: Slone123c (274325721@qq.com)
 | 
					// Author: Slone123c (274325721@qq.com)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package chapter_searching
 | 
					package chapter_binary_search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 二分查找(双闭区间) */
 | 
					/* 二分查找(双闭区间) */
 | 
				
			||||||
func binarySearch(nums []int, target int) int {
 | 
					func binarySearch(nums []int, target int) int {
 | 
				
			||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
// Created Time: 2022-12-05
 | 
					// Created Time: 2022-12-05
 | 
				
			||||||
// Author: Slone123c (274325721@qq.com)
 | 
					// Author: Slone123c (274325721@qq.com)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package chapter_searching
 | 
					package chapter_binary_search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
// Created Time: 2022-11-25
 | 
					// Created Time: 2022-11-25
 | 
				
			||||||
// Author: reanon (793584285@qq.com)
 | 
					// Author: reanon (793584285@qq.com)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package chapter_computational_complexity
 | 
					package chapter_searching
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 方法一:暴力枚举 */
 | 
					/* 方法一:暴力枚举 */
 | 
				
			||||||
func twoSumBruteForce(nums []int, target int) []int {
 | 
					func twoSumBruteForce(nums []int, target int) []int {
 | 
				
			||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
// Created Time: 2022-11-25
 | 
					// Created Time: 2022-11-25
 | 
				
			||||||
// Author: reanon (793584285@qq.com)
 | 
					// Author: reanon (793584285@qq.com)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package chapter_computational_complexity
 | 
					package chapter_searching
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@ -4,6 +4,8 @@
 | 
				
			|||||||
 * Author: Krahets (krahets@163.com)
 | 
					 * Author: Krahets (krahets@163.com)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package chapter_backtracking;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import include.*;
 | 
					import include.*;
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,8 @@
 | 
				
			|||||||
 * Author: Krahets (krahets@163.com)
 | 
					 * Author: Krahets (krahets@163.com)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package chapter_backtracking;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import include.*;
 | 
					import include.*;
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,8 @@
 | 
				
			|||||||
 * Author: Krahets (krahets@163.com)
 | 
					 * Author: Krahets (krahets@163.com)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package chapter_backtracking;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import include.*;
 | 
					import include.*;
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,8 @@
 | 
				
			|||||||
 * Author: Krahets (krahets@163.com)
 | 
					 * Author: Krahets (krahets@163.com)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package chapter_backtracking;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import include.*;
 | 
					import include.*;
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
 * Author: Krahets (krahets@163.com)
 | 
					 * Author: Krahets (krahets@163.com)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package chapter_searching;
 | 
					package chapter_binary_search;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class binary_search {
 | 
					public class binary_search {
 | 
				
			||||||
    /* 二分查找(双闭区间) */
 | 
					    /* 二分查找(双闭区间) */
 | 
				
			||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
 * Author: Krahets (krahets@163.com)
 | 
					 * Author: Krahets (krahets@163.com)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package chapter_computational_complexity;
 | 
					package chapter_searching;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -62,4 +62,3 @@ pub fn main() !void {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    _ = try std.io.getStdIn().reader().readByte();
 | 
					    _ = try std.io.getStdIn().reader().readByte();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB  | 
| 
		 Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB  | 
| 
		 Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB  | 
| 
		 Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB  | 
| 
		 Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB  | 
| 
		 Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB  | 
| 
		 Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB  | 
@ -2,8 +2,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
「二分查找 Binary Search」利用数据的有序性,通过每轮减少一半搜索范围来定位目标元素。
 | 
					「二分查找 Binary Search」利用数据的有序性,通过每轮减少一半搜索范围来定位目标元素。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 算法实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
给定一个长度为 $n$ 的有序数组 `nums` ,元素按从小到大的顺序排列。数组索引的取值范围为:
 | 
					给定一个长度为 $n$ 的有序数组 `nums` ,元素按从小到大的顺序排列。数组索引的取值范围为:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$$
 | 
					$$
 | 
				
			||||||
@ -12,10 +10,10 @@ $$
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
我们通常使用以下两种方法来表示这个取值范围:
 | 
					我们通常使用以下两种方法来表示这个取值范围:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. **双闭区间 $[0, n-1]$** ,即两个边界都包含自身;在此方法下,区间 $[0, 0]$ 仍包含 $1$ 个元素;
 | 
					1. **双闭区间 $[0, n-1]$** ,即两个边界都包含自身;在此方法下,区间 $[i, i]$ 仍包含 $1$ 个元素;
 | 
				
			||||||
2. **左闭右开 $[0, n)$** ,即左边界包含自身、右边界不包含自身;在此方法下,区间 $[0, 0)$ 不包含元素;
 | 
					2. **左闭右开 $[0, n)$** ,即左边界包含自身、右边界不包含自身;在此方法下,区间 $[i, i)$ 不包含元素;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### “双闭区间”实现
 | 
					## 双闭区间实现
 | 
				
			||||||
 | 
					
 | 
				
			||||||
首先,我们采用“双闭区间”表示法,在数组 `nums` 中查找目标元素 `target` 的对应索引。
 | 
					首先,我们采用“双闭区间”表示法,在数组 `nums` 中查找目标元素 `target` 的对应索引。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -102,88 +100,7 @@ $$
 | 
				
			|||||||
    [class]{}-[func]{binarySearch}
 | 
					    [class]{}-[func]{binarySearch}
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### “左闭右开”实现
 | 
					需要注意的是,**当数组长度非常大时,加法 $i + j$ 的结果可能会超出 `int` 类型的取值范围**。在这种情况下,我们需要采用一种更安全的计算中点的方法。
 | 
				
			||||||
 | 
					 | 
				
			||||||
此外,我们也可以采用“左闭右开”的表示法,编写具有相同功能的二分查找代码。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Java"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```java title="binary_search.java"
 | 
					 | 
				
			||||||
    [class]{binary_search}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C++"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```cpp title="binary_search.cpp"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Python"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```python title="binary_search.py"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binary_search1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Go"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```go title="binary_search.go"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "JavaScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```javascript title="binary_search.js"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "TypeScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```typescript title="binary_search.ts"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```c title="binary_search.c"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C#"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```csharp title="binary_search.cs"
 | 
					 | 
				
			||||||
    [class]{binary_search}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Swift"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```swift title="binary_search.swift"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Zig"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```zig title="binary_search.zig"
 | 
					 | 
				
			||||||
    [class]{}-[func]{binarySearch1}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 两种表示对比
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
对比这两种代码写法,我们可以发现以下不同点:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="center-table" markdown>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 表示方法            | 初始化指针          | 缩小区间                  | 循环终止条件 |
 | 
					 | 
				
			||||||
| ------------------- | ------------------- | ------------------------- | ------------ |
 | 
					 | 
				
			||||||
| 双闭区间 $[0, n-1]$ | $i = 0$ , $j = n-1$ | $i = m + 1$ , $j = m - 1$ | $i > j$      |
 | 
					 | 
				
			||||||
| 左闭右开 $[0, n)$   | $i = 0$ , $j = n$   | $i = m + 1$ , $j = m$     | $i = j$      |
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
在“双闭区间”表示法中,由于对左右两边界的定义相同,因此缩小区间的 $i$ 和 $j$ 的处理方法也是对称的,这样更不容易出错。因此,**建议采用“双闭区间”的写法**。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 大数越界处理
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
当数组长度非常大时,加法 $i + j$ 的结果可能会超出 `int` 类型的取值范围。在这种情况下,我们需要采用一种更安全的计算中点的方法。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
=== "Java"
 | 
					=== "Java"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -267,6 +184,83 @@ $$
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 左闭右开实现
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					我们可以采用“左闭右开”的表示法,编写具有相同功能的二分查找代码。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "Java"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```java title="binary_search.java"
 | 
				
			||||||
 | 
					    [class]{binary_search}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "C++"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```cpp title="binary_search.cpp"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "Python"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```python title="binary_search.py"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binary_search1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "Go"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go title="binary_search.go"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "JavaScript"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```javascript title="binary_search.js"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "TypeScript"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```typescript title="binary_search.ts"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "C"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```c title="binary_search.c"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "C#"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```csharp title="binary_search.cs"
 | 
				
			||||||
 | 
					    [class]{binary_search}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "Swift"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```swift title="binary_search.swift"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== "Zig"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```zig title="binary_search.zig"
 | 
				
			||||||
 | 
					    [class]{}-[func]{binarySearch1}
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					对比这两种代码写法,我们可以发现以下不同点:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="center-table" markdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 表示方法            | 初始化指针          | 缩小区间                  | 循环终止条件 |
 | 
				
			||||||
 | 
					| ------------------- | ------------------- | ------------------------- | ------------ |
 | 
				
			||||||
 | 
					| 双闭区间 $[0, n-1]$ | $i = 0$ , $j = n-1$ | $i = m + 1$ , $j = m - 1$ | $i > j$      |
 | 
				
			||||||
 | 
					| 左闭右开 $[0, n)$   | $i = 0$ , $j = n$   | $i = m + 1$ , $j = m$     | $i = j$      |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					在“双闭区间”表示法中,由于对左右两边界的定义相同,因此缩小区间的 $i$ 和 $j$ 的处理方法也是对称的,这样更不容易出错。因此,**建议采用“双闭区间”的写法**。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 复杂度分析
 | 
					## 复杂度分析
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**时间复杂度 $O(\log n)$** :其中 $n$ 为数组长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。
 | 
					**时间复杂度 $O(\log n)$** :其中 $n$ 为数组长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。
 | 
				
			||||||
@ -961,3 +961,11 @@ $$
 | 
				
			|||||||
例如“归并排序”算法,输入长度为 $n$ 的数组,每轮递归将数组从中点划分为两半,形成高度为 $\log n$ 的递归树,使用 $O(\log n)$ 栈帧空间。
 | 
					例如“归并排序”算法,输入长度为 $n$ 的数组,每轮递归将数组从中点划分为两半,形成高度为 $\log n$ 的递归树,使用 $O(\log n)$ 栈帧空间。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
再例如“数字转化为字符串”,输入任意正整数 $n$ ,它的位数为 $\log_{10} n$ ,即对应字符串长度为 $\log_{10} n$ ,因此空间复杂度为 $O(\log_{10} n) = O(\log n)$ 。
 | 
					再例如“数字转化为字符串”,输入任意正整数 $n$ ,它的位数为 $\log_{10} n$ ,即对应字符串长度为 $\log_{10} n$ ,因此空间复杂度为 $O(\log_{10} n) = O(\log n)$ 。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 权衡时间与空间
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					理想情况下,我们希望算法的时间复杂度和空间复杂度都能达到最优。然而在实际情况中,同时优化时间复杂度和空间复杂度通常是非常困难的。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**降低时间复杂度通常需要以提升空间复杂度为代价,反之亦然**。我们将牺牲内存空间来提升算法运行速度的思路称为“以空间换时间”;反之,则称为“以时间换空间”。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					选择哪种思路取决于我们更看重哪个方面。在大多数情况下,时间比空间更宝贵,因此以空间换时间通常是更常用的策略。当然,在数据量很大的情况下,控制空间复杂度也是非常重要的。
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,12 @@
 | 
				
			|||||||
# 小结
 | 
					# 小结
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 算法效率评估
 | 
					**算法效率评估**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 时间效率和空间效率是评价算法性能的两个关键维度。
 | 
					- 时间效率和空间效率是评价算法性能的两个关键维度。
 | 
				
			||||||
- 我们可以通过实际测试来评估算法效率,但难以消除测试环境的影响,且会耗费大量计算资源。
 | 
					- 我们可以通过实际测试来评估算法效率,但难以消除测试环境的影响,且会耗费大量计算资源。
 | 
				
			||||||
- 复杂度分析可以克服实际测试的弊端,分析结果适用于所有运行平台,并且能够揭示算法在不同数据规模下的效率。
 | 
					- 复杂度分析可以克服实际测试的弊端,分析结果适用于所有运行平台,并且能够揭示算法在不同数据规模下的效率。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 时间复杂度
 | 
					**时间复杂度**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 时间复杂度用于衡量算法运行时间随数据量增长的趋势,可以有效评估算法效率,但在某些情况下可能失效,如在输入数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣。
 | 
					- 时间复杂度用于衡量算法运行时间随数据量增长的趋势,可以有效评估算法效率,但在某些情况下可能失效,如在输入数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣。
 | 
				
			||||||
- 最差时间复杂度使用大 $O$ 符号表示,即函数渐近上界,反映当 $n$ 趋向正无穷时,$T(n)$ 的增长级别。
 | 
					- 最差时间复杂度使用大 $O$ 符号表示,即函数渐近上界,反映当 $n$ 趋向正无穷时,$T(n)$ 的增长级别。
 | 
				
			||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
- 某些算法的时间复杂度非固定,而是与输入数据的分布有关。时间复杂度分为最差、最佳、平均时间复杂度,最佳时间复杂度几乎不用,因为输入数据一般需要满足严格条件才能达到最佳情况。
 | 
					- 某些算法的时间复杂度非固定,而是与输入数据的分布有关。时间复杂度分为最差、最佳、平均时间复杂度,最佳时间复杂度几乎不用,因为输入数据一般需要满足严格条件才能达到最佳情况。
 | 
				
			||||||
- 平均时间复杂度反映算法在随机数据输入下的运行效率,最接近实际应用中的算法性能。计算平均时间复杂度需要统计输入数据分布以及综合后的数学期望。
 | 
					- 平均时间复杂度反映算法在随机数据输入下的运行效率,最接近实际应用中的算法性能。计算平均时间复杂度需要统计输入数据分布以及综合后的数学期望。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 空间复杂度
 | 
					**空间复杂度**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 类似于时间复杂度,空间复杂度用于衡量算法占用空间随数据量增长的趋势。
 | 
					- 类似于时间复杂度,空间复杂度用于衡量算法占用空间随数据量增长的趋势。
 | 
				
			||||||
- 算法运行过程中的相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间通常仅在递归函数中影响空间复杂度。
 | 
					- 算法运行过程中的相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间通常仅在递归函数中影响空间复杂度。
 | 
				
			||||||
 | 
				
			|||||||
@ -6,24 +6,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 哈希表效率
 | 
					除哈希表外,我们还可以使用数组或链表实现查询功能,各项操作的时间复杂度如下表所示。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
除哈希表外,还可以使用以下数据结构来实现上述查询功能:
 | 
					在哈希表中增删查改的时间复杂度都是 $O(1)$ ,全面胜出!因此,哈希表常用于对查找效率要求较高的场景。
 | 
				
			||||||
 | 
					 | 
				
			||||||
1. **无序数组**:每个元素为  `[学号, 姓名]` ;
 | 
					 | 
				
			||||||
2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序;
 | 
					 | 
				
			||||||
3. **链表**:每个节点的值为 `[学号, 姓名]` ;
 | 
					 | 
				
			||||||
4. **二叉搜索树**:每个节点的值为 `[学号, 姓名]` ,根据学号大小来构建树;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
各项操作的时间复杂度如下表所示(详解可见[二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/))。无论是查找元素还是增删元素,哈希表的时间复杂度都是 $O(1)$ ,全面胜出!
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="center-table" markdown>
 | 
					<div class="center-table" markdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
|          | 无序数组 | 有序数组    | 链表   | 二叉搜索树  | 哈希表 |
 | 
					|          | 数组   | 链表   | 哈希表 |
 | 
				
			||||||
| -------- | -------- | ----------- | ------ | ----------- | ------ |
 | 
					| -------- | ------ | ------ | ------ |
 | 
				
			||||||
| 查找元素 | $O(n)$   | $O(\log n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ |
 | 
					| 查找元素 | $O(n)$ | $O(n)$ | $O(1)$ |
 | 
				
			||||||
| 插入元素 | $O(1)$   | $O(n)$      | $O(1)$ | $O(\log n)$ | $O(1)$ |
 | 
					| 插入元素 | $O(1)$ | $O(1)$ | $O(1)$ |
 | 
				
			||||||
| 删除元素 | $O(n)$   | $O(n)$      | $O(n)$ | $O(\log n)$ | $O(1)$ |
 | 
					| 删除元素 | $O(n)$ | $O(n)$ | $O(1)$ |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 68 KiB  | 
@ -1,152 +0,0 @@
 | 
				
			|||||||
# 哈希查找
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
「哈希查找 Hash Searching」通过使用哈希表来存储所需的键值对,从而可在 $O(1)$ 时间内完成“键 $\rightarrow$ 值”的查找操作。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
与线性查找相比,哈希查找通过利用额外空间来提高效率,体现了“以空间换时间”的算法思想。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 算法实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
例如,若我们想要在给定数组中找到目标元素 `target` 的索引,则可以使用哈希查找来实现。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Java"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```java title="hashing_search.java"
 | 
					 | 
				
			||||||
    [class]{hashing_search}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C++"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```cpp title="hashing_search.cpp"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Python"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```python title="hashing_search.py"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashing_search_array}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Go"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```go title="hashing_search.go"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "JavaScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```javascript title="hashing_search.js"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "TypeScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```typescript title="hashing_search.ts"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```c title="hashing_search.c"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C#"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```csharp title="hashing_search.cs"
 | 
					 | 
				
			||||||
    [class]{hashing_search}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Swift"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```swift title="hashing_search.swift"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Zig"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```zig title="hashing_search.zig"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
同样,若要根据目标节点值 target 查找对应的链表节点对象,也可以采用哈希查找方法。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Java"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```java title="hashing_search.java"
 | 
					 | 
				
			||||||
    [class]{hashing_search}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C++"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```cpp title="hashing_search.cpp"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Python"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```python title="hashing_search.py"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashing_search_linkedlist}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Go"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```go title="hashing_search.go"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "JavaScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```javascript title="hashing_search.js"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "TypeScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```typescript title="hashing_search.ts"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```c title="hashing_search.c"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C#"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```csharp title="hashing_search.cs"
 | 
					 | 
				
			||||||
    [class]{hashing_search}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Swift"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```swift title="hashing_search.swift"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Zig"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```zig title="hashing_search.zig"
 | 
					 | 
				
			||||||
    [class]{}-[func]{hashingSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 复杂度分析
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**时间复杂度 $O(1)$** :哈希表的查找操作使用 $O(1)$ 时间。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**空间复杂度 $O(n)$** :其中 $n$ 是数组或链表的长度。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 优点与局限性
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
哈希查找的性能表现相当优秀,查找、插入、删除操作的平均时间复杂度均为 $O(1)$ 。尽管如此,哈希查找仍然存在一些问题:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- 辅助哈希表需要占用 $O(n)$ 的额外空间,意味着需要预留更多的计算机内存;
 | 
					 | 
				
			||||||
- 构建和维护哈希表需要时间,因此哈希查找不适用于高频增删、低频查找的场景;
 | 
					 | 
				
			||||||
- 当哈希冲突严重时,哈希表可能退化为链表,导致时间复杂度劣化至 $O(n)$ ;
 | 
					 | 
				
			||||||
- 当数据量较小时,线性查找可能比哈希查找更快。这是因为计算哈希函数可能比遍历一个小型数组更慢;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
因此,在实际应用中,我们需要根据具体情况灵活选择解决方案。
 | 
					 | 
				
			||||||
@ -1,143 +0,0 @@
 | 
				
			|||||||
# 线性查找
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
「线性查找 Linear Search」是一种简单的查找方法,其从数据结构的一端开始,逐个访问每个元素,直至另一端为止。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 算法实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
例如,若我们想要在数组 `nums` 中查找目标元素 `target` 的对应索引,可以采用线性查找方法。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Java"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```java title="linear_search.java"
 | 
					 | 
				
			||||||
    [class]{linear_search}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C++"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```cpp title="linear_search.cpp"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Python"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```python title="linear_search.py"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linear_search_array}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Go"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```go title="linear_search.go"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "JavaScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```javascript title="linear_search.js"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "TypeScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```typescript title="linear_search.ts"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```c title="linear_search.c"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C#"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```csharp title="linear_search.cs"
 | 
					 | 
				
			||||||
    [class]{linear_search}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Swift"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```swift title="linear_search.swift"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Zig"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```zig title="linear_search.zig"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchArray}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
另一个例子,若需要在链表中查找给定目标节点值 `target` 并返回该节点对象,同样可以使用线性查找。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Java"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```java title="linear_search.java"
 | 
					 | 
				
			||||||
    [class]{linear_search}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C++"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```cpp title="linear_search.cpp"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Python"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```python title="linear_search.py"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linear_search_linkedlist}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Go"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```go title="linear_search.go"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "JavaScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```javascript title="linear_search.js"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "TypeScript"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```typescript title="linear_search.ts"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```c title="linear_search.c"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "C#"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```csharp title="linear_search.cs"
 | 
					 | 
				
			||||||
    [class]{linear_search}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Swift"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```swift title="linear_search.swift"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
=== "Zig"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```zig title="linear_search.zig"
 | 
					 | 
				
			||||||
    [class]{}-[func]{linearSearchLinkedList}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 复杂度分析
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**时间复杂度 $O(n)$** :其中 $n$ 代表数组或链表的长度。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**空间复杂度 $O(1)$** :无需借助额外的存储空间。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 优点与局限性
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**线性查找具有极佳的通用性**。由于线性查找是逐个访问元素的,没有跳跃式访问,因此适用于数组和链表的查找。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**线性查找的时间复杂度较高**。当数据量 $n$ 较大时,线性查找的效率较低。
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB  | 
| 
		 Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB  | 
@ -1,14 +1,6 @@
 | 
				
			|||||||
# 权衡时间与空间
 | 
					# 哈希优化策略
 | 
				
			||||||
 | 
					
 | 
				
			||||||
理想情况下,我们希望算法的时间复杂度和空间复杂度都能达到最优。然而在实际情况中,同时优化时间复杂度和空间复杂度通常是非常困难的。
 | 
					在算法题中,**我们时常通过将线性查找替换为哈希查找来降低算法的时间复杂度**。以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例。
 | 
				
			||||||
 | 
					 | 
				
			||||||
**降低时间复杂度通常需要以提升空间复杂度为代价,反之亦然**。我们将牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,则称之为「以时间换空间」。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
选择哪种思路取决于我们更看重哪个方面。在大多数情况下,时间比空间更宝贵,因此以空间换时间通常是更常用的策略。当然,在数据量很大的情况下,控制空间复杂度也是非常重要的。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 示例题目 *
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
!!! question "两数之和"
 | 
					!!! question "两数之和"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -18,11 +10,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    你可以按任意顺序返回答案。
 | 
					    你可以按任意顺序返回答案。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
「暴力枚举」和「辅助哈希表」分别对应“空间最优”和“时间最优”的两种解法。遵循时间比空间更宝贵的原则,后者是本题的最佳解法。
 | 
					## 线性查找:以时间换空间
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 方法一:暴力枚举
 | 
					考虑直接遍历所有可能的组合。开启一个两层循环,在每轮中判断两个整数的和是否为 `target` ,若是,则返回它们的索引。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
考虑直接遍历所有可能的组合。通过开启一个两层循环,判断两个整数的和是否为 `target` ,若是,则返回它们的索引(即下标)。
 | 
					(图)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=== "Java"
 | 
					=== "Java"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,15 +76,17 @@
 | 
				
			|||||||
    [class]{}-[func]{twoSumBruteForce}
 | 
					    [class]{}-[func]{twoSumBruteForce}
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
该方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,**属于以时间换空间**。此方法时间复杂度太高,在大数据量下非常耗时。
 | 
					此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 方法二:辅助哈希表
 | 
					## 哈希查找:以空间换时间
 | 
				
			||||||
 | 
					
 | 
				
			||||||
考虑借助一个哈希表,key-value 分别为数组元素和元素索引。循环遍历数组中的每个元素 num,并执行:
 | 
					考虑借助一个哈希表,将数组元素和元素索引构建为键值对。循环遍历数组中的每个元素 `num` 并执行:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. 判断数字 `target - num` 是否在哈希表中,若是则直接返回该两个元素的索引;
 | 
					1. 判断数字 `target - num` 是否在哈希表中,若是则直接返回该两个元素的索引;
 | 
				
			||||||
2. 将元素 `num` 和其索引添加进哈希表;
 | 
					2. 将元素 `num` 和其索引添加进哈希表;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(图)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=== "Java"
 | 
					=== "Java"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```java title="leetcode_two_sum.java"
 | 
					    ```java title="leetcode_two_sum.java"
 | 
				
			||||||
@ -153,4 +147,6 @@
 | 
				
			|||||||
    [class]{}-[func]{twoSumHashTable}
 | 
					    [class]{}-[func]{twoSumHashTable}
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
该方法的时间复杂度为 $O(N)$ ,空间复杂度为 $O(N)$ ,**体现了以空间换时间**。尽管此方法引入了额外的空间使用,但在时间和空间的整体效率更为均衡,因此它是本题的最优解法。
 | 
					此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降低至 $O(n)$ ,大幅提升运行效率。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					由于需要维护一个额外的哈希表,因此空间复杂度为 $O(n)$ 。**尽管如此,该方法的整体时空效率更为均衡,因此它是本题的最优解法**。
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 102 KiB  | 
							
								
								
									
										81
									
								
								docs/chapter_searching/searching_algorithm_revisited.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					# 搜索算法
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					「搜索算法 Searching Algorithm」用于在数据结构(例如数组、链表、树或图)中搜索一个或一组满足特定条件的元素。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					我们已经学过数组、链表、树和图的遍历方法,也学过哈希表、二叉搜索树等可用于实现查询的复杂数据结构。因此,搜索算法对于我们来说并不陌生。在本节,我们将从更加系统的视角切入,重新审视搜索算法。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 暴力搜索
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					暴力搜索通过遍历数据结构的每个元素来定位目标元素。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 「线性搜索」适用于数组和链表等线性数据结构。它从数据结构的一端开始,逐个访问元素,直到找到目标元素或到达另一端仍没有找到目标元素为止。
 | 
				
			||||||
 | 
					- 「广度优先搜索」和「深度优先搜索」是图和树的两种遍历策略。广度优先搜索从初始节点开始逐层搜索,由近及远地访问各个节点。深度优先搜索是从初始节点开始,沿着一条路径走到头为止,再回溯并尝试其他路径,直到遍历完整个数据结构。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					暴力搜索的优点是简单且通用性好,**无需对数据做预处理和借助额外的数据结构**。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					然而,**此类算法的时间复杂度为 $O(n)$** ,其中 $n$ 为元素数量,因此在数据量较大的情况下性能较差。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 自适应搜索
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					自适应搜索利用数据的特有属性(例如有序性)来优化搜索过程,从而更高效地定位目标元素。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 「二分查找」利用数据的有序性实现高效查找,仅适用于数组。
 | 
				
			||||||
 | 
					- 「哈希查找」利用哈希表将搜索数据和目标数据建立为键值对映射,从而实现查询操作。
 | 
				
			||||||
 | 
					- 「树查找」在特定的树结构(例如二叉搜索树)中,基于比较节点值来快速排除节点,从而定位目标元素。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					此类算法的优点是效率高,**时间复杂度可达到 $O(\log n)$ 甚至 $O(1)$** 。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					然而,**使用这些算法往往需要对数据进行预处理**。例如,二分查找需要预先对数组进行排序,哈希查找和树查找都需要借助额外的数据结构,维护这些数据结构也需要额外的时间和空间开支。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					!!! note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    自适应搜索算法常被称为查找算法,**主要关注在特定数据结构中快速检索目标元素**。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 搜索方法选取
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					给定大小为 $n$ 的一组数据,我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法在该数据中搜索目标元素。各个方法的工作原理如下图所示。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					上述几种方法的操作效率与特性如下表所示。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="center-table" markdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					|              | 线性搜索 | 二分查找           | 树查找             | 哈希查找        |
 | 
				
			||||||
 | 
					| ------------ | -------- | ------------------ | ------------------ | --------------- |
 | 
				
			||||||
 | 
					| 查找元素     | $O(n)$   | $O(\log n)$        | $O(\log n)$        | $O(1)$          |
 | 
				
			||||||
 | 
					| 插入元素     | $O(1)$   | $O(n)$             | $O(\log n)$        | $O(1)$          |
 | 
				
			||||||
 | 
					| 删除元素     | $O(n)$   | $O(n)$             | $O(\log n)$        | $O(1)$          |
 | 
				
			||||||
 | 
					| 额外空间     | $O(1)$   | $O(1)$             | $O(n)$             | $O(n)$          |
 | 
				
			||||||
 | 
					| 数据预处理   | /        | 排序 $O(n \log n)$ | 建树 $O(n \log n)$ | 建哈希表 $O(n)$ |
 | 
				
			||||||
 | 
					| 数据是否有序 | 无序     | 有序               | 有序               | 无序            |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					除了以上表格内容,搜索算法的选择还取决于数据体量、搜索性能要求、数据查询与更新频率等。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**线性搜索**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 通用性较好,无需任何数据预处理操作。加入我们仅需查询一次数据,那么其他三种方法的数据预处理的时间比线性搜索的时间还要更长。
 | 
				
			||||||
 | 
					- 适用于体量较小的数据,此情况下时间复杂度对效率影响较小。
 | 
				
			||||||
 | 
					- 适用于数据更新频率较高的场景,因为该方法不需要对数据进行任何额外维护。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**二分查找**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 适用于大数据量的情况,效率表现稳定,最差时间复杂度为 $O(\log n)$ 。
 | 
				
			||||||
 | 
					- 数据量不能过大,因为存储数组需要连续的内存空间。
 | 
				
			||||||
 | 
					- 不适用于高频增删数据的场景,因为维护有序数组的开销较大。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**哈希查找**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 适合对查询性能要求很高的场景,平均时间复杂度为 $O(1)$ 。
 | 
				
			||||||
 | 
					- 不适合需要有序数据或范围查找的场景,因为哈希表无法维护数据的有序性。
 | 
				
			||||||
 | 
					- 对哈希函数和哈希冲突处理策略的依赖性较高,具有较大的性能劣化风险。
 | 
				
			||||||
 | 
					- 不适合数据量过大的情况,因为哈希表需要额外空间来最大程度地减少冲突,从而提供良好的查询性能。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**树查找**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 适用于海量数据,因为树节点在内存中是离散存储的。
 | 
				
			||||||
 | 
					- 适合需要维护有序数据或范围查找的场景。
 | 
				
			||||||
 | 
					- 在持续增删节点的过程中,二叉搜索树可能产生倾斜,时间复杂度劣化至 $O(n)$ 。
 | 
				
			||||||
 | 
					- 若使用 AVL 树或红黑树,则各项操作可在 $O(\log n)$ 效率下稳定运行,但维护树平衡的操作会增加额外开销。
 | 
				
			||||||
@ -1,16 +1,8 @@
 | 
				
			|||||||
# 小结
 | 
					# 小结
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 线性查找通过遍历数据结构并进行条件判断来完成查找任务。
 | 
					 | 
				
			||||||
- 二分查找依赖于数据的有序性,通过循环逐步缩减一半搜索区间来实现查找。它要求输入数据有序,且仅适用于数组或基于数组实现的数据结构。
 | 
					- 二分查找依赖于数据的有序性,通过循环逐步缩减一半搜索区间来实现查找。它要求输入数据有序,且仅适用于数组或基于数组实现的数据结构。
 | 
				
			||||||
- 哈希查找利用哈希表实现常数阶时间复杂度的查找操作,体现了空间换时间的算法思维。
 | 
					- 暴力搜索通过遍历数据结构来定位数据。线性搜索适用于数组和链表,广度优先搜索和深度优先搜索适用于图和树。此类算法通用性好,无需对数据预处理,但时间复杂度 $O(n)$ 较高。
 | 
				
			||||||
- 下表概括并对比了三种查找算法的特性和时间复杂度。
 | 
					- 哈希查找、树查找和二分查找属于高效搜索方法,可在特定数据结构中快速定位目标元素。此类算法效率高,时间复杂度可达 $O(\log n)$ 甚至 $O(1)$ ,但通常需要借助额外数据结构。
 | 
				
			||||||
 | 
					- 实际中,我们需要对数据体量、搜索性能要求、数据查询和更新频率等因素进行具体分析,从而选择合适的搜索方法。
 | 
				
			||||||
<div class="center-table" markdown>
 | 
					- 线性搜索适用于小型或频繁更新的数据;二分查找适用于大型、排序的数据;哈希查找适合对查询效率要求较高且无需范围查询的数据;树查找适用于需要维护顺序和支持范围查询的大型动态数据。
 | 
				
			||||||
 | 
					- 用哈希查找替换线性查找是一种常用的优化运行时间的策略,可将时间复杂度从 $O(n)$ 降低至 $O(1)$ 。 
 | 
				
			||||||
|                                       | 线性查找                 | 二分查找                      | 哈希查找                 |
 | 
					 | 
				
			||||||
| ------------------------------------- | ------------------------ | ----------------------------- | ------------------------ |
 | 
					 | 
				
			||||||
| 适用数据结构                           | 数组、链表               | 有序数组                          | 数组、链表               |
 | 
					 | 
				
			||||||
| 时间复杂度</br>(查找,插入,删除)        | $O(n)$ , $O(1)$ , $O(n)$ | $O(\log n)$ , $O(n)$ , $O(n)$ | $O(1)$ , $O(1)$ , $O(1)$ |
 | 
					 | 
				
			||||||
| 空间复杂度                             | $O(1)$                   | $O(1)$                        | $O(n)$                   |
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -268,36 +268,21 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## 二叉搜索树的效率
 | 
					## 二叉搜索树的效率
 | 
				
			||||||
 | 
					
 | 
				
			||||||
假设给定 $n$ 个数字,最常见的存储方式是「数组」。对于这串乱序的数字,常见操作的效率如下:
 | 
					给定一组数据,我们考虑使用数组或二叉搜索树存储。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **查找元素**:由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间;
 | 
					观察可知,二叉搜索树的各项操作的时间复杂度都是对数阶,具有稳定且高效的性能表现。只有在高频添加、低频查找删除的数据适用场景下,数组比二叉搜索树的效率更高。
 | 
				
			||||||
- **插入元素**:只需将元素添加至数组尾部即可,使用 $O(1)$ 时间;
 | 
					 | 
				
			||||||
- **删除元素**:先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间;
 | 
					 | 
				
			||||||
- **获取最小 / 最大元素**:需要遍历数组来确定,使用 $O(n)$ 时间;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
为了获得先验信息,我们可以预先将数组元素进行排序,得到一个「排序数组」。此时操作效率如下:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- **查找元素**:由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间;
 | 
					 | 
				
			||||||
- **插入元素**:先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间;
 | 
					 | 
				
			||||||
- **删除元素**:先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间;
 | 
					 | 
				
			||||||
- **获取最小 / 最大元素**:数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
观察可知,无序数组和有序数组中的各项操作的时间复杂度呈现“偏科”的特点,即有的快有的慢。**然而,二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 较大时具有显著优势**。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="center-table" markdown>
 | 
					<div class="center-table" markdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
|                     | 无序数组 | 有序数组    | 二叉搜索树  |
 | 
					|          | 无序数组 | 二叉搜索树  |
 | 
				
			||||||
| ------------------- | -------- | ----------- | ----------- |
 | 
					| -------- | -------- | ----------- |
 | 
				
			||||||
| 查找指定元素        | $O(n)$   | $O(\log n)$ | $O(\log n)$ |
 | 
					| 查找元素 | $O(n)$   | $O(\log n)$ |
 | 
				
			||||||
| 插入元素            | $O(1)$   | $O(n)$      | $O(\log n)$ |
 | 
					| 插入元素 | $O(1)$   | $O(\log n)$ |
 | 
				
			||||||
| 删除元素            | $O(n)$   | $O(n)$      | $O(\log n)$ |
 | 
					| 删除元素 | $O(n)$   | $O(\log n)$ |
 | 
				
			||||||
| 获取最小 / 最大元素 | $O(n)$   | $O(1)$      | $O(\log n)$ |
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 二叉搜索树的退化
 | 
					在理想情况下,二叉搜索树是“平衡”的,这样就可以在 $\log n$ 轮循环内查找任意节点。
 | 
				
			||||||
 | 
					 | 
				
			||||||
在理想情况下,我们希望二叉搜索树是“平衡”的,这样就可以在 $\log n$ 轮循环内查找任意节点。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为链表,这时各种操作的时间复杂度也会退化为 $O(n)$ 。
 | 
					然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为链表,这时各种操作的时间复杂度也会退化为 $O(n)$ 。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										68
									
								
								mkdocs.yml
									
									
									
									
									
								
							
							
						
						@ -136,8 +136,7 @@ nav:
 | 
				
			|||||||
    - 2.1.   算法效率评估: chapter_computational_complexity/performance_evaluation.md
 | 
					    - 2.1.   算法效率评估: chapter_computational_complexity/performance_evaluation.md
 | 
				
			||||||
    - 2.2.   时间复杂度: chapter_computational_complexity/time_complexity.md
 | 
					    - 2.2.   时间复杂度: chapter_computational_complexity/time_complexity.md
 | 
				
			||||||
    - 2.3.   空间复杂度: chapter_computational_complexity/space_complexity.md
 | 
					    - 2.3.   空间复杂度: chapter_computational_complexity/space_complexity.md
 | 
				
			||||||
    - 2.4.   权衡时间与空间: chapter_computational_complexity/space_time_tradeoff.md
 | 
					    - 2.4.   小结: chapter_computational_complexity/summary.md
 | 
				
			||||||
    - 2.5.   小结: chapter_computational_complexity/summary.md
 | 
					 | 
				
			||||||
  - 3.     数据结构简介:
 | 
					  - 3.     数据结构简介:
 | 
				
			||||||
    - 3.1.   数据与内存: chapter_data_structure/data_and_memory.md
 | 
					    - 3.1.   数据与内存: chapter_data_structure/data_and_memory.md
 | 
				
			||||||
    - 3.2.   数据结构分类: chapter_data_structure/classification_of_data_structure.md
 | 
					    - 3.2.   数据结构分类: chapter_data_structure/classification_of_data_structure.md
 | 
				
			||||||
@ -152,44 +151,45 @@ nav:
 | 
				
			|||||||
    - 5.2.   队列: chapter_stack_and_queue/queue.md
 | 
					    - 5.2.   队列: chapter_stack_and_queue/queue.md
 | 
				
			||||||
    - 5.3.   双向队列: chapter_stack_and_queue/deque.md
 | 
					    - 5.3.   双向队列: chapter_stack_and_queue/deque.md
 | 
				
			||||||
    - 5.4.   小结: chapter_stack_and_queue/summary.md
 | 
					    - 5.4.   小结: chapter_stack_and_queue/summary.md
 | 
				
			||||||
  - 6.     散列表:
 | 
					  - 6.     二分查找:
 | 
				
			||||||
    - 6.1.   哈希表: chapter_hashing/hash_map.md
 | 
					    - 6.1.   二分查找: chapter_binary_search/binary_search.md
 | 
				
			||||||
    - 6.2.   哈希冲突处理: chapter_hashing/hash_collision.md
 | 
					  - 7.     散列表:
 | 
				
			||||||
    - 6.3.   小结: chapter_hashing/summary.md
 | 
					    - 7.1.   哈希表: chapter_hashing/hash_map.md
 | 
				
			||||||
  - 7.     树:
 | 
					    - 7.2.   哈希冲突处理: chapter_hashing/hash_collision.md
 | 
				
			||||||
    - 7.1.   二叉树: chapter_tree/binary_tree.md
 | 
					    - 7.3.   小结: chapter_hashing/summary.md
 | 
				
			||||||
    - 7.2.   二叉树遍历: chapter_tree/binary_tree_traversal.md
 | 
					  - 8.     树:
 | 
				
			||||||
    - 7.3.   二叉搜索树: chapter_tree/binary_search_tree.md
 | 
					    - 8.1.   二叉树: chapter_tree/binary_tree.md
 | 
				
			||||||
    - 7.4.   AVL 树 *: chapter_tree/avl_tree.md
 | 
					    - 8.2.   二叉树遍历: chapter_tree/binary_tree_traversal.md
 | 
				
			||||||
    - 7.5.   小结: chapter_tree/summary.md
 | 
					    - 8.3.   二叉搜索树: chapter_tree/binary_search_tree.md
 | 
				
			||||||
  - 8.     堆:
 | 
					    - 8.4.   AVL 树 *: chapter_tree/avl_tree.md
 | 
				
			||||||
    - 8.1.   堆: chapter_heap/heap.md
 | 
					    - 8.5.   小结: chapter_tree/summary.md
 | 
				
			||||||
    - 8.2.   建堆操作 *: chapter_heap/build_heap.md
 | 
					  - 9.     堆:
 | 
				
			||||||
    - 8.3.   小结: chapter_heap/summary.md
 | 
					    - 9.1.   堆: chapter_heap/heap.md
 | 
				
			||||||
  - 9.     图:
 | 
					    - 9.2.   建堆操作 *: chapter_heap/build_heap.md
 | 
				
			||||||
    - 9.1.   图: chapter_graph/graph.md
 | 
					    - 9.3.   小结: chapter_heap/summary.md
 | 
				
			||||||
    - 9.2.   图基础操作: chapter_graph/graph_operations.md
 | 
					  - 10.     图:
 | 
				
			||||||
    - 9.3.   图的遍历: chapter_graph/graph_traversal.md
 | 
					    - 10.1.   图: chapter_graph/graph.md
 | 
				
			||||||
    - 9.4.   小结: chapter_graph/summary.md
 | 
					    - 10.2.   图基础操作: chapter_graph/graph_operations.md
 | 
				
			||||||
  - 10.     查找算法:
 | 
					    - 10.3.   图的遍历: chapter_graph/graph_traversal.md
 | 
				
			||||||
    - 10.1.   线性查找: chapter_searching/linear_search.md
 | 
					    - 10.4.   小结: chapter_graph/summary.md
 | 
				
			||||||
    - 10.2.   二分查找: chapter_searching/binary_search.md
 | 
					 | 
				
			||||||
    - 10.3.   哈希查找: chapter_searching/hashing_search.md
 | 
					 | 
				
			||||||
    - 10.4.   小结: chapter_searching/summary.md
 | 
					 | 
				
			||||||
  - 11.     排序算法:
 | 
					  - 11.     排序算法:
 | 
				
			||||||
    - 11.1.   排序算法: chapter_sorting/sorting_algorithm.md
 | 
					    - 11.1.   排序算法: chapter_sorting/sorting_algorithm.md
 | 
				
			||||||
    - 11.2.   冒泡排序: chapter_sorting/bubble_sort.md
 | 
					    - 11.2.   冒泡排序: chapter_sorting/bubble_sort.md
 | 
				
			||||||
    - 11.3.   插入排序: chapter_sorting/insertion_sort.md
 | 
					    - 11.3.   插入排序: chapter_sorting/insertion_sort.md
 | 
				
			||||||
    - 11.4.   快速排序: chapter_sorting/quick_sort.md
 | 
					    - 11.4.   快速排序: chapter_sorting/quick_sort.md
 | 
				
			||||||
    - 11.5.   归并排序: chapter_sorting/merge_sort.md
 | 
					    - 11.5.   归并排序: chapter_sorting/merge_sort.md
 | 
				
			||||||
    - 11.6.   桶排序(New): chapter_sorting/bucket_sort.md
 | 
					    - 11.6.   桶排序: chapter_sorting/bucket_sort.md
 | 
				
			||||||
    - 11.7.   计数排序(New): chapter_sorting/counting_sort.md
 | 
					    - 11.7.   计数排序: chapter_sorting/counting_sort.md
 | 
				
			||||||
    - 11.8.   基数排序(New): chapter_sorting/radix_sort.md
 | 
					    - 11.8.   基数排序: chapter_sorting/radix_sort.md
 | 
				
			||||||
    - 11.9.   小结: chapter_sorting/summary.md
 | 
					    - 11.9.   小结: chapter_sorting/summary.md
 | 
				
			||||||
  - 12.     回溯算法:
 | 
					  - 12.     搜索算法:
 | 
				
			||||||
    - 12.1.   回溯算法(New): chapter_backtracking/backtracking_algorithm.md
 | 
					    - 12.1.   搜索算法(New): chapter_searching/searching_algorithm_revisited.md
 | 
				
			||||||
  - 13.     附录:
 | 
					    - 12.2.   哈希优化策略: chapter_searching/replace_linear_by_hashing.md
 | 
				
			||||||
    - 13.1.   编程环境安装: chapter_appendix/installation.md
 | 
					    - 12.3.   小结: chapter_searching/summary.md
 | 
				
			||||||
    - 13.2.   一起参与创作: chapter_appendix/contribution.md
 | 
					  - 13.     回溯算法:
 | 
				
			||||||
 | 
					    - 13.1.   回溯算法(New): chapter_backtracking/backtracking_algorithm.md
 | 
				
			||||||
 | 
					  - 14.     附录:
 | 
				
			||||||
 | 
					    - 14.1.   编程环境安装: chapter_appendix/installation.md
 | 
				
			||||||
 | 
					    - 14.2.   一起参与创作: chapter_appendix/contribution.md
 | 
				
			||||||
  - 参考文献:
 | 
					  - 参考文献:
 | 
				
			||||||
    - chapter_reference/index.md
 | 
					    - chapter_reference/index.md
 | 
				
			||||||
 | 
				
			|||||||