Skip to content

Commit bb39a5c

Browse files
committed
feat: add searching algorithm
1 parent 43ff3d9 commit bb39a5c

File tree

8 files changed

+216
-3
lines changed

8 files changed

+216
-3
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@
3333

3434
## 基础算法通关
3535

36-
### 常见的排序算法
36+
### 排序算法
3737

3838
1. [冒泡排序](/basic/sorting/BubbleSort/README.md)
3939
1. [插入排序](/basic/sorting/InsertionSort/README.md)
4040
1. [选择排序](/basic/sorting/SelectionSort/README.md)
4141
1. [归并排序](/basic/sorting/MergeSort/README.md)
4242
1. [快速排序](/basic/sorting/QuickSort/README.md)
4343

44+
### 查找算法
45+
46+
1. [二分查找](/basic/searching/BinarySearch/README.md)
47+
4448
## 面试高频考题
4549

4650
### 数组

README_EN.md

+6
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,18 @@ Complete solutions to [LeetCode](https://leetcode-cn.com/problemset/all/), [LCOF
3333

3434
## Basic Algorithms
3535

36+
### Sorting
37+
3638
1. [Bubble Sort](/basic/sorting/BubbleSort/README.md)
3739
1. [Insertion Sort](/basic/sorting/InsertionSort/README.md)
3840
1. [Selection Sort](/basic/sorting/SelectionSort/README.md)
3941
1. [Merge Sort](/basic/sorting/MergeSort/README.md)
4042
1. [Quick Sort](/basic/sorting/QuickSort/README.md)
4143

44+
### Searching
45+
46+
1. [Binary Search](/basic/searching/BinarySearch/README.md)
47+
4248
## High Frequency Interview Questions
4349

4450
### Arrays

basic/README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# 基础算法通关
22

3-
## 常见的排序算法
3+
## 排序算法
44

55
- [冒泡排序](./sorting/BubbleSort/README.md)
66
- [插入排序](./sorting/InsertionSort/README.md)
77
- [选择排序](./sorting/SelectionSort/README.md)
88
- [归并排序](./sorting/MergeSort/README.md)
99
- [快速排序](./sorting/QuickSort/README.md)
10+
11+
## 查找算法
12+
13+
- [Binary Search](./searching/BinarySearch/README.md)

basic/README_EN.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@
77
- [Selection Sort](./sorting/SelectionSort/README.md)
88
- [Merge Sort](./sorting/MergeSort/README.md)
99
- [Quick Sort](./sorting/QuickSort/README.md)
10+
11+
## Searching
12+
13+
- [Binary Search](./searching/BinarySearch/README.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
public class BinarySearch {
2+
3+
private static int search(int[] nums, int low, int high, int val) {
4+
while (low <= high) {
5+
int mid = low + ((high -low) >> 1);
6+
if (nums[mid] == val) {
7+
return mid;
8+
} else if (nums[mid] < val) {
9+
low = mid + 1;
10+
} else {
11+
high = mid - 1;
12+
}
13+
}
14+
return -1;
15+
}
16+
17+
private static int searchRecursive(int[] nums, int low, int high, int val) {
18+
while (low <= high) {
19+
int mid = low + ((high - low) >> 1);
20+
if (nums[mid] == val) {
21+
return mid;
22+
} else if (nums[mid] < val) {
23+
return searchRecursive(nums, mid + 1, high, val);
24+
} else {
25+
return searchRecursive(nums, low, mid - 1, val);
26+
}
27+
}
28+
return -1;
29+
}
30+
31+
/**
32+
* 二分查找(非递归)
33+
*
34+
* @param nums 有序数组
35+
* @param val 要查找的值
36+
* @return 要查找的值在数组中的索引位置
37+
*/
38+
private static int search(int[] nums, int val) {
39+
return search(nums, 0, nums.length - 1, val);
40+
}
41+
42+
/**
43+
* 二分查找(递归)
44+
*
45+
* @param nums 有序数组
46+
* @param val 要查找的值
47+
* @return 要查找的值在数组中的索引位置
48+
*/
49+
private static int searchRecursive(int[] nums, int val) {
50+
return searchRecursive(nums, 0, nums.length - 1, val);
51+
}
52+
53+
public static void main(String[] args) {
54+
int[] nums = {1, 2, 5, 7, 8, 9};
55+
56+
// 非递归查找
57+
int r1 = search(nums, 7);
58+
System.out.println(r1);
59+
60+
// 递归查找
61+
int r2 = search(nums, 7);
62+
System.out.println(r2);
63+
}
64+
}
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# 二分查找
2+
3+
二分查找是一种非常高效的查找算法,高效到什么程度呢?我们来分析一下它的时间复杂度。
4+
5+
我们假设数据大小是 n,每次查找后数据都会缩小为原来的一半,也就是会除以 2。最坏情况下,直到查找区间被缩小为空,才停止。被查找区间的大小变化:
6+
7+
```
8+
n, n/2, n/4, n/8, ..., n/(2^k)
9+
```
10+
11+
可以看出来,这是一个等比数列。其中 n/(2^k)=1 时,k 的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了 k 次区间缩小操作,时间复杂度就是 O(k)。通过 n/(2^k)=1,我们可以求得 k=log2n,所以时间复杂度就是 O(logn)。
12+
13+
## 代码示例
14+
15+
注意容易出错的 3 个地方。
16+
17+
1. 循环退出条件是 `low <= high`,而不是 `low < high`
18+
1. mid 的取值,可以是 `mid = (low + high) / 2`,但是如果 low 和 high 比较大的话,`low + high` 可能会溢出,所以这里写为 `mid = low + ((high - low) >> 1)`
19+
1. low 和 high 的更新分别为 `low = mid + 1``high = mid - 1`
20+
21+
<!-- tabs:start -->
22+
23+
### **Java**
24+
25+
非递归实现:
26+
27+
```java
28+
public class BinarySearch {
29+
30+
private static int search(int[] nums, int low, int high, int val) {
31+
while (low <= high) {
32+
int mid = low + ((high -low) >> 1);
33+
if (nums[mid] == val) {
34+
return mid;
35+
} else if (nums[mid] < val) {
36+
low = mid + 1;
37+
} else {
38+
high = mid - 1;
39+
}
40+
}
41+
return -1;
42+
}
43+
44+
/**
45+
* 二分查找(非递归)
46+
*
47+
* @param nums 有序数组
48+
* @param val 要查找的值
49+
* @return 要查找的值在数组中的索引位置
50+
*/
51+
private static int search(int[] nums, int val) {
52+
return search(nums, 0, nums.length - 1, val);
53+
}
54+
55+
public static void main(String[] args) {
56+
int[] nums = {1, 2, 5, 7, 8, 9};
57+
58+
// 非递归查找
59+
int r1 = search(nums, 7);
60+
System.out.println(r1);
61+
62+
// 递归查找
63+
int r2 = search(nums, 7);
64+
System.out.println(r2);
65+
}
66+
}
67+
```
68+
69+
递归实现:
70+
71+
```java
72+
public class BinarySearch {
73+
74+
private static int searchRecursive(int[] nums, int low, int high, int val) {
75+
while (low <= high) {
76+
int mid = low + ((high - low) >> 1);
77+
if (nums[mid] == val) {
78+
return mid;
79+
} else if (nums[mid] < val) {
80+
return searchRecursive(nums, mid + 1, high, val);
81+
} else {
82+
return searchRecursive(nums, low, mid - 1, val);
83+
}
84+
}
85+
return -1;
86+
}
87+
88+
/**
89+
* 二分查找(递归)
90+
*
91+
* @param nums 有序数组
92+
* @param val 要查找的值
93+
* @return 要查找的值在数组中的索引位置
94+
*/
95+
private static int searchRecursive(int[] nums, int val) {
96+
return searchRecursive(nums, 0, nums.length - 1, val);
97+
}
98+
99+
public static void main(String[] args) {
100+
int[] nums = {1, 2, 5, 7, 8, 9};
101+
102+
// 非递归查找
103+
int r1 = search(nums, 7);
104+
System.out.println(r1);
105+
106+
// 递归查找
107+
int r2 = search(nums, 7);
108+
System.out.println(r2);
109+
}
110+
}
111+
```
112+
113+
<!-- tabs:end -->

basic/sorting/QuickSort/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,19 @@ public class QuickSort {
6666
但是,如果每次在选择基准值的时候,都不幸地选择了子数组里的最大或最小值。即每次把把数组分成了两个更小长度的数组,其中一个长度为 1,另一个的长度是子数组的长度减 1。这样的算法复杂度变成 O(n²)。
6767

6868
和归并排序不同,快速排序在每次递归的过程中,只需要开辟 O(1) 的存储空间来完成操作来实现对数组的修改;而递归次数为 logn,所以它的整体空间复杂度完全取决于压堆栈的次数。
69+
70+
## 如何优化快速排序?
71+
72+
前面讲到,最坏情况下快速排序的时间复杂度是 O(n²),实际上,这种 O(n²) 时间复杂度出现的主要原因还是因为我们基准值选得不够合理。最理想的基准点是:**被基准点分开的两个子数组中,数据的数量差不多**
73+
74+
如果很粗暴地直接选择第一个或者最后一个数据作为基准值,不考虑数据的特点,肯定会出现之前讲的那样,在某些情况下,排序的最坏情况时间复杂度是 O(n²)。
75+
76+
有两个比较常用的分区算法。
77+
78+
### 1. 三数取中法
79+
80+
我们从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。这样每间隔某个固定的长度,取数据出来比较,将中间值作为分区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。
81+
82+
### 2. 随机法
83+
84+
随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选的很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的 O(n²) 的情况,出现的可能性不大。

basic/summary.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
- 基础算法通关
2-
- 常见的排序算法
2+
- 排序算法
33
- [冒泡排序](/basic/sorting/BubbleSort/README.md)
44
- [插入排序](/basic/sorting/InsertionSort/README.md)
55
- [选择排序](/basic/sorting/SelectionSort/README.md)
66
- [归并排序](/basic/sorting/MergeSort/README.md)
77
- [快速排序](/basic/sorting/QuickSort/README.md)
8+
- 查找算法
9+
- [二分查找](/basic/searching/BinarySearch/README.md)

0 commit comments

Comments
 (0)