挑战408——数据结构(24)——图的存储与矩阵压缩

图的存储方式

在实践中,存储图最常见的策略是:

  • 将每个节点的连接存储在邻接列表中。
  • 将整个图形的连接存储在邻接矩阵中。
用邻接链表来表示图之间的关系

在图中表示连接的最简单方法是在每个节点的数据结构中存储与其连接的节点的列表。该结构称为邻接列表。 例如,在航空公司图表中
在这里插入图片描述
每一个节点连接的相邻节点构成一个邻接表,就像这样:
在这里插入图片描述
现在我们来讲将上图的内容抽象化:
假设我们现在有个有向图:
在这里插入图片描述
根据定义,我们将每个节点的连接存储在邻接列表中。对于节点 1 2 3 4 5,有以下连接表(比如与节点1相连的节点有2和5,他们之间先后顺序可以是任意的):

在这里插入图片描述
当图为有向图的时候,按箭头的方向给出连接表即可:
在这里插入图片描述

使用邻接矩阵表示节点间的关系

虽然临接表提供了一种表示图形中连接的便捷方式,但是当操作需要搜索与节点关联的节点时,它的效率可能会低下。 例如,如果使用邻接列表表示,则确定是两个节点是否相互连接需要O(D)时间,其中D表示节点之间的度。 如果图中的节点都具有少量邻居,则搜索该列表的成本很小。 但是,如果图中的节点往往具有大量邻居,则此成本变得很高。
如果效率成为一个很关心的问题,那么我们可以通过在称为邻接矩阵的二维数组中表示节点之间的花费(即权重),该数组显示哪些节点已连接。 航空公司图的邻接矩阵如下所示:
在这里插入图片描述
通常空白部分我们用∝表示,意思是两个节点之间没有直接关联。
特别的,当图为无向图是,邻接矩阵是对称的(如上图所示),因为每个相邻节点之间有相互的关系。(这个时候,如果矩阵很大,可以采取压缩矩阵的方式将矩阵压缩。矩阵的压缩后面会提到)
一般的,如果无向图中两个节点相互连接,那么我们就记为1,否则记为0,。这个时候我们就可以得出只有0 1 的矩阵。
下面来加班一些具体的实例:
在这里插入图片描述
上图中,第一行表示的是,对于节点1,它与自己本身不相连,记为0,与节点2相连记为1。与节点3不相连记为0,与节点4相连记为1,与节点5不相连记为0.由此得出矩阵的第一行应该是0 1 0 1 0.其他行以此类推。
对于有向图,可以按上述的逻辑进行推算:
在这里插入图片描述
还有一种图,叫做带权图。如下:
在这里插入图片描述
所谓权,就是节点之间相互到达需要的花费。这时候我们的邻接矩阵记录的就是权的大小。当节点之间的距离需要逆向而行或者非直接连接的时候,这是我们表示距离不可达,即无穷。比如第一行,节点1到自己本身没有箭头可指,因此是无穷。而节点1到节点2需要花费5.而节点1到节点三不是直接相连的,也记为无穷。由此得出邻接矩阵每行的数据。

要使用邻接矩阵,必须将每个节点与其索引号相关联,该索引号指定该表中与该节点对应的列或行号。 作为图的具体结构的一部分,实现需要为图中的每个节点分配一行和一列的二维数组。 数组的元素是布尔值。 如果matrix [start] [finish]中的条目为真,则图中就有表示 start→finish的节点间的关系。

两种表示方式的空间复杂度

就执行时间而言,使用邻接矩阵比使用邻接列表快得多。 但另一方面,矩阵需要O(N^2)存储空间(如果不压缩),其中N是节点的数量。 对于大多数图形,邻接列表表示在空间方面往往更有效。 在邻接列表表示中,每个节点都有一个连接列表,在最坏的情况下,它的长度将是Dmax,其中Dmax是图中任一节点的最大度数,即从单个节点出发的最大连接数。 因此,邻接列表的空间成本是O(N×Dmax)。 如果大多数节点彼此连接,则Dmax将相对接近N,这意味着表示连接的成本对于两种方法是相近的的。 另一方面,如果图形包含许多节点但相对较少的互连(称为稀疏矩阵),则邻接列表表示可以节省相当大的空间。

