十字链表法,十字链表压缩存储稀疏矩阵详解

简介: 十字链表法,十字链表压缩存储稀疏矩阵详解

十字链表法,十字链表压缩存储稀疏矩阵详解


对于压缩存储稀疏矩阵,无论是使用三元组顺序表,还是使用行逻辑链接的顺序表,归根结底是使用数组存储稀疏矩阵。介于数组 "不利于插入和删除数据" 的特点,以上两种压缩存储方式都不适合解决类似 "向矩阵中添加或删除非 0 元素" 的问题。


例如,A 和 B 分别为两个矩阵,在实现 "将矩阵 B 加到矩阵 A 上" 的操作时,矩阵 A 中的元素会发生很大的变化,之前的非 0 元素可能变为 0,而 0 元素也可能变为非 0 元素。对于此操作的实现,之前所学的压缩存储方法就显得力不从心


本节将学习用十字链表存储稀疏矩阵,该存储方式采用的是 "链表+数组" 结构,如图 1 所示。

image.png

图 1 十字链表示意图


可以看到,使用十字链表压缩存储稀疏矩阵时,矩阵中的各行各列都各用一各链表存储,与此同时,所有行链表的表头存储到一个数组(rhead),所有列链表的表头存储到另一个数组(chead)中。


因此,各个链表中节点的结构应如图 2 所示:

image.png

图 2 十字链表的节点结构


两个指针域分别用于链接所在行的下一个元素以及所在列的下一个元素。


链表中节点的 C 语言代码表示应为:

typedef struct OLNode{
int i,j;//元素的行标和列标
int data;//元素的值
struct OLNode * right,*down;//两个指针域
}OLNode;
同时,表示十字链表结构的 C 语言代码应为:
#include<stdio.h>
#include<stdlib.h>
typedef struct OLNode
{
int i, j, e; //矩阵三元组i代表行 j代表列 e代表当前位置的数据
struct OLNode *right, *down; //指针域 右指针 下指针
}OLNode, *OLink;
typedef struct
{
OLink *rhead, *chead; //行和列链表头指针
int mu, nu, tu; //矩阵的行数,列数和非零元的个数
}CrossList;
CrossList CreateMatrix_OL(CrossList M);
void display(CrossList M);
int main()
{
CrossList M;
M.rhead = NULL;
M.chead = NULL;
M = CreateMatrix_OL(M);
printf("输出矩阵M:\n");
display(M);
return 0;
}
CrossList CreateMatrix_OL(CrossList M)
{
int m, n, t;
int i, j, e;
OLNode *p, *q;
printf("输入矩阵的行数、列数和非0元素个数:");
scanf("%d%d%d", &m, &n, &t);
M.mu = m;
M.nu = n;
M.tu = t;
if (!(M.rhead = (OLink*)malloc((m + 1) * sizeof(OLink))) || !(M.chead = (OLink*)malloc((n + 1) * sizeof(OLink))))
{
printf("初始化矩阵失败");
exit(0);
}
for (i = 1; i <= m; i++)
{
M.rhead[i] = NULL;
}
for (j = 1; j <= n; j++)
{
M.chead[j] = NULL;
}
for (scanf("%d%d%d", &i, &j, &e); 0 != i; scanf("%d%d%d", &i, &j, &e)) {
if (!(p = (OLNode*)malloc(sizeof(OLNode))))
{
printf("初始化三元组失败");
exit(0);
}
p->i = i;
p->j = j;
p->e = e;
//链接到行的指定位置
if (NULL == M.rhead[i] || M.rhead[i]->j > j)
{
p->right = M.rhead[i];
M.rhead[i] = p;
}
else
{
for (q = M.rhead[i]; (q->right) && q->right->j < j; q = q->right);
p->right = q->right;
q->right = p;
}
//链接到列的指定位置
if (NULL == M.chead[j] || M.chead[j]->i > i)
{
p->down = M.chead[j];
M.chead[j] = p;
}
else
{
for (q = M.chead[j]; (q->down) && q->down->i < i; q = q->down);
p->down = q->down;
q->down = p;
}
}
return M;
}
void display(CrossList M) {
for (int i = 1; i <= M.nu; i++)
{
if (NULL != M.chead[i])
{
OLink p = M.chead[i];
while (NULL != p)
{
printf("%d\t%d\t%d\n", p->i, p->j, p->e);
p = p->down;
}
}
}
}

