一. 初识算法
1.1 什么是算法?
定义
在数学和计算机科学领域,算法是一系列有限的严谨指令,通常用于解决一类特定问题或执行计算
In mathematics and computer science, an algorithm (/ˈælɡərɪðəm/) is a finite sequence of rigorous instructions, typically used to solve a class of specific problems or to perform a computation.[^1]
Introduction to Algorithm[^2]
不正式的说,算法就是任何定义优良的计算过程:接收一些值作为输入,在有限的时间内,产生一些值作为输出。
Informally, an algorithm is any well-defined computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output in a finite amount of time.
1.2 什么是数据结构?
定义
在计算机科学领域,数据结构是一种数据组织、管理和存储格式,通常被选择用来高效访问数据
In computer science, a data structure is a data organization, management, and storage format that is usually chosen for efficient access to data
Introduction to Algorithm[^2]
数据结构是一种存储和组织数据的方式,旨在便于访问和修改
A data structure is a way to store and organize data in order to facilitate access and modifications
接下来我们通过对一个非常著名的二分查找算法的讲解来认识一下算法
1.3 二分查找 [^3]
二分查找算法也称折半查找,是一种非常高效的工作于有序数组的查找算法。后续的课程中还会学习更多的查找算法,但在此之前,不妨用它作为入门。
二分查找基础版
需求:在有序数组 A A A 内,查找值 t a r g e t target target
- 如果找到返回索引
- 如果找不到返回 − 1 -1 −1
算法描述 | |
---|---|
前提 | 给定一个内含 n n n 个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ ⋯ ≤ A n − 1 A_{0}\leq A_{1}\leq A_{2}\leq \cdots \leq A_{n-1} A0≤A1≤A2≤⋯≤An−1,一个待查值 t a r g e t target target |
1 | 设置 i = 0 i=0 i=0, j = n − 1 j=n-1 j=n−1 |
2 | 如果 i > j i \gt j i>j,结束查找,没找到 |
3 | 设置 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) , m m m 为中间索引, f l o o r floor floor 是向下取整( ≤ i + j 2 \leq \frac {i+j}{2} ≤2i+j 的最小整数) |
4 | 如果 t a r g e t < A m target < A_{m} target<Am 设置 j = m − 1 j = m - 1 j=m−1,跳到第2步 |
5 | 如果 A m < t a r g e t A_{m} < target Am<target 设置 i = m + 1 i = m + 1 i=m+1,跳到第2步 |
6 | 如果 A m = t a r g e t A_{m} = target Am=target,结束查找,找到了 |
java 实现
public static int binarySearch(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
// 在左边
j = m - 1;
} else if (a[m] < target) {
// 在右边
i = m + 1;
} else {
return m;
}
}
return -1;
}
- i , j i,j i,j 对应着搜索区间 [ 0 , a . l e n g t h − 1 ] [0,a.length-1] [0,a.length−1](注意是闭合的区间), i < = j i<=j i<=j 意味着搜索区间内还有未比较的元素, i , j i,j i,j 指向的元素也可能是比较的目标
- 思考:如果不加 i = = j i==j i==j 行不行?
- 回答:不行,因为这意味着 i , j i,j i,j 指向的元素会漏过比较
- m m m 对应着中间位置,中间位置左边和右边的元素可能不相等(差一个),不会影响结果
- 如果某次未找到,那么缩小后的区间内不包含 m m m
二分查找改变版
另一种写法
public static int binarySearch(int[] a, int target) {
int i = 0, j = a.length;
while (i < j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
// 在左边
j = m;
} else if (a[m] < target) {
// 在右边
i = m + 1;
} else {
return m;
}
}
return -1;
}
- i , j i,j i,j 对应着搜索区间 [ 0 , a . l e n g t h ) [0,a.length) [0,a.length)(注意是左闭右开的区间), i < j i<j i<j 意味着搜索区间内还有未比较的元素, j j j 指向的一定不是查找目标
- 思考:为啥这次不加 i = = j i==j i==j 的条件了?
- 回答:这回 j j j 指向的不是查找目标,如果还加 i = = j i==j i==j 条件,就意味着 j j j 指向的还会再次比较,找不到时,会死循环
- 如果某次要缩小右边界,那么 j = m j=m j=m,因为此时的 m m m 已经不是查找目标了
衡量算法好坏
时间复杂度
下面的查找算法也能得出与之前二分查找一样的结果,那你能说出它差在哪里吗?
public static int search(int[] a, int k) {
for (
int i = 0;
i < a.length;
i++
) {
if (a[i] == k) {
return i;
}
}
return -1;
}
考虑最坏情况下(没找到)例如 [1,2,3,4]
查找