除此之外,图的存储方式还有邻接多重表和十字链表法

特点

  1. 由上述可以看出,通常我们可以将边的类型定义为0或者1的枚举类型。
  2. 无向图的邻接矩阵一定是对称矩阵(且唯一),对于规模较大的矩阵可以进行压缩。
  3. 当图的边数较多的时候,适合采用邻接矩阵表示。反之当边数较少的时候适合采用邻接链表。
矩阵压缩

我们刚刚提到 ,对于规模较大的对称矩阵可以考虑进行压缩。那么什么是压缩呢?
压缩存储,是指为多个值相同的元素只分配一个存储空间,并且对于0元素不分配空间。其目的就是要节省存储空间。我们通常可以对一些特殊的矩阵进行压缩。下面着重介绍三种(只考虑方阵)。

对称矩阵

定义:对于一个n阶方阵A[1…n][1…n],中的任意一个元素Aij,满足:

Aij = Aji //其中 1 <=1,且j < = n

那么就称为对称矩阵。通常可以将元素分成3大部分:上三角区,下三角区,对角线。:
在这里插入图片描述
图中,A(n,1) = A(1,n), A(n,2) = A(2,n)
因此我们可以考虑直接用一维数组来存储一半的数据即可,另外一半的数据可以由之前的一半交换下标所得。假设我们新建一个数组B来存储:
在这里插入图片描述
其中:

1+2+3+4+....(i-1)   \\定位数组所在的行
(j -1)\\定位数组所在的列

因此得出如下关系(两个式子实际上是i,j 位置互换):
在这里插入图片描述

三角矩阵

这种矩阵的特点是,上三角区的所有元素均为同一常量。其存储思想与对称矩阵类似。先存完下三角和主对角线的元素后,再存对角线上方的常量一次。

  • 下三角矩阵
    在这里插入图片描述
    对于下三角,存储方式完全与对称矩阵的下三角相同:
    在这里插入图片描述
    对于下三角,由于存的都是常数,第一行有一个元素,第二行有两个元素,第三行有3个元素,那么第n行的n个元素(存常数),他所在的位置就是一个等差数列求和的过程。:
    在这里插入图片描述
    因此其在内存中的存储为:
    在这里插入图片描述
    也就说,常数项只占用一个存储位置。
  • 上三角矩阵
    在这里插入图片描述
    在这里插入图片描述
    按刚刚的方法可以很快得出位置关系:
    在这里插入图片描述
    从而得出存储图:
    在这里插入图片描述
三对角矩阵

对于n阶方阵A中的任一元素a(i,j),如果| i - j | >1(即行-列的绝对值大于1)的时候,有 a(i,j)= 0,那么矩阵A就被称为三对角矩阵。在三对角矩阵中,所有的主要元素都集中在以主对角线为中心的3条对角线中,其他区域的数值均为0.
在这里插入图片描述
采用压缩存储,其原理为,先将3条对角线上的元素,以按行优先方式存储在一维数组B中,且B0为a(1,1)。那么存储结构为:
在这里插入图片描述
因此,A中三条对角线的主元素A(i,j)的下标为:

k = 2i + j -3

反推,已知元素位置为k,那么其下标为:

i = [(k+1) /3 +1]   //中括号的意思是向下取整
j = k - 2i +3  //这里是求主元素下标公式的逆推
稀疏矩阵

这个很容易理解,就不用什么官方定义了,就是矩阵中,非零元素非常少的矩阵。(比如一个100 x 100的矩阵中,只有不到100个非零元素)。这种矩阵就是稀疏矩阵。
对于这种矩阵的存储也很好办,只要我们存储非零元素就好了。如果将行,列,值三者组成一个三元组,那么三元组的格式就是(行,列,值)。将其存在数组下标就很好办:
在这里插入图片描述
注意,这里我们是指定了特定的地方存储,那么矩阵压缩存储后,就失去了随机存储的特性

