1. 概念
算法用来描述对特定问题的求解步骤,它是指令的有限序列,其中每一条指令代表一个或多个操作。
软件 = 程序 + 文档
程序 = 数据结构 + 算法
软件 = 数据结构 + 算法 + 文档
算法 = 对结点集合的运算和操作 + 控制结构
2. 算法五大特征
- 有穷性:一个算法必须总是(对任何合法的输入值)在执行有穷步以后结束,且每一步都可以在有穷的时间内完成。
- 确切性:算法中每一个指令都必须有确切的含义,读者和计算机在理解时不会产生二义性。
- 可行性:一个算法是能行的,即算法中描述的操作都是可以通过执行有效次基本运算来实现。
- 输入性:一个算法有 零个或多个 输入,以刻画运算对象的初始情况。
所谓零个输入就是指算法本身给出了初始条件,int a = 5;
- 输出性:一个算法必须有一个输出或多个输出,以反映出对输入数据加工后的结果,没有输出的算法是毫无意义的。
3. 程序和算法的区别
程序与算法不同:
所谓“程序”是指对所要解决问题的各个对象和处理规则的描述,或者说是数据结构和算法的描述,因此有人说“数据结构+算法 = 程序”。
程序可以不满足算法的有穷性,例如,操作系统,它是在无限循环中执行的程序,因而不是算法。
然而可以把操作系统的各种任务看作一些单独的问题,每一个问题由操作系统中的一个子程序通过特定的算法实现,该子程序得到输出结果后便终止。
4. 时间复杂度
4.1. 前言背景(了解)
对于算法的时间效率的计算,通常是抛开与计算机硬件、软件有关的因素,仅考虑实现该算法的高级语言程序。
对于一个算法的复杂性分析:主要是对算法效率的分析,包括衡量其运行速度的时间效率,以及其运行时所需要占用的空间大小。
算法的时间复杂度是一个函数,它定性描述该算法的运行时间,时间复杂度常用 O 表述,它衡量着一个程序的好坏,时间复杂度的估算是算法题的重中之重。
4.2. 概念
计算时间复杂度首先得找到 该算法中的循环,算法中 循环执行部分的次数 就是算法的时间复杂度。
通常时间复杂度用一个问题规模函数来表达:T(n) = O(f(n))
T(n) 问题规模的时间函数
n 代表的是问题的规模 输入数据量的大小
O 时间数量级
f(n) 算法中可执行语句重复执行的次数
4.3. 计算案例
4.3.1. O(n)情况
给定N
个元素的数组a[N]
,求其中奇数多少个。
判断一个数是偶数还是奇数,只需要判断它除上2
的余数是0
还是1
,把所有数都判断一遍,并且对符合条件的情况进行计数,最后返回这个计数就是答案,需要遍历所有的数,因此代码为:
for (i = 0; i < n; i++)
if (a[i] % 2)
num++;
由代码段知,该函数中只有一层for
循环,而该循环内执行了n
次,因此时间复杂度为O(N)
。
4.3.2. O(n2)情况
求下面函数的时间复杂度。
int num = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
num++; // 两层循环,每次循环n次,因此为n*n
for (int k = 0; k < N; k++)
++num; // 一层循环,循环n次
for (int l = 0; l < 10; l++)
++num; // 一层循环,循环10次
加到一起去计算,得计算时间的复杂度的表达式:N*N+N+10
但是我们能写成O( N*N + N + 10 )
吗?
注意:
- 对于时间复杂度我们不需算出精确的数字,只需要算出这个算法属于什么量级即可。
- 如何知道它属于哪个量级呢?即我们将字母取无穷大,例如本题中字母为
N
,N
取无穷大,而10
对于N
取无穷大后没有影响,因此10
可以舍去 - 对于时间复杂度我们不需算出精确的,再转化为
N * (N+1)
,由于N
为无穷大,因此+1
也是没有影响的,原式就变成了O(N*N)
即O(N2)
。 - 这就是大 O 渐近表示法,只是一种量级的估算,而不是准确的值。
由此可得计算时间复杂度的一般规律(大O表示法)
- 如果有常数项将其置为
1
。 - 去除表达式中所有加法常数。
- 修改的表达式中 只保留最高阶项,因为只有它才会对最终结果产生影响。
- 如果最高阶项系数存在且不是1,则将其系数变为1,得到最后的表达式。
4.3.3. 冒泡排序情况
计算冒泡排序的时间复杂度。
for (i = 0; i < N-1; i++)
for (j = 0; j < N-1-i; j++)
if(a[j] > a[j+1]) //这种交换方法,仅适合 整型存储的数据
{
a[j] ^= a[j+1];
a[j+1] ^= a[j];
a[j] ^= a[j+1];
}
例如在这个冒泡排序中,我们需要将无序数组转化为有序数组的一种算法,它并不像上题一样是简单的双层嵌套循环,很容易想到它的循环次数是一个等差数列,第一次循环N-1
次,第二次N-2
次.....一直到1
。
因此为N-1 + N-2 + N-3 + ... + 1 = (N-1)*N/2
由上面所说的规律时间复杂度为O(N2)
。
- 通过上面的例子我们看出,大 O 渐近表示法去掉了对结果影响不大的项,简洁明了地表示出了时间复杂度。
- 在实际情况中一般只关注算法的最坏运行情况。
- 例如在上述冒泡排序中如果给定的数组就已经是有序的了,那么就是它的最好情况,时间复杂度为
O(N)
。 - 但是如果有非常多的数据很显然我们看不出它到底是否为最好情况,所以我们必须用最坏的期望来计算所以它是
O(N*N)
。
4.3.4. O(1)情况
int fun(int n)
{
int i = 0, num = 0;
for(; i<100; i++)
num++;
return num;
}
- 常数情况,认为是 1。
- 此时时间复杂度为
O(1)
,这里的1
不是指一次,而是常数次,该循环执行了100
次,不管n
多大,他都执行100
次,所以是O(1)
。
4.3.5. 练习1
提示: f(n) —— 算法中 可执行语句 重复执行的 次数
f(n) = 3*n5 + 2*n3 + 6*n + 10
常数项10
置为1
保留最高项3*n5
最高项3*n5
系数不为1
则除以3
T(n) = O(n5)
4.3.6. 练习2
f(n) = 8
常数项8
置为1
保留最高项1*n0
最高项1*n0
系数为1
T(n) = O(n0) = O(1)