运行结果:

输入矩阵的行数、列数和非0元素个数:3 3 3
2 2 3
2 3 4
3 2 5
0 0 0
输出矩阵M:
2       2       3
3       2       5
2       3       4

矩阵(稀疏矩阵)的转置算法(C语言)详解


矩阵(包括稀疏矩阵)的转置,即互换矩阵中所有元素的行标和列标,如图 1 所示:

image.png

图 1 矩阵转置示意图


但如果想通过程序实现矩阵的转置,互换行标和列标只是第一步。因为实现矩阵转置的前提是将矩阵存储起来,数据结构中提供了 3 种存储矩阵的结构,分别是



如果采用前两种结构,矩阵的转置过程会涉及三元组表也跟着改变的问题,如图 2 所示:

image.png

图 2 三元组表的变化


图 2a) 表示的是图 1 中转置之前矩阵的三元组表,2b) 表示的是图 1 中矩阵转置后对应的三元组表。

不仅如此,如果矩阵的行数和列数不等,也需要将它们互换。


因此通过以上分析,矩阵转置的实现过程需完成以下 3 步:


  1. 将矩阵的行数和列数互换;
  2. 将三元组表(存储矩阵)中的 i 列和 j 列互换,实现矩阵的转置;
  3. 以 j 列为序,重新排列三元组表中存储各三元组的先后顺序;


此 3 步中,前两步比较简单,关键在于最后一步的实现。本节先介绍较容易的一种。


矩阵转置的实现思路是:不断遍历存储矩阵的三元组表,每次都取出表中 j 列最小的那一个三元组,互换行标和列标的值,并按次序存储到一个新三元组表中,。


例如,将图 2a) 三元组表存储的矩阵进行转置的过程为:


  1. 新建一个三元组表(用于存储转置矩阵),并将原矩阵的行数和列数互换赋值给新三元组;
  2. 遍历三元组表,找到表中 j 列最小值 1 所在的三元组 (3,1,6),然后将其行标和列标互换后添加到一个新的三元组表中,如图 3 所示:
    image.png
    图 3 矩阵转置的第一个过程
  3. 继续遍历三元组表,找到表中 j 列次小值为 2 的三元组,分别为 (1,2,1)、(2,2,3) 和 (3,2,5),根据找到它们的先后次序将各自的行标和列标互换后添加到新三元组表中,如图 4 所示:
    image.png
    图 4 矩阵转置的第二个过程


对比图 4 和图 2b) 可以看到,矩阵被成功地转置。


因此,矩阵转置的 C 语言实现代码为:

#include<stdio.h>
#define number 10
typedef struct {
    int i, j;
    int data;
}triple;
typedef struct {
    triple data[10];
    int n, m, num;
}TSMatrix;
TSMatrix transposeMatrix(TSMatrix M, TSMatrix T) {
    T.m = M.n;
    T.n = M.m;
    T.num = M.num;
    if (T.num) {
        int q = 0;
        for (int col = 1; col <= M.m; col++) {
            for (int p = 0; p < M.num; p++) {
                if (M.data[p].j == col) {
                    T.data[q].i = M.data[p].j;
                    T.data[q].j = M.data[p].i;
                    T.data[q].data = M.data[p].data;
                    q++;
                }
            }
        }
    }
    return T;
}
int main() {
    TSMatrix M;
    M.m = 2;
    M.n = 3;
    M.num = 4;
    M.data[0].i = 1;
    M.data[0].j = 2;
    M.data[0].data = 1;
    M.data[1].i = 2;
    M.data[1].j = 2;
    M.data[1].data = 3;
    M.data[2].i = 3;
    M.data[2].j = 1;
    M.data[2].data = 6;
    M.data[3].i = 3;
    M.data[3].j = 2;
    M.data[3].data = 5;
    TSMatrix T;
    for (int k = 0; k < number; k++) {
        T.data[k].i = 0;
        T.data[k].j = 0;
        T.data[k].data = 0;
    }
    T = transposeMatrix(M, T);
    for (int i = 0; i < T.num; i++) {
        printf("(%d,%d,%d)\n", T.data[i].i, T.data[i].j, T.data[i].data);
    }
    return 0;
}