按行存储和按列存储
按行存储

这个也不看具体的公式定义,先上图:
在这里插入图片描述
上图是个二维数组,一个矩阵。按行存储,意思就是先把行从左往右读,每读到一个就存起来:
在这里插入图片描述
这样就可以得出一个结论,元素a(i,j)所在的位置为:

i x (h +1) +j //h表示列的最大值,从0开始算
                //i x (h +1) 可以定位到a(i,j)开始的位置a (i)
                //再加上所在的列,可以计算出具体的位置。

用LOC(a(0,0))表示数组在内存中表示的物理位置,L表示每个元素的存储空间,那么a(i,j)的物理位置为(从0开始算):
在这里插入图片描述

按列存储

原理同上,放图自己分析:
在这里插入图片描述

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目 录 译者序 前言 第一部分 预备知识 第1章 C++程序设计 1 1.1 引言 1 1.2 函数与参数 2 1.2.1 传值参数 2 1.2.2 模板函数 3 1.2.3 引用参数 3 1.2.4 常量引用参数 4 1.2.5 返回值 4 1.2.6 递归函数 5 1.3 动态存储分配 9 1.3.1 操作符new 9 1.3.2 一维数组 9 1.3.3 异常处理 10 1.3.4 操作符delete 10 1.3.5 二维数组 10 1.4 类 13 1.4.1 类Currency 13 1.4.2 使用不同的描述方法 18 1.4.3 操作符重载 20 1.4.4 引发异常 22 1.4.5 友元和保护类成员 23 1.4.6 增加#ifndef, #define和#endif语句 24 1.5 测试与调试 24 1.5.1 什么是测试 24 1.5.2 设计测试数据 26 1.5.3 调试 28 1.6 参考及推荐读物 29 第2章 程序性能 30 2.1 引言 30 2.2 空间复杂性 31 2.2.1 空间复杂性的组成 31 2.2.2 举例 35 2.3 时间复杂性 37 2.3.1 时间复杂性的组成 37 2.3.2 操作计数 37 2.3.3 执行步数 44 2.4 渐进符号(O、 健?、 o) 55 2.4.1 大写O符号 56 2.4.2 椒?58 2.4.3 符号 59 2.4.4 小写o符号 60 2.4.5 特性 60 2.4.6 复杂性分析举例 61 2.5 实际复杂性 66 2.6 性能测量 68 2.6.1 选择实例的大小 69 2.6.2 设计测试数据 69 2.6.3 进行实验 69 2.7 参考及推荐读物 74 第二部分 数据结构 第3章 数据描述 75 3.1 引言 75 3.2 线性表 76 3.3 公式化描述 77 3.3.1 基本概念 77 3.3.2 异常类NoMem 79 3.3.3 操作 79 3.3.4 评价 83 3.4 链表描述 86 3.4.1 类ChainNode 和Chain 86 3.4.2 操作 88 3.4.3 扩充类Chain 91 3.4.4 链表遍历器类 92 3.4.5 循环链表 93 3.4.6 与公式化描述方法的比较 94 3.4.7 双向链表 95 3.4.8 小结 96 3.5 间接寻址 99 3.5.1 基本概念 99 3.5.2 操作 100 3.6 模拟指针 102 3.6.1 SimSpace的操作 103 3.6.2 采用模拟指针的链表 106 3.7 描述方法的比较 110 3.8 应用 111 3.8.1 箱子排序 111 3.8.2 基数排序 116 3.8.3 等价类 117 3.8.4 凸包 122 3.9 参考及推荐读物 127 第4章 数组和矩阵 128 4.1 数组 128 4.1.1 抽象数据类型 128 4.1.2 C++数组 129 4.1.3 行主映射和列主映射 129 4.1.4 类Array1D 131 4.1.5 类Array2D 133 4.2 矩阵 137 4.2.1 定义和操作 137 4.2.2 类Matrix 138 4.3 特殊矩阵 141 4.3.1 定义和应用 141 4.3.2 对角矩阵 143 4.3.3 三对角矩阵 144 4.3.4 三角矩阵 145 4.3.5 对称矩阵 146 4.4 稀疏矩阵 149 4.4.1 基本概念 149 4.4.2 数组描述 149 4.4.3 链表描述 154 第5章 堆栈 161 5.1 抽象数据类型 161 5.2 派生类和继承 162 5.3 公式化描述 163 5.3.1 Stack的效率 164 5.3.2 自定义Stack 164 5.4 链表描述 166 5.5 应用 169 5.5.1 括号匹配 169 5.5.2 汉诺塔 170 5.5.3 火车车厢重排 172 5.5.4 开关盒布线 176 5.5.5 离线等价类问题 178 5.5.6 迷宫老鼠 180 5.6 参考及推荐读物 188 第6章 队列 189 6.1 抽象数据类型 189 6.2 公式化描述 190 6.3 链表描述 194 6.4 应用 197 6.4.1 火车车厢重排 197 6.4.2 电路布线 201 6.4.3 识别元 204 6.4.4 工厂仿真 206 6.5 参考及推荐读物 217 第7章 跳表和散列 218 7.1 字典 218 7.2 线性表描述 219 7.3 跳表描述 222 7.3.1 理想情况 222 7.3.2 插入和删除 223 7.3.3 级的分配 224 7.3.4 类SkipNode 224 7.3.5 类SkipList 225 7.3.6 复杂性 229 7.4 散列表描述 229 7.4.1 理想散列 229 7.4.2 线性开型寻址散列 230 7.4.3 链表散列 234 7.5 应用——文本压缩 238 7.5.1 LZW压缩 239 7.5.2 LZW压缩的实现 239 7.5.3 LZW解压缩 243 7.5.4 LZW解压缩的实现 243 7.6 参考及推荐读物 247 第8章 二叉树和其他树 248 8.1 树 248 8.2 二叉树 251 8.3 二叉树的特性 252 8.4 二叉树描述 253 8.4.1 公式化描述 253 8.4.2 链表描述 254 8.5 二叉树常用操作 256 8.6 二叉树遍历 256 8.7 抽象数据类型BinaryTree 259 8.8 类BinaryTree 260 8.9 抽象数据类型及类的扩充 263 8.9.1 输出 263 8.9.2 删除 264 8.9.3 计算高度 264 8.9.4 统计节点数 265 8.10 应用 265 8.10.1 设置信号放大器 265 8.10.2 在线等价类 268 8.11 参考及推荐读物 275 第9章 优先队列 276 9.1 引言 276 9.2 线性表 277 9.3 堆 278 9.3.1 定义 278 9.3.2 最大堆的插入 279 9.3.3 最大堆的删除 279 9.3.4 最大堆的初始化 280 9.3.5 类MaxHeap 281 9.4 左高树 285 9.4.1 高度与宽度优先的最大及最小 左高树 285 9.4.2 最大HBLT的插入 287 9.4.3 最大HBLT的删除 287 9.4.4 合并两棵最大HBLT 287 9.4.5 初始化最大HBLT 289 9.4.6 类MaxHBLT 289 9.5 应用 293 9.5.1 堆排序 293 9.5.2 机器调度 294 9.5.3 霍夫曼编码 297 9.6 参考及推荐读物 302 第10章 竞?303 10.1 引言 303 10.2 抽象数据类型WinnerTree 306 10.3 类WinnerTree 307 10.3.1 定义 307 10.3.2 类定义 307 10.3.3 构造函数、析构函数及Winner 函数 308 10.3.4 初始化赢者树 308 10.3.5 重新组织比赛 310 10.4 输者树 311 10.5 应用 312 10.5.1 用最先匹配法求解箱子装载 问题 312 10.5.2 用相邻匹配法求解箱子装载 问题 316 第11章 搜索树 319 11.1 二叉搜索树 320 11.1.1 基本概念 320 11.1.2 抽象数据类型BSTree和 IndexedBSTree 321 11.1.3 类BSTree 322 11.1.4 搜索 322 11.1.5 插入 323 11.1.6 删除 324 11.1.7 类DBSTree 326 11.1.8 二叉搜索树的高度 327 11.2 AVL树 328 11.2.1 基本概念 328 11.2.2 AVL树的高度 328 11.2.3 AVL树的描述 329 11.2.4 AVL搜索树的搜索 329 11.2.5 AVL搜索树的插入 329 11.2.6 AVL搜索树的删除 332 11.3 红-黑树 334 11.3.1 基本概念 334 11.3.2 红-黑树的描述 336 11.3.3 红-黑树的搜索 336 11.3.4 红-黑树的插入 336 11.3.5 红-黑树的删除 339 11.3.6 实现细节的考虑及复杂性分析 343 11.4 B-树 344 11.4.1 索引顺序访问方法 344 11.4.2 m 叉搜索树 345 11.4.3 m 序B-树 346 11.4.4 B-树的高度 347 11.4.5 B-树的搜索 348 11.4.6 B-树的插入 348 11.4.7 B-树的删除 350 11.4.8 节点结构 353 11.5 应用 354 11.5.1 直方 354 11.5.2 用最优匹配法求解箱子装载 问题 357 11.5.3 交叉分布 359 11.6 参考及推荐读物 363 第12章 365 12.1 基本概念 365 12.2 应用 366 12.3 特性 368 12.4 抽象数据类型Graph和Digraph 370 12.5 无向和有向的描述 371 12.5.1 邻接矩阵 371 12.5.2 邻接压缩表 373 12.5.3 邻接链表 374 12.6 网络描述 375 12.7 类定义 376 12.7.1 不同的类 376 12.7.2 邻接矩阵类 377 12.7.3 扩充Chain类 380 12.7.4 类LinkedBase 381 12.7.5 链接类 382 12.8 的遍历 386 12.8.1 基本概念 386 12.8.2 邻接矩阵的遍历函数 387 12.8.3 邻接链表的遍历函数 388 12.9 语言特性 389 12.9.1 虚函数和多态性 389 12.9.2 纯虚函数和抽象类 391 12.9.3 虚基类 391 12.9.4 抽象类和抽象数据类型 393 12.10 的搜索算法 394 12.10.1 宽度优先搜索 394 12.10.2 类Network 395 12.10.3 BFS的实现 395 12.10.4 BFS的复杂性分析 396 12.10.5 深度优先搜索 397 12.11 应用 399 12.11.1 寻找路径 399 12.11.2 连通及其构件 400 12.11.3 生成树 402 第三部分 算法设计方法 第13章 贪婪算法 405 13.1 最优化问题 405 13.2 算法思想 406 13.3 应用 409 13.3.1 货箱装船 409 13.3.2 0/1背包问题 410 13.3.3 拓扑排序 412 13.3.4 二分覆盖 415 13.3.5 单源最短路径 421 13.3.6 最小耗费生成树 424 13.4 参考及推荐读物 433 第14章 分而治之算法 434 14.1 算法思想 434 14.2 应用 440 14.2.1 残缺棋盘 440 14.2.2 归并排序 443 14.2.3 快速排序 447 14.2.4 选择 452 14.2.5 距离最近的点对 454 14.3 解递归方程 462 14.4 复杂性的下限 463 14.4.1 最小最大问题的下限 464 14.4.2 排序算法的下限 465 第15章 动态规划 467 15.1 算法思想 467 15.2 应用 469 15.2.1 0/1背包问题 469 15.2.2 压缩 471 15.2.3 矩阵乘法链 476 15.2.4 最短路径 480 15.2.5 网络的无交叉子集 483 15.2.6 元件折叠 486 15.3 参考及推荐读物 491 第16章 回溯 492 16.1 算法思想 492 16.2 应用 496 16.2.1 货箱装船 496 16.2.2 0/1背包问题 503 16.2.3 最大完备子 506 16.2.4 旅行商问题 508 16.2.5 电路板排列 510 第17章 分枝定界 516 17.1 算法思想 516 17.2 应用 519 17.2.1 货箱装船 519 17.2.2 0/1背包问题 526 17.2.3 最大完备子 528 17.2.4 旅行商问题 529 17.2.5 电路板排列 532

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值