Linux环境下的C语言编程(四十九)

一、稀疏矩阵

1. 基本概念

稀疏矩阵 是指矩阵中大多数元素为零的矩阵。相反,稠密矩阵是指大多数元素非零的矩阵。

2. 稀疏性判断标准

通常认为,当非零元素数量占总元素数量的比例低于某个阈值(如5%)时,可以认为是稀疏矩阵。

3. 现实中的例子

复制代码
密集矩阵示例(6×6,36个元素,21个非零):
1 2 0 0 0 0
3 4 5 0 0 0
0 6 7 8 0 0
0 0 9 10 11 0
0 0 0 12 13 14
0 0 0 0 15 16
非零比例:21/36 ≈ 58% → 稠密矩阵

稀疏矩阵示例(6×6,36个元素,仅6个非零):
0 0 0 0 0 0
0 5 0 0 0 0
0 0 0 0 8 0
0 0 0 0 0 0
3 0 0 0 0 0
0 0 9 0 0 0
非零比例:6/36 ≈ 17% → 稀疏矩阵

二、为什么需要特殊存储?

1. 传统二维数组的问题

复制代码
// 6×6的矩阵,只有6个非零元素
int matrix[6][6] = {
    {0, 0, 0, 0, 0, 0},
    {0, 5, 0, 0, 0, 0},
    {0, 0, 0, 0, 8, 0},
    {0, 0, 0, 0, 0, 0},
    {3, 0, 0, 0, 0, 0},
    {0, 0, 9, 0, 0, 0}
};

问题

  • 浪费大量内存存储0值

  • 遍历效率低(需要检查所有36个位置)

  • 运算时对0值进行无意义计算

2. 解决方案:只存储非零元素

三、三元组表示法

1. 三元组的定义

每个非零元素用三个值表示:

  • 行号(row):元素在矩阵中的行位置

  • 列号(col):元素在矩阵中的列位置

  • (value):元素的实际值

2. 数据结构设计

复制代码
// 单个非零元素的三元组
typedef struct {
    int row;    // 行号(从0开始)
    int col;    // 列号(从0开始)
    int value;  // 元素值
} Triple;

// 稀疏矩阵的完整表示
typedef struct {
    Triple *data;   // 三元组数组
    int rows;       // 原矩阵总行数
    int cols;       // 原矩阵总列数
    int num;        // 非零元素个数
    int capacity;   // 数组容量
} SparseMatrix;

3. 存储示例

复制代码
原始矩阵(6×6):
0  0  0  0  0  0
0  5  0  0  0  0  
0  0  0  0  8  0
0  0  0  0  0  0
3  0  0  0  0  0
0  0  9  0  0  0

三元组表示:
索引  row  col  value
0     1    1     5    ← 第1行第1列是5
1     2    4     8    ← 第2行第4列是8  
2     4    0     3    ← 第4行第0列是3
3     5    2     9    ← 第5行第2列是9

存储空间对比:
传统数组:6×6×4 = 144字节(假设int占4字节)
三元组:4个元素×3×4 = 48字节(节省66%)

四、代码实例

1. 基本操作实现

复制代码
#include <stdio.h>
#include <stdlib.h>

// 三元组结构
typedef struct {
    int row;
    int col;
    int value;
} Triple;

// 稀疏矩阵结构
typedef struct {
    Triple *data;
    int rows;
    int cols;
    int num;        // 当前非零元素个数
    int capacity;   // 数组容量
} SparseMatrix;

// 创建稀疏矩阵
SparseMatrix* createSparseMatrix(int rows, int cols, int capacity) {
    SparseMatrix *sm = (SparseMatrix*)malloc(sizeof(SparseMatrix));
    sm->data = (Triple*)malloc(sizeof(Triple) * capacity);
    sm->rows = rows;
    sm->cols = cols;
    sm->num = 0;
    sm->capacity = capacity;
    return sm;
}

// 销毁稀疏矩阵
void destroySparseMatrix(SparseMatrix *sm) {
    if (sm) {
        free(sm->data);
        free(sm);
    }
}

// 插入非零元素(保持行主序)
int insertElement(SparseMatrix *sm, int row, int col, int value) {
    if (row < 0 || row >= sm->rows || col < 0 || col >= sm->cols) {
        printf("位置(%d,%d)超出范围\n", row, col);
        return 0;
    }
    
    if (value == 0) {
        printf("注意:值为0的元素不应存储在三元组中\n");
        return 0;
    }
    
    if (sm->num >= sm->capacity) {
        printf("容量不足,无法插入新元素\n");
        return 0;
    }
    
    // 查找插入位置(保持行主序:行号小的在前,同行时列号小的在前)
    int i;
    for (i = sm->num - 1; i >= 0; i--) {
        if (sm->data[i].row > row || 
            (sm->data[i].row == row && sm->data[i].col > col)) {
            // 后移元素
            sm->data[i+1] = sm->data[i];
        } else {
            break;
        }
    }
    
    // 插入新元素
    sm->data[i+1].row = row;
    sm->data[i+1].col = col;
    sm->data[i+1].value = value;
    sm->num++;
    
    return 1;
}

// 打印稀疏矩阵(三元组形式)
void printSparseMatrix(SparseMatrix *sm) {
    printf("稀疏矩阵 %d×%d,有%d个非零元素:\n", 
           sm->rows, sm->cols, sm->num);
    printf("索引\t行\t列\t值\n");
    printf("------------------------\n");
    
    for (int i = 0; i < sm->num; i++) {
        printf("%d\t%d\t%d\t%d\n", 
               i, sm->data[i].row, sm->data[i].col, sm->data[i].value);
    }
}

