高级数据结构之多维数组与广义表与存储管理

多维数组

基本概念

多维数组 (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)

  • 把内存中的所有碎片集中起来,组成一个大的可利用块
  • 内存碎片很多,即将产生溢出时使用
  • 句柄使得存储地址相对化
    • 对存储位置的二级间接指引
    • 移动存储块位置,只需要修改句柄值,不需要修改应用程序
  • 两种存储压缩
    1. 一旦有用户释放存储块即进行回收压缩
    2. 在可利用空间不够分配或在进行无用单元的收 集时进行“存储压缩”

无用单元收集

无用单元收集:最彻底的失败处理策略

  • 普查内存,标记把那些不属于任何链的结点,将它们收集到可利用空间表中
  • 回收过程通常还可与存储压缩一起进行

可利用空间表实例

常常会有频繁申请、释放内存的需求,比如在发送网络报文时,每次都要分配内存以存储报文,等报文发送完成后又需要删除报文。 为了避免频繁的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;
}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值