文章目录
- 1 数据结构基础
- 1.1 什么是程序?
- 1.2 数据、数据元素、数据项、数据对象
- 1.3 基本的逻辑结构
- 2 算法效率
- 2.1 时间复杂度
- 2.1.1 循环执行次数
- 2.1.2 大O(n)表示法
- 2.2 空间复杂度
1 数据结构基础
1.1 什么是程序?
程序 = 数据结构 + 算法
- 数据结构:数据结构就是数据的 “收纳盒”,比如书包的不同夹层帮你快速找到课本、文具。数据结构就是用特定的整理方式,让计算机能快速的存取需要的数据。合理的数据结构能够有效提升数据存取的效率,为算法的高效执行提供基础。
- 算法:算法就是解决问题的"操作手册",就像菜谱分步教你怎么做菜一样,它用明确的步骤告诉计算机该怎么处理数据、解决问题。所以,算法就是解决问题的具体方法、电脑运算的具体过程。
1.2 数据、数据元素、数据项、数据对象
- 数据:所有的信息,例如文本、图像、音频、视频等,计算机都会用 “二进制语言”(0和1的组合)记录下来。计算机用 0101 这样的数字记录所有的数据。所以,数据是客观事物的(二进制)符号表示。
- 数据元素:是数据的基本单位,例如一张图片、一段音频等,在数据结构中,就是链表中的一个节点、树中的一个节点、图中的一个节点。
- 数据项:是组成数据元素的、有独立含义、不可分割的最小单位,例如一段音频的音高、音量、时间长度等,都是数据项。
- 数据对象:是有相同性质的数据元素的集合,是数据的一个子集,例如相似的图片,相似风格的音乐等。
举个栗子,以一个学生管理系统为例,将所有学生的信息用 0101 的二进制形式记录下来,这就是数据。而每一个学生就是一个数据元素,数据项就是学生的各种属性,例如学号、姓名、性别等,数据对象就是一堆有相同性质的学生,比如可以按照性别将数据分为男同学数据和女同学数据两种数据对象,所以当数据对象的分类标准不同时,分类结果也就不同了。
PS:数据、数据元素、数据项、数据对象这些概念,如果你是需要考试的同学,一定要搞清楚,但在实际的应用中,意义并不大,不必深纠。
1.3 基本的逻辑结构
首先,逻辑结构和存储结构是截然不同的两个概念:
- 逻辑结构:是指数据元素之间的逻辑关系。
- 存储结构:是指数据在计算机中的存储形式,即物理上的存储结构。
基本的逻辑结构有以下四种:
基本的存储结构有以下两种:
2 算法效率
2.1 时间复杂度
时间复杂度用来表示算法运行时间的长短,用来定性的描述程序的运行时间。
2.1.1 循环执行次数
(1)
for (int i=0; i<n; i++)
{
printf("%d\n", i);
}
//该代码段内,for() 语句将被执行 n+1 次,而不是 n 次,当 i=n-1 时,循环正常运行,当 i=n 时,for() 仍需要判断 i<n 条件是否成立,不成立,跳出 for() 循环,最后这一次判断导致 for() 语句的执行次数是 n+1 次,而不是 n 次。
//printf()只有在符合 i<n 条件时,才会执行,所以执行次数是 n 次。
(2)
for (int i=0; i<n; i++)
{
for (int j=0; j<n; j++)
{
printf("i:%d,j:%d\n", i ,j);
}
}
//该代码段内,for()外部循环语句将执行 n+1 次。for()内部循环将被执行 n(n+1) 次。
//printf()将执行 n*n 次。
(3)
for (i=1; i<=n; i++)
{
for (j=1; j<=i; j++)
{
for (k=1; k<=j; k++)
{
printf("i:%d, j:%d, k:%d \n", i, j , k);
}
}
}
//该代码段内,从内到外思考 printf() 的循环次数,内部第一层循环执行 j 次,第二层循环,执行次数是 j 分别取值 0~i 的求和。可得 printf() 的执行次数为:
∑ i = 1 i = n ∑ j = 1 j = i j = 1 / 6 n ( n + 1 ) ( 2 n + 1 ) \sum_{i=1}^{i=n}\sum_{j=1}^{j=i} j = 1/6 n(n+1)(2n+1) i=1∑i=nj=1∑j=ij=1/6n(n+1)(2n+1)
这里有些小伙伴会疑惑,为什么就成累加了呢?我们只考虑内部的 j 和 k 两层循环,带入具体的值想一下:
假设 j 的最大值是 5,j 会从 1 一直累加到 5 ,那么 j = 1 时,k <= 1,此时 printf 会被执行一次,之后内部循环就不满足循环条件退出了,而外部循环中的 j 就被累加到了 2 ,此时 k <= 2,此时printf会被执行两次,然后跳出循环,以此类推,执行的次数就是 1+ 2 + 3+ 4 + 5。
(4)
for (i=0; i<n; i=i*2)
{
printf("Hello World\n");
}
//该代码段内,i=i*2 我们设执行次数为k,所以跳出循环的判断条件可以简化表示为:
2 k − 1 > n 时跳出循环,即 k = l o g 2 ( n + 1 ) 时 2^k-1 > n 时跳出循环,即 k = log_2 (n+1)时 2k−1>n时跳出循环,即k=log2(n+1)时
2.1.2 大O(n)表示法
(1)对于以上四个循环例子,当 n 趋于无限大时,执行次数的系数和常数项可以忽略不计,因此,以上四个例子的 printf() 语句的时间复杂度用 O(n) 可以表示为:
-
O ( n ) O(n) O(n)
-
O ( n 2 ) O(n^2) O(n2)
-
O ( n 3 ) O(n^3) O(n3)
-
O ( l o g 2 ( n ) ) O(log_2 (n)) O(log2(n))
简单来看,多项式中有多少 n 相乘,复杂度就是 n 的几次方。
(2)常见的时间复杂度,以及当 n 区域无穷大时的效率:
(3)在用 O(n) 描述算法的时间复杂度时,我们往往一般说的是平均时间复杂度,除此之外,我们还需要考虑,最差时间复杂度,和最好时间复杂度,具体问题具体分析,才能写出更好的程序。
2.2 空间复杂度
即算法占用内存空间的大小。与时间复杂度不同,我们通常只关注最差空间复杂度。因为内存空间是硬性要求,我们必须确保所有输入的数据都有足够的内存空间。空间复杂度的推算方法与时间复杂度大致相同,只需将统计对象从 “操作数量” 转为 “使用空间大小” 我们举几个例子。
-
O ( 1 ) :与循环无关的变量 O(1):与循环无关的变量 O(1):与循环无关的变量
-
O ( l o g 2 ( n ) ) :例如递归树,分治算法 O(log_2 (n)):例如递归树,分治算法 O(log2(n)):例如递归树,分治算法
-
O ( n ) :例如数组,链表,栈,队列 O(n):例如数组,链表,栈,队列 O(n):例如数组,链表,栈,队列
-
O ( n 2 ) :例如二维数组 O(n^2):例如二维数组 O(n2):例如二维数组
-
O ( l o g 2 ( n ) ) :例如二叉树 O(log_2 (n)):例如二叉树 O(log2(n)):例如二叉树