一、“数据结构说来说去也就只有那几种”
说到数据结构,我们讨论的只有固定的以下几种:集合、数组、链表(线性表)、树、图,这几大类:
- 集合 { , , , }
从数学/数据结构的领域来看,当我们谈论“集合”(Set)时,我们通常指的是一个不包含重复元素的数据结构。因此,集合中的元素是唯一不重复的;数据元素之间的关系是并列的。
- 线性表(1 v 1)
- 树(1 v n)
- 图(n v n)
还有一种比较特殊的Hash结构,本身使用数组存储的,但访问不是线性的,而是<key,value>的特殊访问方式,先放在这里,后面进度到了再说;
二、“当同一个问题有多种解决方式时,如何评判各种方式的好坏呢?时间T(n)/空间复杂度S(n)”
(一)关于时间效率
T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n)// T代表Time
1. 题目
写程序计算给定多项式,在给定点x处的值;
f
1
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
a
3
x
3
+
…
…
+
a
n
x
n
f1(x) = a_0+a_1x+a_2x^2+a_3x^3+……+a_nx^n
f1(x)=a0+a1x+a2x2+a3x3+……+anxn
f
2
(
x
)
=
a
0
+
x
(
a
1
+
x
(
a
2
+
x
(
…
…
(
a
n
−
1
+
x
∗
a
n
)
f2(x)=a_0+x(a_1+x(a_2+x(……(a_{n-1}+x*a_n)
f2(x)=a0+x(a1+x(a2+x(……(an−1+x∗an)
2. 两种实现方式
a) 直接模拟(运行时间长很多)
double f1(int n, double a[], double x){
double p = a[0];
for (int i=1;i<=n;i++){
p = p + a[i]*Math.pow(x,i);
}
return p;
}
b) 标准程序
double f2(int n, double a[], double x){
double p = a[n];
for (int i=n;i>0;i--){
p = a[i-1] + x*p;
}
return p;
}
3. 计算时间复杂度(*次数)
- 直接模拟
- 基本操作:Math.pow(x,i); (1<=i<=n):x的i次方,每个循环里有进行i次乘法;
- T 1 ( n ) = 1 + … … + n = ( 1 + n ) ∗ n 2 = O ( n 2 ) T_1(n) = 1+……+n = \frac{(1+n)*n}{2} = O(n^2) T1(n)=1+……+n=2(1+n)∗n=O(n2)
- 标准程序
- 基本操作:每个循环里只有1次乘法;
- T 2 ( n ) = 1 ∗ n = n = O ( n ) T_2(n) = 1*n = n = O(n) T2(n)=1∗n=n=O(n)
- 所以当n的规模够大的时候,f2()运行一定是比f1()更快的;
4. 测试运行时间
f1()程序运行时间:2.9E-6/毫秒ms
f2()程序运行时间:1.7E-6/毫秒ms
+ Java中测试代码块运行时间:System.currentTimeMillis();
long starTime = System.currentTimeMillis();
// Your Code Block
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:"+(endTime-starTime)+"/毫秒ms");
Q:当函数运行时间太短,测试结果为0时,如何测试准确运行时间?A:重复!
+ 直观感受n与O(n)的联系
- logn:按说是以2为底数的,但也只是常数级别的所以不重要,默认写法忽略底数;
- 遇到一个 O ( n 2 ) O(n^2) O(n2)的算法,第一反应是能不能想办法降成 O ( n ∗ l o g n ) O(n*logn) O(n∗logn),效率提高会是很显著的;
- 修正:第二行第二列的f(n)改成:f(n) = n*logn;
(二)关于空间使用
S ( n ) = O ( n ) S(n) = O(n) S(n)=O(n)// S代表Space
1. “空间复杂度其实就是申请辅助空间做现在的事情”
- 申请几个变量:O(1)
- 申请一维数组、链表、队列、栈、Hash:O(n)
- 申请二维数组:O(n^2)
(到平方级别的辅助空间需求就已经很过分了,基本就用到这些)
2. 题目
写程序实现一个函数PrintN,使得传入一个正整数为N的参数后,能顺序打印从1到N的全部正整数;
3. 两种实现方式
a) for循环
void printNCirculation(int N){
for (int i=1; i<=N ; i++){
System.out.println(i);
}
}
b) 递归
void printNRecursion(int N){
if (N>0){
printNRecursion(N-1);
System.out.println(N);
}
}
4. 测试
- 测试数据:N = 100, 1000, 10000, 100000……
- 测试结果:
- Recursion递归:辅助栈 空间复杂度O(n),在10w测试时报错:StackOverflowError 栈溢出;
- for循环:辅助变量i 空间复杂度O(1),正常运行无报错;
5. 测试结论
- 递归的代码整洁易理解,但计算机很不愿意跑递归程序;
- 递归因为要保存每层的运行数据,对空间的占用是很恐怖的,程序很容易崩溃;