考纲要求 💕
(一)绪论:
1.熟悉各名词、术语的含义,掌握基本概念,特别是数据的逻辑结构和存储结构之间的关系;
2.了解抽象数据类型的定义、表示和实现方法;
3.熟悉类C语言的书写规范,特别要注意值调用和引用调用的区别,输入、输出的方式以及错误处理方式;
4.理解算法五个要素的确切含义;
5.掌握计算语句频度和估算算法时间复杂度的方法。
第一章的主要是理论知识,但是也要基本了解,时间空间复杂度可能会出计算题
1 术语(逻辑结构&存储结构)
如下面思维导图:
1.1 数据结构的形式定义(语法格式)✨
1.2 数据类型 (抽象数据类型)
:是一个值的集合和定义在此集合上的一组操作的总称
- :其值不可再分的数据类型
- :其值可以再分解为若干分量的数据类型
- :↓ 1.2.1
比如C++中的bool类型就是原子类型,其值为true或false,所对应的操作可以有与、或、非等
比如C语言中的结构体struct是一种结构类型
struct Coordinate
{
int x;//横坐标
int y://纵坐标
}
其值可以再分,比如这个分为x和y 子量, 所对应的操作可以有赋值等
❗1.2.1 抽象数据类型 (ADT) ❤️✨💕
: 是指具有一定关系的以及定义在该集合上的。
作用:抽象数据类型可以使我们更容易描述现实世界。例:用线性表描述学生成绩表,用树或图描述遗传关系。
定义:一个数学模型以及定义在该模型上的一组操作。
关键:使用它的人可以只关心它的逻辑特征,不需要了解它的存储方式。定义它的人同样不必要关心它如何存储。
ADT的表示和实现💘🆚💬
ADT的表示格式不统一,我们采用下面的格式 :
ADT抽象数据类型名 {
数据对象:<数据对象的定义>
数据关系:<数据关系的定义>
基本操作:<基本操作的定义>
比如下面例子:
数据元素D,数据关系S ,最后P是下面的基本操作
有点类似抽象类,就是存储函数,这个也是数据结构+操作集
1.3 数据结构的三要素(逻辑、存储、运算)✨💕
❗ 1.3.1 逻辑结构
是指数据元素之间的逻辑关系。
结构图:✨
逻辑结构的四大分类:✨
❗ 1.3.2 存储结构
逻辑结构 相当于“纸上谈兵”——是指“这个数据应该这样存,他们之间应该具有这样、那样的关系”,但是计算机可不管这么多,因为它就那么一个硬盘,还能玩出什么花样?
:是指数据的逻辑结构在计算机中的存储形式
数据的存储结构应该正确的反映数据元素之间的逻辑关系,这是实现物理结构的重点和难点。
主要分为:
- 比如完全二叉树,它逻辑结构上很明显是树,但是在存储上可以用顺序存储(数组)也可以用链式存储(链表)
顺序存储结构 ( ..数组)
:把的元素存储在中。元素之间的关系由存储单元的邻接关系体现
-
你可以这样理解,排队坐位置,规定好了A后面是B,那么B在坐的时候一定要坐到与A相邻的位置
-
最典型的就是数组
链式存储结构 (...链表)
:不要求的元素存储在中。元素之间的关系借助指示元素存储地址的指针来表示
- 上例中这些元素逻辑上相邻,但所存放的存储单元并不是连续的(地址不一定连续)
索引存储结构
:在存储元素信息的同时,还建立附加的索引表。索引表中的每项称之为索引项,索引项一般形式是关键字+地址
散列存储结构
:根据元素的关键字出该元素的地址。计算依靠的方式称之为哈希函数
详细介绍见后面的散列表
总结:
- 若采用
顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的。 - 数据的
存储结构会影响存储空间分配的方便程度 - 数据的
存储结构会影响对数据运算的速度
1.3.3 数据运算
:施加在数据上的元素包括运算的定义和实现
- 运算的是针对
逻辑结构的,指出运算的功能 - 运算的是针对的,指出运算的
比如说:把信息插入线性表中
都是大佬啊
2. 时间空间复杂度(算法)
2.1 算法基本概念和评价
关于算法的基本概念和评价见: 算法的基本概念、算法的特性及设计要求
❗❗ 2.2 时间空间复杂度✨
2.2.1❗❗时间复杂度✨✨
时间复杂度指算法中所有语句的频度(执行次数)之和。
主要根据问题规模n确定、公式是
比如下面这个过程!
就是一个求出时间复杂度的过程,时间复杂度是T(n)=O(n);
推导出大O的方法:
B:推导大O阶的方法
推导基本方法如下
-
用常数1取代运行时间中的所有·
-
在修改后的运行次数函数中,只保留
-
如果最高项存在且不为1,则
也可以用数学公式来解释:(利用除法求无穷极限)
下面依次分析:
1. 常数阶 O(1)
高斯算法
//普通人
int i,sum=0,n=100; //执行1次
for(i=1;i<=n;i++)//执行n+1次
{
sum=sum+i;//执行n次
}
cout << sum;//执行1次
//大神高斯
int sum=0,n=100;//执行1次
sum=(1+n)*n/2;//执行1次
cout << sum//执行1次
高斯算法,很明显复杂度为O(1)
但是如果这样:
int sum=0,n=100;//执行1次
sum=(1+n)*n/2;//执行1次
sum=(1+n)*n/2;//执行2次
sum=(1+n)*n/2;//执行3次
sum=(1+n)*n/2;//执行4次
sum=(1+n)*n/2;//执行5次
sum=(1+n)*n/2;//执行6次
sum=(1+n)*n/2;//执行7次
sum=(1+n)*n/2;//执行8次
sum=(1+n)*n/2;//执行9次
sum=(1+n)*n/2;//执行10次
cout << sum//执行1次
初学者很容易被其中置入的这些代码误导
, ,
2.线性阶
如下代码由于循环体中的代码执行n次,因此其时间复杂度为O(n)
int i
for(i=0;i<n;i++)
{
//时间复杂度为O(1)的程序步骤序列
}
就是在循环中,看循环多少
3.对数阶
如下代码,由于循环中count乘以2之后,距离循环结束条件的n就更近了一步
int count = 1;
while(count < n)
{
count = count*2;
//时间复杂度O(1)的程序步骤序列
......
}
也就是说现在在问你: 有多少个count*2后会大于n,那么自然得到答案是 ,所以时间复杂度是O()
过程如下:
4. 平方阶
如下例子,存在两个for循环,很明显其时间复杂度为O(mn),且当m=n时,时间复杂度为O(n^2),所以,
int i,j;
for(i =0;i<m;i++)
{
for(j=0;j<n;j++)
{
//时间复杂度为O(1)的程序步骤序列
}
}
等于循环嵌套时候要注意下!
下面的这个例子呢? 当i=1的时候执行n次,当n=2的时候执行(n-1)次,........... 明显这是一个等差数列,n+(n-1)+(n-2)+...+1
求和易得:n+n*(n-1)/2
整理一下就是n*(n+1)/2
然后我们将其展开可以得到n^2/2+n/2。
根据我们的步骤走,保留最高次项,去掉相乘的常数就可以得到时间复杂度为:O(n^2)
public class TS {
public static void main(String[] args) {
int sum = 0;
for(int i=1;i<=100;i++) {
for(int j=i;j<=100;j++)
sum = sum + i;
}
}
}
另外还有一种情况较为常见,那就是对于递归算法其时间复杂度为:
下面例子时间复杂度为O(n)
// 计算阶乘递归Factorial的时间复杂度?
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N-1)*N;
}
就是自己调用自己,这里按照公式来写:
每次,递归了n次时间复杂度是,每次进行了一个乘法操作,乘法操作的时间复杂度一个常数项,所以这份代码的时间复杂度是
如果感兴趣的话可以读读这篇文章比较深入:# 递归算法的时间复杂度(面试)
5.线性对数阶O(nlogN)
for (int m = 1; m <= n; m++) {
int i = 1;
while (i < n)
i = i * 2;
}
下面那段就是对数阶O(logn)的代码,上面加了个for循环了n次,它的时间复杂度就是 n * O(logn),也就是了O(nlogn),归并排序的复杂度就是O(nlogn)。
2.2.2 ❗❗最坏\最好\平均时间复杂度✨✨
大家会发现代码过程(例如查找)中有这样几种情形:
- 最好时间复杂度:第—次就找到了,时间复杂度为O(1)
- 最坏时间复杂度,第n次才能找到,时间复杂度为O(n)
for (int i=0; i < n; ++i) {
if (girlArray[i] == number) {
pos = i;
break;
}
其实最好与最坏都是极端情况,发生的概率并不大。为了能更好的的表示平均情况下的时间复杂度,在这里引入了一个新的词汇
分析:
变量 number 在 girlArray 数组中出现的情况有 n + 1 种,在数组中 n 种,不在数组中 1 种。
每种情况我们要遍历的次数都不一样,我们把每种情况需要遍历的次数累加,然后再除以所有情况数 n + 1,就能得到需要遍历次数的平均值。
公式就是:
平均情况时间复杂度为:
((1+2+3+......+n-1) + n) / (n + 1) = n*(n+1)/2(n+1)
O(n)
而我们在,因为这已经是最坏的了,那么剩余的情况肯定就这个好
所以时间空间复杂度其实考虑的是最差的情况
2.2.3 ✨ 空间复杂度
算法的空间复杂度定义为:
表示随着问题规模n的增大,算法运行所需存储量的增长率与g (n)的增长率相同。
其中,n为问题的规模,g(n)为语句关于n所占存储空间的函数
- 输入数据所占空间。
- 程序本身所占空间。
- 辅助变量所占空间。
若算法执行时所需要的辅助空间相对于输入数据量而言是一个常数,则称此算法为原地工作,其空间复杂度为O(1)
若所需存储量依赖于特定的输入,则通常按最坏情况考虑。
变量—O(1)
// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
共定义了 *a n end end i 五个变量,去除常数 还是O(1)
数组—O(n)
#O(n)
list1 = 'physics', 'chemistry , 1997,2000
申请了一个数组,O(N)
long long* Fibonacci(size_t n)
{
if (n == 0)
return NULL;
long long * fibArray =
(long long *)malloc((n + 1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
波那契数列开辟了N+1个空间,故空间复杂度为O(N)
二维数组 0(n^2)
递归
// 计算阶乘递归Factorial的空间复杂度?
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N - 1)*N;
}
递归调用了n次,Factorial(N - 1)*N;
每次开辟一个空间给下一个Factorial用,
开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)
🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝🌝
祝大家学习快乐,总结.....
参考: