什么是数据结构
定义:数据结构 是指所有数据元素以及数据元素之间的关系,可以看作是相互之间存在着某种特定关系的数据元素的集合,即把数据结构看成是带结构的数据元素的集合。(逻辑结构、存储结构、数据运算)
逻辑结构(数据结构):
面向用户呈现
- 集合:同属于一个集合,无其他关系
- 线性结构:一对一,典型的是线性表。
- 树形结构:一对多,典型的二叉树。
- 图形结构:多对多。
存储结构:
面向计算机存储
- 顺序存储结构:把逻辑上相邻的节点存储在物理位置上相邻的存储单元里,节点之间的逻辑关系由存储单元的邻接关系来体现,通常使用数组来描述。
- 优点:节省存储空间(存储单元全用于存放节点数据,节点之间的逻辑关系没有占用额外的存储空间);可实现对节点的随机存取(每个节点对应一个序号,由该序号可以直接计算出节点的存储地址)
- 缺点:不便于修改,对节点插入、删除时,可能要移动一系列的节点
- 链式存储结构:该结构不要求逻辑上相邻的节点在物理位置上也相邻,节点间的逻辑关系是由附加的指针字段表示的,通常使用指针来描述。
- 优点:便于修改,插入、删除时,只需要修改指针域,不必移动节点
- 缺点:存储空间利用率较低,因为分配给数据的存储单元有一部分被用来存储节点之间的逻辑关系了;由于逻辑上相邻的节点存储空间不一定相邻,所以不能对节点进行随机存取
- 索引存储结构:该结构在存储节点信息的同时,还建立附加的索引表,索引表每一项为索引项,索引项一般为(关键字,地址),其中关键字唯一标识一个节点,地址是指向节点的指针。
- 优点:这种带有索引表的存储结构大大提高了数据的查询速度;线性结构采用索引存储后可以对节点进行随机访问;插入、删除运算时,只需移动索引表中对应节点的存储地址,不必移动节点表中节点的数据,保持较高的修改速率
- 缺点:增加了索引表,降低了存储空间的利用率
- 散列(哈希)存储结构:根据节点的关键字通过哈希(散列)函数直接计算出一个值,并将该值作为该节点的存储地址
- 优点:查找速度快,只需要给出节点关键字,可立即计算出节点存储地址
- 缺点:只存储节点数据,不存储节点之间的逻辑关系;只适合要求对数据进行查找和插入的场合
什么是算法
定义:算法是对特定问题的求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示计算机的一个或多个操作。
算法的五个特性
- 有穷性
- 确定性
- 有输入
- 有输出
- 可行性
算法分析
算法设计目标
- 正确性
- 可使用性
- 可读性
- 健壮性
- 高效率和低存储需求
算法效率分析(时间复杂度)
时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。
定义:算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 输入大小n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。
当 T(n) = c,c 为一个常数的时候,我们说这个算法的时间复杂度为 O(1);如果 T(n) 不等于一个常数项时,直接将常数项省略
T(n) = 2,时间复杂度为 O(1)。
T(n) = n + 29,时间复杂度为 O(n)
高次项对于函数的增长速度的影响是最大的。n^3 的增长速度是远超 n^2 的,同时 n^2 的增长速度是远超 n 的,同时因为要求的精度不高,所以我们直接忽略低此项
T(n) = n^3 + n^2 + 29,此时时间复杂度为 O(n^3)
函数的阶数对函数的增长速度的影响是最显著的,所以我们忽略与最高阶相乘的常数
T(n) = 3n^3,此时时间复杂度为 O(n^3)
综合起来:如果一个算法的执行次数是T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。
时间复杂度分析的基本策略是:从内向外分析,从最深层开始分析。如果遇到函数调用,要深入函数进行分析
- 对于一个循环,假设循环体的时间复杂度为 O(n),循环次数为 m,则这个循环的时间复杂度为 O(n×m)
- 对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c…,则这个循环的时间复杂度为 O(n×a×b×c…)。分析的时候应该由里向外分析这些循环
- 对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度
- 对于条件判断语句,总的时间复杂度等于其中时间复杂度最大的路径的时间复杂度
O(1)<O(logn)<O(√n)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)<O(nⁿ)
常数阶:O(1)
线性阶:O(n)
对数阶:O(logn)
基本运算是i=i*2,设其执行时间为T(n),则2 T(n)<=n,即T(n)<=log 2n=O(log 2n)。void fun(int n) { int i=l; while(i<=n) i=i*2; }
假设循环次数为 t,则循环条件满足 2^t < n。 可以得出,执行次数t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。void aFunc(int n) { for (int i = 2; i < n; i++) { i *= 2; printf("%i\n", i); } }
在程序中,执行频率最高的语句为“x=2*x”。设该语句共执行了 t次,则2 t+1=n/2,故t=log 2(n/2)-1=log 2n-2,得 T(n)=O(log 2n)。x=2; while(x<n/2) x=2*x;
线性对数阶:O(nlogn)
一个算法所需时间由下述递归方程表示,试求出该算法的时间复杂度的级别(或阶)。
式中,n是问题的规模,为简单起见,设n是2的整数幂。
设n=2 k(k>=0),根据题目所给定义,有,由此,可得一般递推公式
,进而,可得
,即
,即为
。
语句int num1, num2;的频度为1;int num1,num2; for(int i=0;i<n;i++){ num1 += 1; for(int j=0;j<=n;j *= 2){ num2 += num1; } }
语句i=0;的频度为1;
语句i
O(n的次方)例题:
算法的基本运算是i++,设其执行时间为T(n),则有,T(6)*T(n)*T(n)<=n,即T(n) 3<=n。故有,void fun (int n){ int i=0; while(i*i*i<=n) i++; }
。
设循环体共执行T(n)次,每循环一次,循环变量y加1,最终T(n)=y。故(T(n)) 2<=n,解得 T(n)=O(n 1/2)。y=0; while((y+1)*(y+1)<=n) y=y+1;
a[i][j]=0是基本语句,内循环执行m次,外循环执行n次,共执行了m*n次,所以 T(m, n)=O(m*n)for(i=0;i<n;i++) for(j=0;j<m;j++) a[i][j]=0;
平方阶:O(n²)
当所有相邻元素都为逆序时,则最后一行的语句每次都会执行。此时,for(i=n-l;i>l;i--) for(j=1;j<i;j++) if (A[j]>A[j+l]) A[j]与 A[j+1]对换; 其中n为正整数,则最后一行的语句频度在最坏情况的时间复杂度
![]()
int m=0, i, j; for(i=l;i<=n;i++) for(j=1;j<=2 * i;j++) m++; 求m++执行次数
![]()
立方阶:O(n³)
x++是基本语句,for(i=1;i<=n;i++) for(j =1;j<=i;j ++) for(k=1;k<=j;k++) x++;
![]()
公式:![]()
指数阶:O(2ⁿ)
long aFunc(int n) { if (n <= 1) { return 1; } else { return aFunc(n - 1) + aFunc(n - 2); } }
显然运行次数,T(0) = T(1) = 1,同时 T(n) = T(n - 1) + T(n - 2) + 1,这里的 1 是其中的加法算一次执行。
显然 T(n) = T(n - 1) + T(n - 2) 是一个斐波那契数列,通过归纳证明法可以证明,当 n >= 1 时 T(n) < (5/3)^n,同时当 n > 4 时 T(n) >= (3/2)^n。
所以该方法的时间复杂度可以表示为 O((5/3)^n),简化后为 O(2^n)。
阶乘阶
f(n)=n!时,时间复杂度为O(n!),可以称为阶乘阶
算法存储空间分析(空间复杂度)
空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。
定义:空间复杂度是对一个算法在运行过程中临时占用的存储空间大小的量度,一般也作为问题规模n的函数,记作:S(n)=O(g(n))
存储空间一般包括三个方面:
- 输入数据所占用的存储空间;
- 指令常数变量所占用的存储空间;
- 辅助(存储)空间。
一般地,算法的空间复杂度指的是辅助空间。如:
- 一维数组a[n]:空间复杂度 O(n)
- 二维数组a[n][m]:空间复杂度 O(n*m)
空间复杂度比较常用的有:O(1)、O(n)、O(n²)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
int i = 1; int j = 2; ++i; j++; int m = i + j;
代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)
int[] m = new int[n] for(i=1; i<=n; ++i) { j = i; j++; }
这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)