绪论
一、数据结构的基本概念和术语
- 数据:信息的载体。
- 数据元素:数据的基本单位。
- 数据项:组成数据元素的、有独立含义的、不可分割的最小单位。
- 组合项:可分的数据项称为组合项。
- 数据对象:具有相同性质的数据元素的集合,是数据的一个子集。
- 数据类型:一个值的集合和定义在此集合上的一系列操作的总称。
原子类型:其值不可再分的数据类型
结构类型:其值可以再分解为若干成分的数据类型固定聚合类型:变量的值由确定数目的成分按某种结构组成。
可变聚合类型:其值的成分数目不确定。抽象数据类型:抽象数据组织及与之相关的操作
多形数据类型:具有相同的数学抽象特性。
- 数据结构:相互之间存在一种或多种特定关系的数据元素的集合。
二、数据结构基本概念的关系
三、数据结构三要素
3.1 逻辑结构
- 数据元素之间的逻辑关系,即从逻辑关系上描述数据
- 它与数据的存储无关,是独立于计算机的
- 逻辑结构主要分为线性结构(线性表,栈,队列)和非线性结构(图、树,集合)
常见的逻辑结构 | 数据元素的关系 |
---|---|
集合 | 同属一个集合 |
线性结构 | 一对一 |
树形结构 | 一对多 |
图状结构或网状结构 | 多对多 |
3.2 物理结构
- 数据结构在计算机中的表示(又称映像),也称存储结构
- 包括数据元素的表示和关系的表示
- 它是用计算机语言实现的逻辑结构,它依赖于计算机语言
主要的存储结构 | 定义 | 优点 | 缺点 |
---|---|---|---|
顺序存储 | 按顺序 | 可以实现随机存取每个元素占用很少的存储空间 | 只能使用相邻的一整块存储单元 可能产生较多的外部碎片 |
链式存储 | 借助指示元素存储地址的指针 | 不会出现碎片现象 能充分使用所有存储单元 | 每个元素因存储指针而占用额外的存储空间 只能实现顺序存取 不同节点的存储空间可以不连续 同一节点内的存储空间要连续 |
索引存取 | 建立附加的索引表 | 检索速度快 | 附加的索引表额外占用存储空间 增加和删除数据时也要修改索引表 会花费较多的时间 |
散列存储 | 根据元素的关键字直接计算出元素的存储地址 也叫哈希存储 | 检索、增加和删除节点的操作都很快 | 若散列函数不好,则可能出现元素存储单元的冲突 解决冲突又会增加时空开销 |
3.3 数据的运算
- 包括运算的定义和实现
- 运算的定义是针对逻辑结构的,指出运算的功能
- 运算的实现是针对存储结构的,指出运算的具体操作步骤
四、算法的基本概念
4.1 算法的定义
对特定问题求解步骤的一种描述,是指令的有限序列,其中的每条指令都代表一个或多个操作。
4.2 算法的五个重要特性
特性 | 定义 |
---|---|
有穷性 | 一个算法必须总在有限步骤后结束,每个步骤可在有限时间内完成 |
确定性 | 算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出 |
可行性 | 算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现 |
输入 | 一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合 |
输出 | 一个算法有一个或多个输出,这些输出是与输入有特定关系的量 |
4.3 一个好算法有的四个特性
特性 | 定义 |
---|---|
正确性 | 算法应能正确解决问题 |
可读性 | 可以帮助人们理解 |
健壮性 | 当输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果 |
高效率与低存储量的需求 | 效率指算法执行的时间,存储量需求指算法执行过程中所需要的最大存储空间,两者都与问题的规模有关 |
五、时间复杂度与空间复杂度
5.1 常见时间复杂度的代码
- 常数阶 O(1)
- 线性阶 O(n)
for (i = 1; i <= n; ++i)
{
j = i;
j++;
}
- K次方阶O( n^k)
for (x = 1; i <= n; x++)
{
for (i = 1; i <= n; i++)
{
j = i;
j++;
}
}
- 对数阶 O(logN)
int binary(int n, int a[], int k)
{
int left = 0, right = n - 1;
while(l <= r) {
mid = (l + r)/2;
if(a[mid] == k)
return mid;
else if(a[mid] < k)
right = mid + 1;
else
left = mid + 1;
}
return -1;
}
- 线性对数阶 O(nlogN)
for (m = 1; m < n; m++)
{
i = 1;
while (i < n)
{
i = i * 2;
}
}
- 乘方阶 O(n*m)
for (x = 1; i <= m; x++)
{
for (i = 1; i <= n; i++)
{
j = i;
j++;
}
}
- 根号阶
bool isPrime(int n) {
int i;
if(n == 1) {
return false;
}
int sqrtn = sqrt(n);
for(int i = 2; i <= sqrtn; ++i) {
if(n % i == 0) {
return false;
}
}
return true;
}
5.2 常见时间复杂度的比较
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
5.3 时间复杂度的计算规则
5.3.1 加法规则
O(f(n))+ O (g(n))= O (max(f(n),g(n)))
5.3.2 乘法规则
O(f(n))×O(g(n))=O(f(n)*g(n))
5.4 复杂时间复杂度的计算
前置知识:
- ∑ i = 1 n i = n ( n + 1 ) 2 \sum_{i=1}^{n}i =\frac{n(n+1)}{2} ∑i=1ni=2n(n+1)
- ∑ i = 1 n k = k n \sum_{i=1}^{n}k =kn ∑i=1nk=kn
- ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 2 \sum_{i=1}^{n}i^2 =\frac{n(n+1)(2n+1)}{2} ∑i=1ni2=2n(n+1)(2n+1)
- 等差数列求和公式: S n = a 1 + a 2 + . . . + a n = n ( a 1 + a n ) 2 S_n=a_1+a_2+...+a_n=\frac{n(a_1+a_n)}{2} Sn=a1+a2+...+an=2n(a1+an)
- 等比数列求和公式: S n = a 1 1 − q n 1 − q S_n=a_1\frac{1-q^n}{1-q} Sn=a11−q1−qn
5.4.1 单循环
void func(int N) {
int num = 0;
for(int i=0; i<100; i++) {
++num;
}
}
解法: 肉眼观察法(常见时间复杂度): O ( 1 ) O(1) O(1)
void func(int N) {
int num = 0;
for(int i=0; i<N; i++) {
++num;
}
}
解法: 肉眼观察法(常见时间复杂度): O ( N ) O(N) O(N)
void func(int N,int m) {
int i = 1;
while(pow(i,m)<=N) {
i++;
}
}
解法: 简单计算法: O ( N m ) O(\sqrt[m]{N}) O(mN)
i m = N i^{m} =N im=N
i = N m i=\sqrt[m]{N} i=mN
void func(int N,int m) {
int i = 1;
while(i<=N) {
i=i*m;
}
}
解法: 简单计算法: O ( log m N ) O(\log_{m}{N}) O(logmN)
m k = N m^{k}=N mk=N
k = log m N k=\log_{m}{N} k=logmN
void func(int N) {
i=0;
while(sum<N) {
sum+=++i;
}
}
解法: 比较麻烦计算法: O ( N ) O(\sqrt{N}) O(N)
s u m = 1 + 2 + 3 + . . . + k sum=1+2+3+...+k sum=1+2+3+...+k
k ( k + 1 ) 2 = N \frac{k(k+1)}{2}=N 2k(k+1)=N
k 2 + k 2 = N \frac{k^2+k}{2}=N 2k2+k=N
k 2 = N k^2=N k2=N
k = N k=\sqrt{N} k=N
5.4.2 互不干扰双循环
void func(int N){
int count=0;
for(int i=1;i<=N;i*=2){
for(int j=1;j<=N;j++){
count++;
}
}
}
解法: 乘法法则: O ( N log N ) O(N\log{N}) O(NlogN)
每层计算同- 单循环
5.4.3 干扰但++双循环
void func(int N){
int m=0;
for(int i=1;i<=N;i++){
for(int j=1;j<=2*i;j++){
m++;
}
}
}
解法: 干扰公式法: O ( N 2 ) O(N^2) O(N2)
∑ i = 1 N ∑ j = 1 2 i 1 = 2 ∑ i = 1 N i = n ( n + 1 ) \sum_{i=1}^{N} \sum_{j=1}^{2i} 1=2\sum_{i=1}^{N}i=n(n+1) ∑i=1N∑j=12i1=2∑i=1Ni=n(n+1)
5.4.4 三层以下图形计算法
int func(int N) {
int num = 0;
for(int i=0; i<N; ++i) {
for(int j=0; j<i; ++j) {
for(int k=0; k<j; ++k) {
num++;
}
}
}
}
解法: 图像法: O ( N 3 ) O(N^3) O(N3)
5.4.5 干扰且非++恶心双循环
void func(int N){
int m=0;
for(int i=1;i<=N;i*=2){
for(int j=1;j<=i;j++){
m++;
}
}
}
解法: 干扰递推法: O ( N ) O(N) O(N)
外次数 | i | j<=i | 内次数 |
---|---|---|---|
1 | 2 0 2^{0} 20 | j ≤ 1 j\le1 j≤1 | 2 0 − 1 2^{0}-1 20−1 |
2 | 2 1 2^{1} 21 | j ≤ 2 j\le2 j≤2 | 2 1 − 1 2^{1}-1 21−1 |
3 | 2 2 2^{2} 22 | j ≤ 4 j\le4 j≤4 | 2 2 − 1 2^{2}-1 22−1 |
… | … | … | … |
k | 2 k − 1 2^{k-1} 2k−1 | j ≤ 2 k − 1 j\le2^{k-1} j≤2k−1 | 2 k − 1 − 1 2^{k-1}-1 2k−1−1 |
等比数列求和:
a 1 1 − q n 1 − q = 1 ∗ 1 − 2 k 1 − 2 = 2 k − 1 a_{1} \frac{1-q^{n} }{1-q} =1*\frac{1-2^{k}}{1-2} =2^k-1 a11−q1−qn=1∗1−21−2k=2k−1
2 k ≤ N ⇒ k = log 2 N 2^k\le{N}\Rightarrow k=\log_{2}{N} 2k≤N⇒k=log2N
2 k − 1 = N − 1 2^k-1=N-1 2k−1=N−1
5.4.6 递归
5.4.6.1 递推法
汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?
伪代码:
算法 Hanoi (𝐵,𝐷,n) // n个盘子𝐵到𝐷
if n = 1 then move (𝐵,𝐷) // 1个盘子𝐵到𝐷
else Hanoi (𝐵,𝐶,n − 1)
move (𝐵,𝐷)
Hanoi (𝐶,𝐷,n − 1)
时间复杂度:
T ( n ) = { 1 n = 1 2 T ( n − 1 ) + 1 n > 1 T(n)=\begin{cases}{1} & n=1 \\{2T(n-1)+1} & n>1 \end{cases} T(n)={12T(n−1)+1n=1n>1
递推法:
已知 T ( n ) = 2 T ( n − 1 ) + 1 T(n)={2T(n-1)+1} T(n)=2T(n−1)+1,那么可知
T ( 1 ) = 2 T ( 0 ) + 1 = 2 0 + 2 0 − 1 T(1)=2T(0)+1=2^0+2^0-1 T(1)=2T(0)+1=20+20−1
T ( 2 ) = 2 T ( 1 ) + 1 = 2 1 + 2 1 − 1 T(2)=2T(1)+1=2^1+2^1-1 T(2)=2T(1)+1=21+21−1
T ( 3 ) = 2 T ( 2 ) + 1 = 2 2 + 2 2 − 1 T(3)=2T(2)+1=2^2+2^2-1 T(3)=2T(2)+1=22+22−1
…
T ( n ) = 2 T ( n − 1 ) + 1 = 2 n − 1 + 2 n − 1 − 1 = 2 n − 1 T(n)=2T(n-1)+1=2^{n-1}+2^{n-1}-1=2^n-1 T(n)=2T(n−1)+1=2n−1+2n−1−1=2n−1
因此
T ( n ) = 2 n − 1 ⇒ O ( 2 n ) T(n)=2^n-1 \Rightarrow O(2^n) T(n)=2n−1⇒O(2n)
5.4.6.2 Master定理
递归定义: T ( n ) = { O ( 1 ) n = n 0 a T ( n b ) + f ( n ) n > n 0 T(n)=\begin{cases}{O(1)} & n=n_0 \\{aT(\frac{n}{b})+f(n)} & n>n_0 \end{cases} T(n)={O(1)aT(bn)+f(n)n=n0n>n0
比较 n log b a n^{\log_{b}{a}} nlogba和f(n)的关系,得出T(n)
规则1:
如果 f ( n ) = O ( n log b a − ε ) , ε > 0 f(n)=O(n^{\log_{b}{a}-\varepsilon}),\varepsilon>0 f(n)=O(nlogba−ε),ε>0为常数,则 T ( n ) = Θ ( n log b a ) T(n)=\Theta (n^{\log_{b}{a}}) T(n)=Θ(nlogba)
规则2:
如果 f ( n ) = Θ ( n log b a ) , ε > 0 f(n)=\Theta (n^{\log_{b}{a}}),\varepsilon>0 f(n)=Θ(nlogba),ε>0为常数,则 T ( n ) = Θ ( n log b a log n ) T(n)=\Theta (n^{\log_{b}{a}}\log{n}) T(n)=Θ(nlogbalogn)
规则3:
如果 f ( n ) > Ω ( n log b a + ε ) f(n)>\Omega (n^{\log_{b}{a}+\varepsilon }) f(n)>Ω(nlogba+ε),且 ε > 0 \varepsilon>0 ε>0为常数,且 ∃ n 0 \exists n_0 ∃n0,当 n > n 0 n>n_0 n>n0时, a f ( n b ) ≤ c f ( n ) af(\frac{n}{b})\le cf(n) af(bn)≤cf(n), c < 1 c<1 c<1为常数,则 T ( n ) = Θ ( f ( n ) ) T(n)=\Theta (f(n)) T(n)=Θ(f(n))
5.4.6.3 递归树
递归树的规则为:
(1) 每层的节点为 T ( n ) = k T ( n m ) + f ( n ) T(n) = kT( \frac{n}{m} ) + f(n) T(n)=kT(mn)+f(n)中的f(n)在当前的 n m \frac{n}{m} mn下的值。
(2) 每个节点的分支数为k。
(3) 每层的右侧标出当前层中所有节点的和。
5.5 空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。