// 将三元组还原为完整矩阵并打印
void printAsFullMatrix(SparseMatrix *sm) {
    printf("\n完整矩阵形式(%d×%d):\n", sm->rows, sm->cols);
    
    // 创建临时矩阵并初始化为0
    int **matrix = (int**)malloc(sizeof(int*) * sm->rows);
    for (int i = 0; i < sm->rows; i++) {
        matrix[i] = (int*)calloc(sm->cols, sizeof(int));
    }
    
    // 填充非零值
    for (int i = 0; i < sm->num; i++) {
        int r = sm->data[i].row;
        int c = sm->data[i].col;
        matrix[r][c] = sm->data[i].value;
    }
    
    // 打印矩阵
    for (int i = 0; i < sm->rows; i++) {
        for (int j = 0; j < sm->cols; j++) {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    // 释放内存
    for (int i = 0; i < sm->rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
}

2. 转置操作

复制代码
// 普通转置算法(简单但效率低)
SparseMatrix* transposeSimple(SparseMatrix *sm) {
    // 创建转置矩阵(行列互换)
    SparseMatrix *result = createSparseMatrix(sm->cols, sm->rows, sm->num);
    result->num = sm->num;
    
    int index = 0;
    
    // 按列扫描原矩阵
    for (int col = 0; col < sm->cols; col++) {
        for (int i = 0; i < sm->num; i++) {
            if (sm->data[i].col == col) {
                // 找到列号为col的元素,进行转置
                result->data[index].row = sm->data[i].col;    // 原列变行
                result->data[index].col = sm->data[i].row;    // 原行变列
                result->data[index].value = sm->data[i].value;
                index++;
            }
        }
    }
    
    return result;
}

// 快速转置算法(重点掌握!)
SparseMatrix* fastTranspose(SparseMatrix *sm) {
    // 创建转置矩阵
    SparseMatrix *result = createSparseMatrix(sm->cols, sm->rows, sm->num);
    result->num = sm->num;
    
    if (sm->num == 0) {
        return result;  // 空矩阵直接返回
    }
    
    // 步骤1:统计原矩阵每列的非零元素个数
    int *colCount = (int*)calloc(sm->cols, sizeof(int));
    for (int i = 0; i < sm->num; i++) {
        colCount[sm->data[i].col]++;
    }
    
    // 步骤2:计算转置后每行(对应原矩阵的列)的起始位置
    int *rowStart = (int*)malloc(sizeof(int) * sm->cols);
    rowStart[0] = 0;
    for (int i = 1; i < sm->cols; i++) {
        rowStart[i] = rowStart[i-1] + colCount[i-1];
    }
    
    // 步骤3:执行转置
    for (int i = 0; i < sm->num; i++) {
        int col = sm->data[i].col;  // 原矩阵的列号
        int pos = rowStart[col];    // 在转置矩阵中的位置
        
        result->data[pos].row = sm->data[i].col;    // 行列互换
        result->data[pos].col = sm->data[i].row;
        result->data[pos].value = sm->data[i].value;
        
        rowStart[col]++;  // 该行的下一个位置
    }
    
    free(colCount);
    free(rowStart);
    
    return result;
}

五、算法复杂度分析

1. 普通转置算法

复制代码
// 时间复杂度分析
for (int col = 0; col < sm->cols; col++) {      // 循环 cols 次
    for (int i = 0; i < sm->num; i++) {         // 每次循环 num 次
        // 检查每个元素
    }
}

时间复杂度:O(cols × num)

  • 适合:非零元素非常少的情况

  • 缺点:当列数很大时效率低

2. 快速转置算法

时间复杂度:O(cols + num)

  1. 统计每列非零元素数:O(num)

  2. 计算起始位置:O(cols)

  3. 执行转置:O(num)

空间复杂度:O(cols)(需要额外的两个数组)

六、优缺点总结

优点:

  1. 节省存储空间:只存储非零元素

  2. 提高运算效率:避免对零元素的无意义运算

  3. 适合大型稀疏矩阵:内存消耗大大减少

缺点:

  1. 随机访问慢:需要顺序查找特定位置的元素

  2. 插入/删除复杂:需要维护有序性

  3. 不适合密集矩阵:存储开销反而更大

相关推荐
YGGP2 小时前
【Golang】LeetCode198. 打家劫舍
算法·leetcode
啊阿狸不会拉杆2 小时前
《数字图像处理》实验6-图像分割方法
图像处理·人工智能·算法·计算机视觉·数字图像处理
YGGP2 小时前
【Golang】LeetCode 152. 乘积最大子数组
算法·leetcode
范纹杉想快点毕业2 小时前
C语言设计模式:从基础架构到高级并发系统(完整实现版)
c语言·开发语言·设计模式
爱学大树锯2 小时前
171 · 乱序字符串
算法
小李小李快乐不已2 小时前
栈和堆理论基础
c++·算法·leetcode
最爱吃咸鸭蛋2 小时前
LeetCode 97
算法·leetcode·职场和发展
淮北也生橘122 小时前
Linux驱动开发:移植一个MIPI摄像头驱动并将其点亮(基于Sstar 2355平台)
linux·运维·驱动开发·嵌入式linux
遇见火星2 小时前
Linux运维:RPM包配置管理指南
linux·运维·服务器·rpm