多维数组
基本概念
多维数组 (Multi-array) 是向量的扩充,向量的向量就组成了多维数组。 可以表示为ELEM A[c1..d1][c2..d2]...[cn..dn]
- ci 和 di 是各维下标的下界和上界。
数组的存储
内存是一维的,所以数组的存储也只能是一维的
- 以行为主序 (也称为“行优先”)
- 以列为主序 (也称为“列优先”) C++ 多维数组ELEM A[d1][ d2]...[dn];
用数组表示特殊矩阵
- 三角矩阵:上三角、下三角
- 对称矩阵
- 对角矩阵
- 稀疏矩阵
- 十字链表存储
矩阵乘法时间代价
- A为 p×m 的矩阵,B 为 m×n 的矩阵,乘得的结果 C 为 p×n 的矩阵
- 若矩阵 A 中行向量的非零元素个数最多为 ta
- 矩阵 B 中列向量的非零元素个数最多为 tb
- 总执行时间降低为 O ( (ta+tb) ×p×n)
- 经典矩阵乘法所需要的时间代价为 O (p×m×n)
广义表-更多内容
- 回顾线性表
- 由 n (n≥0) 个数据元素组成的有限有序序列
- 线性表的每个元素都具有相同的数据类型
- 如果一个线性表中还包括一个或者多个子表,那 就称之为广义表 (Generalized Lists,也称Multi-list) 一般记作:
- L=(x0,x1,...,xi,...,xn-1)
广义表的各种类型
- 纯表 (pure list)
- 从根结点到任何叶结点只有一条路径
- 即任何一个元素 (原子、子表) 在广义表中只出现一次
- 可重入表
- 其元素(包括原子和子表)可能会在表中多次出现
- 如果没有回路图示对应于一个 DAG
- 循环表
- 包含回路
- 循环表的深度为无穷大 图≥再入表≥纯表 (树)≥线性表,广义表是线性与树形结构的推广。递归表是有回路的再入表。
广义表存储
- 增加头指针,简化删除、插入操作
广义表应用
- 函数的调用关系
- 内存空间的引用关系
- LISP 语言
存储管理
分配与回收
- 内存管理最基本的问题
- 分配存储空间
- 回收被“释放”的存储空间
- 碎片问题
- 存储的压缩
- 无用单元收集
- 无用单元:可以回收而没有回收的空间
- 内存泄漏 (memory leak)
- 程序员忘记 delete 已经不再使用的指针
可利用空间表
- 把存储器看成一组变长块数组
- 一些块是已分配的
- 链接空闲块,形成可利用空间表 (freelist)
- 存储分配和回收
- new p 从可利用空间分配
- delete p 把 p 指向的数据块返回可利用空间表
- 空间不够,则求助于失败策略
可利用空间表:单链表栈freelist
使用者总是从freelist中分配内存,如果存在没有使用的内存块就直接摘出来使用,如果没有的话再从系统中分配。 使用完毕后并不去直接delete该内存块,而是把他插入到可用内存块的头部,同时让他指向之前可用内存块的头部。
存储的动态分配和回收
变长可利用块
- 分配
- 找到其长度大于等于申请长度的结点
- 从中截取合适的长度
- 回收
- 考虑刚刚被删除的结点空间能否与邻接合并
- 以便能满足后来的较大长度结点的分配请求
碎片问题
- 内部碎片:多于请求字节数的空间
- 外部碎片:小空闲块
空闲块分配方法-顺序适配 (sequential fit)
常见的顺序适配方法:
- 首先适配 (firstfit) ,实例
- 最佳适配 (bestfit)
- 最差适配 (worstfit) 组织成循环链表的可利用空间表的结点大小按递增序排列时, 首次适配策略就转变为最佳适配策略。
回收:考虑合并相邻块
把待回收块释放回可利用空间表
适配策略选择
- 需要考虑以下因素用户的要求
- 分配或回收效率对系统的重要性
- 所分配空间的长度变化范围
- 分配和回收的频率
- 在实际应用中,首先适配最常用
- 分配和回收的速度比较快
- 支持比较随机的存储请求
- 很难笼统地讲这哪种适配策略最好
失败处理策略和无用单元回收
如果遇到因内存不足而无法满足一个存储请求,存储管理器可以有两种行为:
- 一是什么都不做,直接返回一个系统错误信息;
- 二是使用失败处理策略 (failure policy) 来满足请求。
存储压缩 (compact)
- 把内存中的所有碎片集中起来,组成一个大的可利用块
- 内存碎片很多,即将产生溢出时使用
- 句柄使得存储地址相对化
- 对存储位置的二级间接指引
- 移动存储块位置,只需要修改句柄值,不需要修改应用程序
- 两种存储压缩
- 一旦有用户释放存储块即进行回收压缩
- 在可利用空间不够分配或在进行无用单元的收 集时进行“存储压缩”
无用单元收集
无用单元收集:最彻底的失败处理策略
- 普查内存,标记把那些不属于任何链的结点,将它们收集到可利用空间表中
- 回收过程通常还可与存储压缩一起进行
可利用空间表实例
常常会有频繁申请、释放内存的需求,比如在发送网络报文时,每次都要分配内存以存储报文,等报文发送完成后又需要删除报文。 为了避免频繁的new/delete对系统带来的开销,需要实现一个通用的FreeList机制。使用者总是从free list中分配内存,如果存在没有使用的内存块就直接摘出来使用,如果没有的话再从系统中分配。使用完毕后并不去直接delete该内存块,而是交给FreeList保管。
template <typename Elem>
class FreeList
{
private:
static FreeList<Elem> *freelist;
public:
Elem element;
FreeList *next;
FreeList(const Elem& elem, FreeList* next=NULL);
FreeList(FreeList* next=NULL);
void* operator new(size_t); // 重载new
void operator delete(void*); // 重载delete
};
template <typename Elem>
FreeList<Elem>* FreeList<Elem>::freelist = NULL;
template <typename Elem>
FreeList<Elem>::FreeList(const Elem& elem, FreeList* next)
{
this->element = elem;
this->next = next;
}
template <typename Elem>
FreeList<Elem>::FreeList(FreeList* next)
{
this->next = next;
}
template <typename Elem>
void* FreeList<Elem>::operator new(size_t)
{
/*freelist没有可用空间,就从系统分配*/
if(freelist == NULL)
return ::new FreeList;
/*否则,从freelist表头摘取结点*/
FreeList<Elem>* temp = freelist;
freelist = freelist->next;
return temp;
}
template <typename Elem>
void FreeList<Elem>::operator delete(void* ptr)
{
/*把要释放的结点空间加入到freelist中*/
((FreeList<Elem>*)ptr)->next = freelist;
freelist = (FreeList<Elem>*)ptr;
}
复制代码