写在前面:
该系列博客,是本人系统学习数据结构以及算法的笔记类博客,用于记录一些知识点以及自己所实现的代码,以作备忘,欢迎批评交流。
下面开始正文,介绍一下数据结构及算法的入门定义。
目录
1. 什么是数据结构、算法
简而言之,数据结构就是数据的存储结构,算法就是操作这些数据的方法。
两者都是前人为了更高效、更节省空间地进行数据处理而创造的,所聚焦的是“快”和“省”两个字。
2. 复杂度分析
既然已经知道,数据结构和算法是为了快且省地进行数据操作,那么就需要一个指标来评价你的算法到底有多快、多省。
复杂度分析即是为此而生,包括时间复杂度分析、空间复杂度分析两种。
主要用到大O分析法:,表示的是复杂度的量级,也即不是具体的时间、内存消耗,而是其消耗与数据规模相关的量级。
常用的复杂度有:
- 常量阶:
- 对数阶:
- 线性阶:
- 线性对数阶:
- k次方阶:
- 指数阶:
- 阶乘阶:
2.1 时间复杂度
分析时间复杂度时,需要假设每一个单元操作是一个单位时间,然后只需要统计整个程序的操作次数再取最大量级即可。
下面列举几种复杂度的程序,即可一目了然:
// 程序A:
int a=1;
int b=2;
// 程序B:
int i=1;
while (i <= n) {
i = i * 2;
}
// 程序C:
int i=1;
int sum=0;
while (i <= n) {
sum += i;
}
对于程序A,执行了两句话,其量级是常量,因此时间复杂度为;
对于程序B,执行了次,因此其时间复杂度为;
对于程序C,则执行了次,因此其时间复杂度为;
其他的时间复杂度同理,只需要记住:我们所求的只是最大量级。
此外,对于有两个或者多个数据规模的情况,则需要对每个规模单独计算时间复杂度,然后相加,如:
int cal(int m, int n) {
int sum_1 = 0;
int i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
上述代码的时间复杂度为:.
对于嵌套代码的复杂度,等于嵌套内外代码复杂度的乘积。这个不难理解,想象一个双重for循环:
int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
}
int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
其时间复杂度为:.
2.2. 空间复杂度
空间复杂度主要从程序运行过程所申请内存大小来判断,表示算法的存储空间与数据规模之间的增长关系。
以上述程序A为例,其空间复杂度为。再举个例子:
int n = 1000;
int[] a = new int[n];
其空间复杂度则为,因为申请了个int型内存空间。
3. 最好、最坏、平均、均摊时间复杂度
空间复杂度不必多讲,对于时间复杂度,有时候需要考虑不同情况下复杂度的不同,主要分为:最好、最坏、平均、均摊几种。
最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度;
最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度;
平均时间复杂度:各种情况下的平均情况;
以代码为例:
// n表示数组array的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
可简单得出:最好情况下,时间复杂度为,最坏情况下为。
而平均时间复杂度呢,上述代码中,是用于查找数组中指定元素的程序,所以分两种情况:x在数组中、x不在数组中,假设每种情况的概率为,对于在数组中的情况,x在数组各个位置的概率又为,因此把所有情况加起来即为:
因此,平均时间复杂度也为。
此外,还有一个概念——均摊时间复杂度。这个概念是用于描述好情况、坏情况有规律重复的时候,将坏情况复杂度直接均摊给所有简单情况,从而计算时间复杂度的方法。
比如:对于一组规模为n的数据,前n-1中情况,都是复杂度为,第n种情况复杂度为,则利用均摊计算方法,其均摊时间复杂度为.