什么是数据结构?
传统上:数据结构 = 逻辑结构 + 物理结构
物理结构,研究的是,如何把数据元素存储到计算机的存储器中。
算法就是解决问题的技巧和方式。
1. 正确性 (大致分为四层次)
2. 可读性
3. 时间效率高 & 存储量低
如何计算算法效率呢?
需要事先编制好测试程序。利用计算机计时器对不同算法编制的运行时间做比较,从而确定算法效率的高低。
在计算机重新编写前,依据统计方法对算法进行估算。
由此可见: 除了软硬件之外,就是算法的好坏和问题的输入规模。
计算1~100相加
for(int i = 1, n = 100, sum = 0; i <= n; i++) { // 执行 1+n+1 次
sum = sum + i; // 执行 n 次
}
总共 2n + 2 次
int sum = 0, n = 100; // 执行1次
sum = (1 + n) * n / 2; // 执行1次
总共 2 次
所以上面的算法1和算法2的关系,是n和1的关系。
给定两个函数f(n) 和 g(n),如果存在一个整数N,使得对于所有的n > N, f(n) 总是比g(n)大,那么,我们说f(n)的渐进增长快于g(n)。
可以使用相关工具(如Excel或matlab),进行绘图。会发现,当n足够大时,似乎只与最高阶阶数相关,其它因素可以忽略不计。
判断一个算法效率时: 函数中的常数和次要项常常快于忽略,而更应该关注主项(最高项)的阶数。
在进行算法分析时,语句总的执行次数 T(n)是关于问题规模 n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级 。算法的时间复杂度,也就是时间量度,记做: T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n))它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率 相同,称作算法的渐进时间复杂度,简称为时间复杂度 。其中f(n)是问题规模n的某个函数。(意思是,看重潜力,而不是当前。)
关键是输入n与输出T(n)的关系
注: 推导大O并不难,关键是对数列的一些相关运算,更多地考虑数学知识与能力。
int i, j; // 1
for(i = 0; i < n; i++) { // n + 1
fuction(i); // 1
}
product static void function(int count) {
System.out.println(count); // 1
}
1 + n + 1 + n * 1 ⇒ 2n +2
所以,时间复杂度是: O ( n ) O(n) O(n)
int i, j; // 1
for(i = 0; i < n; i++) { // n + 1
fuction(i); // n
}
product static void function(int count) {
int j; // 1
for(j = count; j < n; j++) { // O(n)
System.out.println(j);
}
}
里面那个循环,一眼瞄上去,执行次数就是一个等差数列。不管它是从n还是n+1还是n-1开始,也不管它是1或2或0结束。总归是等差数列,最高阶是 n 2 \frac n 2 2n,也就是 O ( n ) O(n) O(n)。(作为程序员,需要的是抽象的概括能力。比如这里,一眼瞄过去知道是等差,立马就是一个 O ( n ) O(n) O(n)甩过去。)
1 + n + 1 + n * O(n) ⇒ O ( n 2 ) O(n^2) O(n2)
n++; // 1
function(n); // n * O(n)
for(i = 0; i < n; i++) { // n+2
function(i); // O(n) * (n + 1)
}
for(i = 0; i < n; i++) { // O(n^2)
for(j = i; j < n; j++) {
System.out.println(j)
}
}
product static void function(int count) {
int j; // 1
for(j = count; j < n; j++) { // O(n)
System.out.println(j);
}
}
( 1 + O ( n 2 ) + O ( n 2 ) + O ( n 2 ) ) (1 + O(n^2) + O(n^2) + O(n^2)) (1+O(n2)+O(n2)+O(n2)) ⇒ O ( n 2 ) O(n^2) O(n2)
时间复杂度(升序) | 术语 | 举例 |
---|---|---|
O ( 1 ) O(1) O(1) | 常数阶 | 1 |
O ( l o g n ) O(logn) O(logn) | 对数阶 | 3 l o g 2 n + 1 3log_2n +1 3log2n+1 |
O ( n ) O(n) O(n) | 线性阶 | 2n + 1 |
O ( n l o g n ) O(nlogn) O(nlogn) | nlogn阶 | 4 n + 3 n l o g 2 n + 1 4n +3nlog_2n +1 4n+3nlog2n+1 |
O ( n 2 ) O(n^2) O(n2) | 平方阶 | 3 n 2 + 2 n + 1 3n^2 + 2n + 1 3n2+2n+1 |
O ( n 3 ) O(n^3) O(n3) | 立方阶 | 4 n 3 + 3 n 2 + 2 n + 1 4n^3 + 3n^2 + 2n + 1 4n3+3n2+2n+1 |
O ( 2 n ) O(2^n) O(2n) | 指数阶 | 2 n 2^n 2n |
O ( n ! ) O(n!) O(n!) | 阶乘 | n! |
O ( n n ) O(n^n) O(nn) | - | - |
由与 O ( n 3 ) O(n^3) O(n3)太大了,我们没有讨论它们的意义。我们只探究前5个。
写代码时,可以用空间 换时间
空间复杂度计算公式: S ( n ) = O ( f ( n ) ) S(n) = O(f(n)) S(n)=O(f(n))
n为问题规模
f(n)为语句关于n所占存储空间的函数