Chapter 1 绪论
软件开发知识体系
- 项目管理开发: 软件工程
- 软件逻辑设计: 数据结构与算法
- 程序编程编译: 编译原理
- 软件运行环境: 操作系统/数据库
软件开发过程
- 问题理解
- 算法设计
- 数据结构设计
- 算法分析
- 程序设计
- 程序实现
操作系统
操作系统是一组控制和管理计算机硬件和软件资源,合理地对各类作业进行调度,以及方便用户使用的程序的集合。
主要功能
- 处理机管理: 进程管理
- 控制: 程序的创建, 撤销, 状态转换
- 调度: CPU分配
- 同步: 多个进程协调
- 通信: 进程信息交换
- 储存器管理: 内存管理
- 分配: 为每个作业分配内存 静态分配 动态分配
- 保护: 确保用户程序只在自己内存内运行 互不干扰
- 映射: 地址空间 内存空间 地址映射
- 扩展: 虚拟储存技术扩充内存容量 请求调入, 置换
- 设备管理: I/O管理
- 缓冲: 缓和CPU和I/O速度不匹配矛盾
- 分配: 根据进程I/O请求, 为之分配所需设备
- 驱动: 设备处理, 实现CPU与设备控制器之间的通信
- 文件管理: 用户文件, 系统文件管理
- 储存: 储存空间分配 回收
- 组织: 建立目录项, 按名存取
- 读写: 外村读写数据
- 安全: 放置未核准用户存取, 防止不正确使用文件
- 用户接口: 提供有好的接口, 方便用户使用
- 图形用户接口
- 命令接口
- 程序接口
软件程序编译
- 操作系统依赖: 高级语言 --> 翻译程序 --> 机器码
- 不依赖操作系统: 高级语言 --> 翻译程序 --> 中间语言 --> 执行虚拟机
编译步骤
源程序字符串 --> 词法分析器 --> 单词流 --> 词法分析器 --> 语法树 --> 语义分析器 --> 中间代码序列 --> 代码优化器 --> 目标代码生成器 --> 目标程序
计算机软件数据管理
位于用户与操作系统之间的数据管理软件
功能
- 数据定义
- 数据操作
- 数据库运行管理
- 数据组织, 储存, 管理
- 数据库建立维护
- 数据通信接口
SQL
- DDL definition
- DML manipulation
Chapter 2 数据结构
线性表
- 数据元素一一对应, 顺序关系
- 位序, 表长, 前驱, 后继, 除第一个和最后一个, 唯一前驱和后继
顺序储存结构
- 地址连续 依次存放 起始地址
- 随机存取
- 定义{基址, 长度length, 容量size}
- 插入操作: E = n / 2 E=n/2 E=n/2 O ( n ) O(n) O(n)
Status List_Insert(ListPtr L, int pos, ElemType elem){
Status status = range_error;
int len = L->length,i;
if (len = MAXSIZE) status = overflow;
else if (1<= pos && pos <=len+1){
for(i=len;i>=pos;i--)
L->elem[i+1] = L->elem[i]; /* 数据元素后移一个位置*/
L->elem[pos] = elem;
L->length ++; /* 表长加1 */
status = success;
}
return status;
}
- 删除操作: E = n − 1 2 E=\frac{n-1}{2} E=2n−1 O ( n ) O(n) O(n)
Status List_Remove(ListPtr L,int pos){
Status status = range_error;
int len = L->length, i;
if(1<= pos && pos<=len){
for(i=pos;i<len;i++)
L->elem[i] = L->elem[i+1]; /* 数据元素前移一个位置*/
L->length --; /* 表长减1 */
status = success;
}
return status;
}
链式储存结构
- 数据域+指针=节点
- 头指针 带不带头结点皆可
- 查找
Status List_Locate(ListPtr L, ElemType elem, int *pos){
Status status = range_error;
ListNodePtr p = (*L)->next; /*p指向第一个元素结点*/
int i=1;
while(p !=NULL){ /* p指向的结点存在才能向下比较*/
if ( p->data == elem ) break; /* 比较 */
i++;
p=p->next; /*指针后移,同时计数器加1*/
}
if(p) { /*找到给定结点*/
*pos = i;
status = success;
}
return status;
}
- 插入
Status List_Insert(ListPtr L, int pos, ElemType elem){
Status status;
ListNodePtr pre,s;
status = List_SetPosition(L,pos-1,&pre); /*找插入位置的前驱*/
if (status ==success){
s=(ListNodePtr)malloc(sizeof(ListNode));
if (s){
s->data=elem;
s->next=pre->next;
pre->next=s;
}
else
status =fatal;
}
return status;
}
- 删除
Status List_Remove(ListPtr L, int pos){
Status status;
ListNodePtr ptr,q;
status =List_SetPosition(L, pos-1, &ptr); /*找插入位置的前驱*/
if(status ==success){
q=ptr->next;
ptr->next=q->next;
free(q);
}
return status;
}
- 创建
- 建立一个“空表”
- 输入数据元素an,建立结点并插入到单链表;
- 输入数据元素an-1,建立结点并插入到单链表;
- 依次类推,直至输入a1为止
p->next=L->next;
L->next=p;
顺序储存链式储存对比
顺序插入删除不方便 适宜查找静态操作 容量固定 额外内存申请开销
长度变化大 链表
渐进复杂度
O
(
g
(
n
)
)
→
0
≤
f
(
n
)
≤
c
g
(
n
)
O(g(n)) \rightarrow 0\le f(n) \le cg(n)
O(g(n))→0≤f(n)≤cg(n)
a
0
+
a
1
n
+
.
.
.
+
a
d
n
d
=
Θ
(
n
d
)
a_0+a_1n+...+a_dn^d=\Theta(n^d)
a0+a1n+...+adnd=Θ(nd)
分治
- 问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质
- 问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
归并排序
树
- 一对多 根节点没有前驱 其他只有一个前驱 叶子没有后继
- 深度
二叉树
- 左右子树不能颠倒
- 满二叉树: 深度为k 有2^k-1个节点
- 完全二叉树: 每个节点都与深度相同的满二叉树一一对应
- 顺序储存: 必须为完全二叉树 i左孩子下标为2i 添加虚节点转为完全二叉树
- 链式储存: {data, *lc, *rc} 三叉{data , lc,rc,parent}
- 遍历
- 先序, 中序, 后序
void preorder(BiTree T){
if(T!=NULL){
printf("%d\t",T->data);
preorder(T->lchild);
preorder(T->rchild);
}
}
- 层次
- 初始化队列,根节点入队列
- 如果队列不空,则出队列并访问该节点;该节点左孩子入队列,右孩子入队列;如果队列为空,则层次遍历结束
- 树的存储结构
- 双亲表示法
- 孩子表示法
- 孩子兄弟表示法
- 二叉树与树互换
图
-
树的存储
- 邻接矩阵
- 邻接表
- 无向图: n顶点 e条边 需要n个头结点和2e个链表节点 顶点Vi的度=链表中链表节点数
- 有向图: 逆邻接表 邻接表
-
一个图的邻接矩阵表示是唯一的;邻接表表示不唯一。邻接表中各边表结点的次序取决于建立算法和及输入边的次序.
-
邻接表(逆邻接表)中,每个边表对应邻接矩阵中的一行(或一列);边表中结点的个数等于邻接矩阵中的一行(或一列)非0元素的个数。
-
邻接表或逆邻接表的空间复杂度为S(n,e)=O(n+e)。若图中的边数e远远小于n2,称为稀疏图,其邻接表比邻接矩阵要节省存储空间。当边数e接近n2 (无向图:e接近n(n-1)/2;有向图:e接近n(n-1))时,称为稠密图,考虑链域占空间,应选择邻接矩阵存储为宜。
-
求有向图顶点的度,采用邻接矩阵比邻接表结构方便。在邻接表结构中,求顶点的出度容易,入度困难。逆邻接表中,求顶点的入度容易,出度困难。
-
判断边,邻接矩阵比邻接表容易;求边数:邻接矩阵中花费的时间复杂度为O(n2),邻接表中花费的时间复杂度为O(n+e)
-
图的遍历
- 深度优先遍历
- 广度优先遍历: 栈
贪心算法
- 简单, 高校, 可能不是最优解
- 活动安排
public static int greedySelector(int [] s, int [] f, boolean a[])
{
int n=s.length-1;
a[1]=true;
int j=1;
int count=1;
for (int i=2;i<=n;i++) {
if (s[i]>=f[j]) {
a[i]=true;
j=i;
count++;
}
else a[i]=false;
}
return count;
}
3.Dijkstra 算法
- 无向图转换为有向图不会对计算造成影响
- 非负环不会对计算造成影响
Chapter 3 操作系统
进程
一个正在执行中的程序实例
- 动态性: 正在执行, 存在生命周期
- 并发: 可以同其他进程一起推进
- 独立: 地址空间相互独立
- 异步: 各自独立 不可预知速度执行
- 结构性: 进程控制块+程序段+数据段
状态转换
- 就绪: 分配到除CPU以外所有资源, 只要获得CPU, 立即执行
- 执行: 正在执行
- 阻塞: 由于发生某事件暂时无法执行
- 新建状态: 已构造了进程标识符, 表格, 程序还未进入主存
- 终止: 不再有执行资格, 表格和其他信息暂时保留
- 挂起: 不再参与CPU竞争, 进程被交换到外存
挂起原因
- 进程全部阻塞 处理机空闲
- 负荷过重, 空间紧张
- 操作系统需要, 挂起后台, 服务进程
- 终端用户的请求
- 父进程请求
特征
- 不能立即执行
- 挂起条件独立与阻塞
- 使之挂起的进程: 自身, OS, 父进程
- 激活挂起进程: 实施挂起操作的进程
阻塞与挂起
-
阻塞: 是否等待
-
挂起: 是否被换出内存
-
就绪/挂起: 只要调入内存就可以执行
-
阻塞/挂起: 进程在外存. 等待事件
进程控制块PCB
- 作用
- 存在的唯一标志
- 常驻内存
- 块信息
- 标识符: 内部 操作系统赋予一个整数 外部 创建者提供 用户访问时使用
- 处理机状态: 寄存器
- 程序调度信息
- 进程控制信息 程序和数据地址
创建进程
pid = fork() 父子均在下一条语句执行
- 子进程 pid=0 父进程为所创建子进程标识
- 子进程是父进程的精确复制
- 通过exec()调用族, 加载新的程序
- fork()和exec()结合使用
线程
- 调度并分派部分称为线程 资源所有权称为进程
- 拥有 线程控制块 程序计数器 寄存器 , 其他不拥有资源
- 独立调度和分派的基本单位
- 可并发执行 共享资源
进程与线程
- 进程是资源分配的独立单位
- 线程是独立调度的基本单位
- 引入线程更好的并发性
- 拥有的资源不同
- 管理, 切换进程开销显著大于线程
- 同一进程同步 通信方便
- 线程切换不许系统内核干预
线程类型
- 用户级线程: 线程创建, 撤销和切换等全部由应用程序完成, 操作系统不知道线程的存在 调度更灵活 但是用户线程引起整个进程阻塞 削弱线程的并发性
- 内核级线程: 均由系统内核完成 可以调度到多个处理器 进程一个线程被阻塞, 其他也可以调度 但是开销大
- 混合线程: 多个线程被映射到一个或较少的内核级线程
进程间同步
互斥
- 资源共享–>互斥
- 进程合作–> 同步
- 必须互斥使用的资源称为临界资源
- 访问临界区的那段代码称为临界区
- 临界区使用原则: 空闲让进 忙则等待 有限等待 让权等待
信号量
- 信号量原子操作: wait(s)/P(s) signal(s)/V(s)
- 互斥信号量/资源信号量
进程间通信
- 进程间通信:共享储存区/消息传递
- 消息格式: 消息头-消息体
- 消息同步: 发送进程发送消息, 若没有空闲的消息缓冲区 则发送进程阻塞; 接受进程若没有消息可接受, 则接受进程阻塞 直到一条消息到达
- send receive
- 阻塞发送 阻塞接收/不阻塞发送 阻塞接收/ 不阻塞发送 不阻塞接收
- 直接寻址/间接寻址:邮箱
- 消息传递互斥:
- 多个并发执行的发送进程和接收进程共享一个邮箱box,且box的初始状态为仅包含一条空消息;
- 采用“不阻塞发送,阻塞接收”方式传递消息;
- 若邮箱中存在一条消息,则允许一个进程进入临界区。
- 若邮箱为空,则表明有一个进程位于临界区,其它试图进入临界区的进程必须阻塞。
- 只要保证邮箱中最多只有一条消息,就能保证只允许一个进程进入临界区,从而实现进程互斥使用临界资源。
程序装入和链接
链接
由链接程序(Linker)将编译后形成的一组目标模块,以及它们所需要的库函数链接在一起,形成一个完整的装入模块。
- 静态链接: 程序运行前, 连接成一个完整的装配模块
- 相对地址修改
- 缺点: 不利于代码共享 不利于模块独立升级 浪费储存空间
- 装入时链接: 边装入边链接
- 优点: 便于各个模块的升级 代码共享
- 缺点: 可能链接一些不会执行的模块 装入后不能移动位置
- 动态链接: 需要该目标模块时再找到模块调入内存
- 优点: 加快程序装入, 节省内存空间
装入
-
装入内存
-
地址重定位: 逻辑地址变为物理地址
-
分类
- 绝对装入: 编译时就知道驻存的具体位置, 产生绝对地址的目标代码 不需对程序和数据地址修改 采用符号地址
- 简单, 每次必须装入统一内存区 不适合多道程序系统
- 静态重定位: 采用相对地址 必须重定位
- 重定位后不能移动 不利于内存有效利用
- 动态重定位: 程序运行时动态进行 需要硬件支持
- 不必连续存放在内存中 便于共享 有利于紧凑 碎片问题 主流
- 但需要硬件支持 算法复杂
- 绝对装入: 编译时就知道驻存的具体位置, 产生绝对地址的目标代码 不需对程序和数据地址修改 采用符号地址
编译原理
- 冯诺依曼体系: 控制器, 存储器, 处理器
- 变量: 储存单元抽象, 赋值是修改储存单元的抽象
- 编译步骤
- 词法分析: 输入字符串, 识别单词符号
- 语法分析: 根据语法规则, 将符号构成语法单位, 语法检查
- 语义分析: 根据语义规则, 初步编译
- 优化: 中间代码等价变换, 代码更有效
- 目标代码生成: 生成机器语言程序或汇编语言程序
- 符号表管理: 符号表建立, 查找, 更新
- 出错处理: 发现 指出 限制
- 数据类型: 值的集合和一组操作
- 抽象层次: 内部类型, 用户定义类型, 抽象数据类型
- 内部类型不能对成分进行操作, 用户类型可以 抽象数据类型不能直接操作, 通过过程访问抽象数据
- 用户定义类型分类
- 笛卡尔积: C结构
- 有限映射: 定义域到值域的映射 数组
- 序列: 类型相同 字符串
- 递归: T包含T成分, 二叉树
- 判定或: C联合 两种不同对象选择
- 幂集: T子集的集合, 集合操作
- 语句级控制结构
- 顺序: 按程序计数器顺序获得指令抽象
- 分支和循环: 显式修改程序计数器的值, 无条件转移和条件转移抽象
- 单元级控制结构:函数 参数传递 异常处理 并发
- 语法 字母表 符号 字汇表:关键字表 词法规则: 规定什么样的字符序列是有效符号 语法规则: 确定符号序列是否合法
- 生成(文法): 非终结符 终结符
- 识别(语法图): 如果合法, 必须从语法图入口通过出口,识别终结符序列 所有终结符序列称为语言
- 语义: 抽象机行为描述语言
- 文法分类
G
=
(
V
T
,
V
N
,
S
,
P
)
G=(V_T, V_N, S, P)
G=(VT,VN,S,P)
- 0型文法: α → β \alpha \rightarrow \beta α→β
- 1型 上下文有关文法 替换时需考虑上下文
- 2型上下文无关文法:
- 3型 正则文法: (无左递归)
- 推导: 产生式替换 最左推导 最右推导 规约
- 句型: 包括非终结符, 句子终结符序列
- 语法树 叶子:非终结符 短语: 子树末端节点生成的符号串
- 二义性: 句子有两颗不同的推导树
- 词法分析: 标识符 基本字 常数 运算符 界符 双缓冲区 超前搜索
语法分析
- 自上而下
- 回溯分析: 推导失败 回溯
- 回溯原因: 公共左因子 左递归 ϵ \epsilon ϵ产生式
- 左递归与间接左递归的消除
- 扩充BNF: {} [] 改进的递归下降 利用循环消除左递归
- 预测分析: 下推栈, 当前输入符号, 预测分析表
- 预测分析表构建:
- FIRST集: 注意空串
- FOLLOW集
- LL(1) FIRST集互斥, 空串时,FIRST与FOLLOW互斥 仅利用当前非终结符 向前查看1个符号就能决定采取动作
- 自下而上:移入 规约 LR分析法
- 短语 直接短语 句柄
语义翻译
- 语义分析的目的是生成代码并实现句子的语义 语义分析生成的不是最终的目标代码,而是便于实现优化的某种中间代码;
- offset 产生最早句柄初始化
- 对赋值语句类型检查
- 拉链 回填
数据库
- 数据库是长期存储在计算机内, 有组织的, 可共享的大量数据集合
- 按一定的数据模型组织, 描述和储存 独立性高 冗余小 可共享
- DBMS: 定义, 创建和维护数据库 控制数据库访问的软件系统
- 实体集 表
模式结构
- 内模式
- (概念|逻辑)模式: 一个数据库只有一个模式 公共数据视图 中间层
- 外模式(视图, 用户模式): 用户使用的局部数据 不同用户外模式不同 模式与外模式一对多 外模式与应用一对多
- 内模式(存储模式) 物理结构
- 外模式/模式映射: 模式改变 修改映射 使外模式保持不变 保证程序独立性
数据模型
- 数据结构 操作 约束条件
- 关系实例: 表
- 关系模式: 关系名 属性名 域名 完整性约束 1NF 不能表中有表
- 关系数据库是关系有限集合 关系模式集合称为数据库模式 对应实例集合成为数据库实例
- 键: 不能取空值 实体完整性 候选键 主键 外键
- 元祖不能重复 顺序不重要
- 实体完整性:主键不能为空 参照完整性: 外码 孤子记录
- 关系运算
- 条件连接 在两关系笛卡尔积上的选择运算
- 自然连接: 相同属性列相等 重复属性列去掉.
- 左连接(加入左面) 右连接(加入右面)
SQL
- CREATE DATABASE | ALTER DATABASE | DROP DATABASE
- 完整性约束
- 主键约束 PRIMARY KEY
- 唯一性约束 UNIQUE
- 非空值约束 NOT NULL
- 参照完整性约束 不允许引用不存在实体
- ALTER TABLE
数据库设计
- 函数依赖: 属性之间的联系 语义关联性
- 决定因子 依赖因子
- 完全函数依赖, 部分函数依赖: 单属性只能是完全函数依赖
- 主属性: 包含在候选码中的属性
- 模式分解必须无损
- 可以决定决定其他所有属性的属性是主键
- 范式
- 1NF: 每个属性值都是不可再分的原子值
- 2NF: 每个非主属性完全函数依赖于候选码 主键只有一个字段 就一定符合2NF
- 3NF: 每个非主属性不传递依赖于R, 依赖因子必须是候选码
- BCNF: 每个主属性都不传递函数依赖于候选码
E-R模型
- 一多一 一对多 多对多 同一实体型内联系 多个实体型之间联系
- 弱实体
- 数据库设计方法
- 直观设计
- 新奥尔良: 需求分析 概念设计 逻辑设计 物理设计
- 基于E-R模型的数据库设计
- 3NF设计方法
- 面向对象的数据库设计方法
- 数据库设计计算机辅助
- 数据库的三种模型
- 层次模型:树
- 网状模型
- 关系模型
- 阶段
- 需求分析: 明确需要完成何种任务
- 概念设计: E-R
- 逻辑设计: 转换为关系模型 模式优化 范式
- 物理设计: 储存结构 存取方法
- 实现
- 运行与维护