一、算法
1.1 基本概念
算法是解题方案,不等于程序,不等于计算方法。
1.1.1 算法基本特征
- 可行性
- 确定性
- 有穷性
- 拥有足够情报
1.1.2 算法基本要素
对数据的运算和操作 | 算法的控制结构 |
---|---|
算数运算(加减乘除) | 顺序 |
逻辑运算 (与或非) | 分支 |
关系运算 (大于、小于、等于) | 循环 |
数据的传输(赋值、输入输出等) | - |
1.1.3 算法设计的基本方法
1、列举法 不实用
问题:母鸡3钱、公鸡2钱、鸡仔0.5钱。百钱百鸡,问各多少只?
列举法思路:
{
x
+
y
+
z
=
100
3
x
+
2
y
+
0.5
z
=
100
\begin{cases} x+y+z =100 \\ 3x+2y+0.5z = 100 \end{cases}
{x+y+z=1003x+2y+0.5z=100
推导:
x
,
y
,
z
∈
[
0
,
100
]
,
{
x
,
y
,
z
都是正整数
}
x,y,z \in [0,100],\{\ x,y,z都是正整数 \}\
x,y,z∈[0,100],{ x,y,z都是正整数}
列举出所有情况,代码如下所示:
#include <stdio.h>
#define N 100
int x,y,z;
void main(void){
for(x=0;x<N;x++){
for(y=0;y<N;y++){
for(z=0;z<N;z++){
if((x+y+z == 100) && (3x+2y+0.5z == 100)){
printf("母鸡有%d只,公鸡有%d只,鸡仔有%d只",x,y,z);
}
}
}
}
}
2、归纳法
3、递推法
4、递归
问题:给出一个正整数 n ,打印出1~n 的所有正整数。
代码如下:
#include <stdio.h>
void write(int n){
if(n == 0){
printf("0\n");
}else{
printf("%d\n",n);
write(n-1);
}
}
void main(){
write(10);
}
5、减半递推技术
6、回溯法
其实就是几种方法齐上阵,因为很多问题不能在只用一种方法的情况下解决。
1.2 算法复杂度
影响因素:
时间复杂度 | 空间复杂度 |
---|---|
程序语言 | 算法程序所占的空间 |
编译产生的机器语言,代码质量 | 输入的初始数据所占的存储空间 |
机器指令的执行速度 | 算法执行过程中所要的额外空间 |
问题的规模(主要因素) |
1.2.1 问题的规模函数
算法的工作量 = f(n)
1、算法中基本操作重复执行的频率 T(n) ,是问题规模 n 的某个函数 f(n) ,
记作:
T
(
n
)
=
O
(
f
(
n
)
)
T(n)=O(f(n))
T(n)=O(f(n))
tips:随着问题规模 n 的增加,算法执行时间的增长率和 f(n) 相应增加。
常见算法的复杂度
复杂度符号表示方法 | 名称 |
---|---|
O(1) | 常数阶 |
O(n) | 线性阶 |
O(n2) | 平方阶 |
O(n3) | 立方阶 |
O(log(n)) | 对数阶 |
O(2n) | 指数阶 |
示例:n*n矩阵相乘算法,三重循环 时间复杂度为 O(n3)
#include <stdio.h>
//已知a,b两个n阶方阵,求 a和b的乘积,结果保存在 c 中
void Mult_matrix(int c[][],int a[][],int b[][],int n)
{
for(int i=0;i<n;i++){//循环**n**次
for(int j=0;j<n;j++){//循环**n**次
for(int k=0;k<n;k++){//循环**n**次
c[i,j] += a[i,k]*b[k,j];
}
}
}
}
分析算法工作量的两种方法
- 平均性态
- 最坏情况复杂性
1.2.2 算法的空间复杂度
算法执行过程中所需要的最大存储空间
算法的存储量包括以下三个部分:
- 算法程序所占的空间
- 输入的初始数据所占的存储空间
- 算法执行过程中所要的额外空间
空间复杂度定义:
S
(
n
)
=
O
(
f
(
n
)
)
S(n) = O(f(n))
S(n)=O(f(n))
原地工作(in place)的算法:记作:
O
(
1
)
O(1)
O(1)
压缩存储技术
二、数据结构
2.1 数据结构的概念
相互有关联的数据元素的集合;数据元素之间的关系可以用前后件关系来描述。
一个数据结构应该包含以下两方面信息:
- 表示数据元素的信息
- 表示个数据元素之间的前后件关系
数据结构研究的主要内容 | 研究数据结构的目的 |
---|---|
数据的逻辑结构 | 时间上:提高数据处理的速度 |
数据的存储结构 | 空间上:尽量节省在数据处理过程中所占用的存储空间 |
对各种数据结构进行的运算 |
2.1.1 什么是数据结构
2.1.2 数据的逻辑结构
- 对数据元素之间的逻辑关系的描述
- 只抽象地反映数据元素之间地逻辑关系,和计算机地存储无关
- 两个要素:1、数据元素的集合,通常记为 D 2、前后件关系,记为 R
一个数据结构B可以表示为: B = ( D , R ) B=(D,R) B=(D,R)
2.1.3 数据的存储结构
概念:数据的逻辑结构在计算机存储空间中的存放形式
常用的存储结构:
- 顺序
- 链式
- 索引
2.2 数据结构的图形表示
- 数据结点:用方框表示;根节点和终端结点比较特殊
- 前后件关系:用有向线段表示
基本运算:
- 插入
- 删除
- 查找、分类、合并、分解、复制、修改…
2.3 线性结构和非线性结构
**空的数据结构:**一个数据元素都没有
2.3.1 线性结构
满足以下两个条件:
- 有且只有一个根结点
- 每个节点最多一个前件,最多一个后件
常见的线性结构: - 线性表
- 栈和队列
- 线性链表
2.3.2 非线性结构
常见的非线性结构:
- 树
- 二叉树
- 图
三、线性表及其顺序存储结构
3.1 基本概念
由n个相同类型的数据元素构成的有限序列
例如:
- 英文大写字母表 A~Z;
- 扑克牌中同花色的13张牌 2,3,4,…,Q,K,A;
结构特征:
- 数据元素在表中的位置由序号(下标)决定。数据元素之间的相对位置是线性的。
- 对于一个非空线性表,有且只有一个根节点,无前件;有且只有一个终端节点,无后件;其余的节点有且只有一个前件和后件。
线性表的存储结构:
- 顺序存储
- 链式存储
3.2 线性表顺序存储结构
两个基本特点:
- 存储空间连续
- 所有数据元素在存储空间中按照逻辑顺序依次存放
3.3 插入运算
向位置 i 插入一个元素,需要将位置 i+1 ~ n 的所有元素后移一位。
- 元素从大的位置开始后移
- 移动开目标位置
- 插入新元素
分析:
假设线性表中含有 n 个数据元素,在进行插入操作时,假定在 n+1 个位置插入元素的可能性均等,那么平均移动元素的个数为:
E
i
s
=
1
n
+
1
∑
i
=
1
n
+
1
p
i
(
n
−
i
+
1
)
=
n
2
E_{is}=\frac {1}{n+1}\sum_{i=1}^{n+1} {p_i(n-i+1)}=\frac{n}{2}
Eis=n+11i=1∑n+1pi(n−i+1)=2n
3.4 删除运算
分析:
假设线性表中含有 n 个数据元素,在进行删除操作时,假定在每个位置删除元素的可能性均等,那么平均移动元素的个数为:
E
d
l
=
1
n
∑
i
=
1
n
p
i
(
n
−
1
)
=
n
−
1
2
E_{dl}=\frac {1}{n}\sum_{i=1}^{n} {p_i(n-1)}=\frac{n-1}{2}
Edl=n1i=1∑npi(n−1)=2n−1
分析结论:
顺序存储结构的线性表,在做插入或者删除操作时,平均需要移动一半数据元素,当线性表的数据元素量较大,并且经常要对其做插入和删除操作时,这一点值得考虑。
四、栈和队列
栈和队列是特殊的线性表
4.1 栈基本运算
定义:栈(stack):只允许在一端(栈顶)进行插入和删除操作的特殊线
性表。
- 栈顶(top)
- 栈底(bottom):不许进行插入和删除
- 先进后出(FILO)或者后进先出(LIFO)的线性表
运算:
- 入栈 (需要事先检查是否栈满)
- 出栈(退栈)
- 读栈顶元素
4.2 队列基本运算
定义: 限定只能在表的一端插入,在另一端进行删除的线性表
例子:排队做核酸
- 队头(front)允许删除
- 队尾(rear) 允许插入
基本运算:
- 退队(从队头退) 指针front 后移一位
- 入队 (从队尾入队)指针 rear 后移一位,插入数据
- 先进先出、后进后出
循环队列运算: 队头咬队尾
- 入队:队尾指针 加1,当 rear = m+1 时,置rear =1
- 出队:对头指针 加1,当 front = m+1时,置 front = 1
五、线性链表
5.1 基本概念
5.2 基本运算
5.3 循环链表及其基本运算
六、树和二叉树
6.1 树的基本概念
- 有且只有一个根节点(root)
- 其余节点可分为 m 个互不相交的子集 T1、T2…Tm,其中每个子集又是一颗树,称为子树。
- 每个节点只能由一个父节点
- 每个节点可以有多个子节点。没有子节点的节点时叶子节点
- 节点的度和树的度:节点的后件个数就是这个节点的度。一颗树中各节点度数最大值叫这棵树的度。
- 层和树的深度:树结构是一种层次结构,根节点为第一层,根的子节点为第二层。一棵树中节点的最大层数叫做树的深度。
6.2 二叉树及其基本性质
- 性质1:在二叉树的第 k 层上,最多有
2
k
−
1
(
k
>
=
1
)
2^{k-1} (k>=1)
2k−1(k>=1)
个节点 - 性质2:深度为 m 的二叉树,最多有 2 m − 1 2^m-1 2m−1个节点
- 性质3:度数为0的节点(就是叶子)总是比度数为2的节点多一个。即: n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
- 性质4:具有n个节点的二叉树深度至少为 [ l o g 2 n ] + 1 [log_2n]+1 [log2n]+1,[]表示取整(不进行四舍五入)
- 性质5:具有n个节点的完全二叉树深度为 [ l o g 2 n ] + 1 [log_2n]+1 [log2n]+1
满二叉树和完全二叉树
- 满二叉树:除了叶子,所有节点都有两个子节点。共 2 n − 1 2^n - 1 2n−1个节点,n为层数。
- 完全二叉树:除了最后一层,每一层的节点数都达到最大值;在最后一层上,只缺少右边的若干个节点(只有最后一层可以缺少节点)
6.3 二叉树的存储结构
普通二叉树
- 链式存储结构
- 存储节点由两部分组成:数据域(data)和两个指针域(Lnext、Rnext)
满二叉树和完全二叉树
- 顺序存储结构(连续存储)
6.4 二叉树的 遍历(考试重点)
不重复地访问二叉树中的所有节点
- 前序遍历(DLR)(根左右)
- 中序遍历 (LDR)
- 后序遍历 (LRD)(左右根)
七、查找技术
7.1 顺序查找
从表中第一个元素开始,逐个比较,直到两者相符。否则就是查找不成功。
平均和一般元素比较,最坏的情况比较n次。
以下情况必须顺序查找
- 如果线性表是无序表(表中元素无序),不管它是顺序存储还是链式存储,都只能顺序查找
- 即使是有序线性表,如果采用链式存储,只能顺序查找
7.2 二分查找法
先确定范围,再缩小范围,直到找到或者找不到。
前提:
- 有序表
- 顺序存储结构
特点:
- 效率较高
- 最坏的情况,需要比较次数为: l o g 2 n log_2n log2n
八、排序技术
8.1 交换类排序法
基本思想: 两两比较排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序为止
方法:
- 冒泡排序
- 快速排序(综合性能最好)
冒泡排序性能分析
假设线性表长度为n,最坏情况下,需要比较次数:
n
(
n
−
1
)
/
2
n(n-1)/2
n(n−1)/2
快速排序
基本思想:任取一个元素为基准,通过一趟排序,分为左右两个子序列。然后分别对两个子序列继续进行排序,直至整个序列有序。
- 快速排序的平均时间复杂度为: O ( n ∗ l o g 2 n ) O(n*log_2 n) O(n∗log2n)
8.2 插入类排序法
基本思想: 每次将一个待排序的记录,按照其关键字大小插入到前面已经排好序的子序列的适当位置,直到全部记录插入完成为止。
方法:
- 简单插入排序
- 希尔排序
8.2.1 简单插入排序
基本思想: 把n个待排序的元素看成为一个有序表和一个无序表。开始的时候有序表只有一个元素,排序过程中每次从无序表中取第一个元素,把他的排序码依次和有序表的排序码进行比较,将它插入到有序表的适当位置,使之成为新的有序表。
最坏情况下,需要进行比较次数为:
n
(
n
−
1
)
/
2
n(n-1)/2
n(n−1)/2
8.2.2 希尔排序
基本思想: 本质上也是插入排序。先将整个序列分为若干个子序列(由相隔某个增量 h的元素组成的),分别进行直接插入排序,等整个序列元素基本有序,再对全体元素进行一次直接插入排序。直接插入排序再元素基本有序的情况下,效率非常高
增量一般取
h
i
=
n
/
2
k
(
k
=
1
,
2
,
.
.
.
,
[
l
o
g
2
n
]
)
h_i = n/2^k (k=1,2,...,[log_2n])
hi=n/2k(k=1,2,...,[log2n])
最坏情况下: 希尔排序的时间复杂度为:
O
(
n
1.5
)
O(n^{1.5})
O(n1.5)
8.3 选择类排序法
基本思想: 每一趟选出最小值,顺序放在已经排好序的子序列最后,直到排序完成
方法:
- 简单选择排序
扫描整个线性表,选择最小值,放到表的最前面;然后对剩下的子表继续同样的操作,直到子表变成空表。最坏情况下:比较次数为: n ( n − 1 ) / 2 n(n-1)/2 n(n−1)/2 - 堆排序
分为大根堆和小根堆
最坏情况,需要比较次数为: O ( n ∗ l o g 2 n ) O(n*log_2n) O(n∗log2n)