程序运行结果为:

(1,3,6)

(2,1,1)

(2,2,3)

(2,3,5)

由于此算法中嵌套使用了两个 for 循环,时间复杂度O(n2)


相关文章
|
存储 算法
树(Tree) - 概念与基础
树(Tree) - 概念与基础
|
存储 关系型数据库 MySQL
深入理解MySQL索引:从原理到最佳实践
深入理解MySQL索引:从原理到最佳实践
1476 0
|
算法
最小生成树算法:Prim算法
在图论中,最小生成树(Minimum Spanning Tree,简称MST)是一种常用的算法问题。最小生成树是指在一个加权连通图中选取边的子集,使得所有顶点都被覆盖,并且边的总权值最小。
1555 0
|
5月前
|
人工智能 自然语言处理 前端开发
【SpringAIAlibaba新手村系列】(9)Text to Image 文本生成图像技术
本文介绍 Spring AI 中的文生图能力,围绕 ImageModel、ImagePrompt 与阿里云百炼图像模型展开,演示如何根据文字描述生成图片链接,并结合 Prompt 编写技巧与参数配置,帮助开发者提升生成效果与落地能力。
997 8
|
9月前
|
JSON 缓存 API
【剪映小助手】向现有草稿中添加视频特效
向现有草稿中添加视频特效。该接口用于在指定的时间段内添加特效素材到剪映草稿中,支持多种特效类型如边框特效、滤镜特效、动态特效等。特效可以用于增强视频的视觉效果。
|
12月前
|
网络安全
wegame登录失败错误代码7610001该怎么解决?wegame错误代码7610001解决方法
WeGame错误代码7610001通常由防火墙或网络问题引起,可尝试删除WeGame相关防火墙规则后重新登录。此外,显卡驱动问题也可能导致此错误,建议使用驱动修复工具更新显卡驱动。本文还介绍了关闭防火墙及设置DirectX加速等解决方法,并提供相关软件下载链接,帮助你快速修复问题。
4843 3
wegame登录失败错误代码7610001该怎么解决?wegame错误代码7610001解决方法
|
数据库
数据库五种基本运算
【5月更文挑战第15天】数据库的关系代数包括并、差、投影、笛卡尔积和选择等基本运算,以及交、连接、除、广义投影和外连接等扩展运算。在这些操作中,笛卡尔积通常最消耗资源,因为它会大幅增加元组数量和计算量。
2155 4
数据库五种基本运算
|
人工智能 运维 自然语言处理
2025保姆级JupyterLab 4.0安装指南|全平台部署+AI编程环境配置
JupyterLab 是下一代交互式计算开发环境,2025年发布的4.0版本新增多语言内核支持(Python/R/Julia/JavaScript一键切换)、实时协作功能、AI辅助编程(集成GPT-5代码补全与错误诊断)和可视化调试器等特性。本文详细介绍其技术定位、跨平台安装方案、安装流程、高阶功能配置、典型应用场景及故障排查指南,帮助用户高效使用JupyterLab进行开发。
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
1597 1
|
云安全 人工智能 监控
直播流量下,涌动的安全风险
针对直播前、直播中、直播账号和直播边界的有效防控,阿里云给出业内最佳安全实践。
3326 0
直播流量下,涌动的安全风险