数据结构与算法: 特殊矩阵的压缩存储

什么是矩阵?

在这里插入图片描述

什么是压缩存储?

在这里插入图片描述


一.对称矩阵的压缩存储:

在这里插入图片描述

  • 样式
    在这里插入图片描述

  • 储存方式图示:
    在这里插入图片描述

  • 对称矩阵的存储结构:
    在这里插入图片描述

  • 对称矩阵的压缩存储:

//对称矩阵的压缩存储
#include <stdio.h>
#define len 5

int main(){
    //定义对称矩阵
    int A[len][len] = {1,2,3,4,5,
                       2,3,4,5,6,
                       3,4,5,6,7,
                       4,5,6,7,8,
                       5,6,7,8,9};

    //定义存储数组
    int B[len*(len + 1) / 2];

    //进行压缩存储
    for(int i = 0;i < len ;i++){
        for(int j = 0;j <= i;j++){
            if (i >= j) {
                B[i*(i+1)/2+j] = A[i][j]; // 二维转一维
            } else break;
        }
    }
    printf("压缩矩阵的元素是:\n");

    //输出B中元素
    for (int k = 0; k < len*(len + 1) / 2; ++k) {
        printf("%d ",B[k]);
    }
    return 0;
} 

在这里插入图片描述


  • 对称矩阵的基本应用:
#include <stdio.h>
#include <stdlib.h>
#define M 10
#define N 4
int main()
{
    int a[M]={1,2,3,4,5,6,7,8,9,10};
    int b[M]={1,1,1,1,1,1,1,1,1,1};
    int c[N][N],d[N][N];
    int i,j,k=0,s;
    for(i=0;i<N;i++)
        for(j=0;j<=i;j++)
        {
            c[i][j]=a[k];
            d[i][j]=b[k];
            k++;
        }
    for(i=0;i<N-1;i++)
        for(j=i+1;j<N;j++)
        {
            c[i][j]=c[j][i];
            d[i][j]=d[j][i];
        }
    printf("1、输出对称矩阵M:\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N-1;j++)
            printf("%d ",c[i][j]);
        printf("%d\n",c[i][j]);
    }
    printf("2、输出对称矩阵N:\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N-1;j++)
            printf("%d ",d[i][j]);
        printf("%d\n",d[i][j]);
    }
    printf("3、两个对称矩阵M、N的和为:\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N-1;j++)
            printf("%d ",c[i][j]+d[i][j]);
        printf("%d\n",c[i][j]+d[i][j]);
    }
    printf("3、两个对称矩阵M、N的积为:\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N-1;j++)
        {
            s=0;
            for(k=0;k<N;k++) s+=c[j][k]*d[k][j];
            printf("%d ",s);
        }
        s=0; for(k=0;k<N;k++) s+=c[j][k]*d[k][j];
        printf("%d\n",c[i][j]+d[i][j]);
    }
    return 0;
}

在这里插入图片描述


以下转载自 : https://blog.csdn.net/SWEENEY_HE/article/details/85956176

二.三角矩阵的压缩存储

以主对角线划分三角矩阵有下三角矩阵和上三角矩阵

下三角矩阵:矩阵(除主对角线)的上三角部分的值均为一个常数C或者0

上三角矩阵:与下三角矩阵相反

图示:(图中蓝色主对角线部分元素(一般情况)永远不都为一个常数或者0)

               

二.压缩原理:

根据上、下三角矩阵的特殊性(有一小半部分的元素都为一个常数C或者0)我们可以考虑将这一半的空间压缩到一个元素(多对一的映射),然后另一半的部分就类似对称矩阵一半部分的压缩存储了。

三.矩阵坐标[i,j]转换为一维数组下标K的方法:

1>下三角矩阵:

(1).首先考虑,非重复部分的存储,见图:

可以得出 k = i(i-1)/2+j-1

(2).重复部分只需一个元素即可,为不影响前面的存储,考虑放到所有非重复元素的后面即可, 由于前面部分总共的元素个数为:

n(1+n)/2(等差数列求和,每行元素逐级递增)又由于数组以0为起点,所以放到n(1+n)/2的位置即可。

总结:

k = i(i-1)/2+j-1 (i >=j)  

k = n(n+1)/2 (i>j)   

2>上三角矩阵

(1).先考虑非重复部分的存储,图示:

  

 

按行优先存储,矩阵中元素对应一维数组的元素规则为:

由图显而易见,某元素在一维数组中的下标就是它按行优先存储时在半个矩阵中的序号

(1-1)序号 = 所有在它前面元素的个数 = 它所在行前面行的所有元素+它所在行它前面的元素

由于每行元素个数逐级递减,构成一个等差数列,公差:d = 1,首项:a1 = n ,末项:an = n-(i-1) = n-i+1(经评论区提醒,已自纠)

(1-2)前i-1行元素个数为:sn = n(a1+an)/2 = (i-1){n+[n-(i-1)+1]}/2=(i-1)(2n-i+2)/2  

(1-3)第i行中在它前面的元素个数为:j-1

(1-4)公式:K =(i-1)(2n-i+2)/2+j-1

(2).考虑重复部分元素和下三角一样:k = n(n+1)/2

总结:

K = (i-1)(2n-i+2)/2+j-i-1 (i >=j)

K = n(n+1)/2 (i>j)

  • 下三角矩阵压缩存储代码实现:
#include<stdio.h>

int main() {
    int a[5][5] = {
                    1,0, 0, 0, 0,
                    5,9, 0, 0, 0,
                    4,6, 8, 0, 0,
                    2,3, 44,55,0,
                    7,11,12,13,14
                  };

    int len = sizeof(a[5])/sizeof(int);
    int b[len*(len+1)/2], x, y, k;

    printf("原二维数组:\n");    //输出原二维数组

    for (x = 0; x < len; x++){
        for (y = 0; y < len; y++){
            if (a[x][y] < 10){ // 使得输出更加整齐
                printf("%d  ", a[x][y]);
            } else {
                printf("%d ", a[x][y]);
            }
        }

        printf("\n");

    }

    printf("压缩后的一维数组:\n");

    for (int i = 0; i < len; i++){         //将二维数组中非0值压缩至一维数组中
        for (int j = 0; j < len; j++){
            if (i >= j) {   //特殊矩阵,只压下三角的值
                k = i * (i + 1) / 2 + j;  // 二维数组和一维数组中原值的对应关系
                b[k] = a[i][j];
            } else break; // 提升性能
        }
    }

    for (int l = 0; l < len*(len+1)/2; l++){      //输出一维数组
        printf("%d ", b[l]);
    }

    printf("\n");
    printf("输入要查询的 行号&&列号 : ");   //输出要查询的数据
    scanf("%d%d", &x, &y);
    printf("\n");
    printf("您查询的数据是: ");

    if (x < y)  printf("0\n");  //如果上三角直接输出0
    else printf("%d\n", b[(x - 1) * (x) / 2 + y - 1]);  // 下三角输出一维数组中对应的值

    return 0;
}

在这里插入图片描述


三. 对角矩阵的压缩存储:

在这里插入图片描述


存储方法:

在这里插入图片描述二维数组存储


所有非主对角线元素全等于零的n阶矩阵,称为对角矩阵或称为对角方阵。

在这里插入图片描述


对角矩阵压缩存储代码实现:

#include <stdio.h>
#include <stdlib.h>
#define n 4
int d[n];
void Store(int x, int i, int j){
    /* 把x存为D ( i , j ) */
	if (i<0||j<0||i>=n||j>=n)
	{
		printf("数组出界!");
		exit(1);
	}
	if (i != j && x != 0)
	{
		printf("非对角线上元素值必须为零");
		exit(1);
	}
	if (i == j)
		d[i] = x;
}

int main(){
	int i,j;
	int D[n][n] ={{2,0,0,0},{0,1,0,0},{0,0,4,0},{0,0,0,6}};
	for(i=0;i<n;i++){
        for(j=0;j<n;j++){
            Store(D[i][j],i,j);
        }
	}
	for(i=0;i<n;i++) {
        printf("%d  ",d[i]);
	}
	printf("\n");

	return 0;
}

在这里插入图片描述


四. 稀疏矩阵的压缩存储:

在这里插入图片描述
三元组(i , j ,aij)唯一确定矩阵的一个非零元=


三元组顺序表:

在这里插入图片描述
三元顺序表的优缺点:

在这里插入图片描述


  • 稀疏矩阵的三元组压缩存储以及简单应用:
// δ=t/(n?m) <= 0.05 则证明是稀疏矩阵
/*  稀疏矩阵的类型说明及转置算法  */
#include<stdio.h>
#include<stdlib.h>

#define MAX 12

typedef int datatype;

typedef struct{
    int i, j;          //行号、列号
    datatype  v;       //元素值
} node;

typedef struct{
    int m, n, t;        //行数,列数,非零元素个数
    node data[MAX];     //三元组表
}spMatrix;

spMatrix *Create();   //创建一个3行4列的稀疏矩阵
spMatrix *TransMat(spMatrix *a);    //稀疏矩阵的转置
void Output(spMatrix *a);     //在屏幕上以行列的形式输出矩阵

int main(){
    printf("输入12个整数:\n");
    spMatrix *a = Create();
    spMatrix *b = TransMat(a);
    printf("原矩阵:\n");
    Output(a);
    printf("转置矩阵:\n");
    Output(b);
    return 0;
}

spMatrix *Create(){ // 创建一个3行4列的稀疏矩阵
    int m = 3, n = 4, k = 0, t = 0;
    datatype element;
    spMatrix *matrix;
    matrix = (spMatrix *)malloc(sizeof(spMatrix));   //创建一个稀疏矩阵a
    matrix->m = m;
    matrix->n = n;
    while (k < m*n){
        scanf("%d", &element);
        if (element != 0){
            matrix->data[t].i = k / n;
            matrix->data[t].j = k % n;
            matrix->data[t].v = element;
            t++;
        }
        k++;
    }
    matrix->t = t;
    return matrix;
}

spMatrix *TransMat(spMatrix *a){     //稀疏矩阵的转置
    int p, q, bno = 0;
    spMatrix *b;
    b = (spMatrix *)malloc(sizeof(spMatrix));   //为矩阵b分配内存空间
    b->m = a->n;
    b->n = a->m;
    b->t = 0;
    if (a->t == 0)    //若b中元素全为零,则将b返回
        return b;
    for (p = 0; p < a->n; p++)
        for (q = 0; q < a->t; q++)
            if (a->data[q].j == p){
                b->data[bno].i = a->data[q].j;
                b->data[bno].j = a->data[q].i;
                b->data[bno].v = a->data[q].v;
                bno++;
            }
    b->t = bno;
    return b;
}

void Output(spMatrix *a){
    //输出:在屏幕上以行列的形式输出矩阵
    int i = 0, j = 0, k = 0;
    for (i = 0; i < a->m; i++){
        for (j = 0; j < a->n; j++){
            if (i == a->data[k].i && j == a->data[k].j){
                printf("%d ", a->data[k].v);
                k++;
            }
            else
                printf("%d ", 0);
        }
        printf("\n");
    }
}

在这里插入图片描述


稀疏矩阵的链式存储结构 (十字链表)

优点:它能够灵活地插入因运算而产生的新的非零元素,删除因运算而产生的新的零元素,实现矩阵的运算。

在十字链表中,矩阵的每一个非零元素用一个结点表示,该结点除了(row,col,value)外,还有两个域:

right: 用于链接同一行中的下一个非零元素;

down:用以链接同一列中的下一个非零元素。

十字链表中结点的结构示意图:

在这里插入图片描述在这里插入图片描述


稀疏矩阵十字链表压缩存储实现:

// 压缩矩阵的十字矩阵压缩存储

#include<stdio.h>
#include<stdlib.h>

#define   OK           1
#define   ERROR        0
#define   OVERFLOW    -2


typedef int ElemType;

typedef struct OLNode{
    int i,j;       // 非零元的行下标和列下标
    ElemType e;    // 非零元的值
    struct OLNode  *right, *down; // 该非零元所在的行表和列表的后继链域
}OLNode, *OLink;


typedef struct{
    OLink *rhead, *chead; // 行和链链表头指针向量基址
    int mu,nu,tu; // 行数,列数,非零个数
}CrossList;

int CreateSMatrix_OL(CrossList *M){
    if(M) free(M); // 如果十字链已存在,则先释放
    int m,n,t=0;
    scanf("%d%d",&m,&n); // 矩阵的行列
    M->mu=m; M->nu=n, M->tu = t; // 行列数赋值
    if(!(M->rhead=(OLink *)malloc((m+1)*sizeof(OLink))))  return ERROR; // 创建行指针,创建失败则返回 ERROR
    if(!(M->chead=(OLink *)malloc((n+1)*sizeof(OLink))))  return ERROR; // 创建列指针,创建失败则返回 ERROR

    for (int a = 1; a <= m; a++)  M->rhead[a]=NULL; // 初始化置NULL
    for (int b = 1; b <= n; b++)  M->chead[b]=NULL; // 初始化置NULL

    int e; // 定义输入元素
    for(int i = 1;i <= m; i++){
        for(int j = 1; j <= n; j++){
            scanf("%d",&e);
            if(e!=0){
                t++; // 非零个数累加
                OLNode *p,*q;
                if(!(p = (OLNode *)malloc(sizeof(OLNode))))  exit(OVERFLOW); // 创建一个 p指针节点,用于储存输入的数据
                p->i=i; p->j=j; p->e=e; p->down=NULL; p->right=NULL; // 节点赋值
                if(M->rhead[i] == NULL || 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(M->chead[j] == NULL || 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;
                }
            }
        }
    }
    M->tu=t;
    return OK;
}

void print(CrossList M){
    OLNode *pTemp;
    for(int p = 1; p <= M.mu; p++){
        pTemp=M.rhead[p];
        for(int q = 1; q <= M.nu; q++){
            if(pTemp != NULL && pTemp->j == q){
                printf("%d ",pTemp->e);
                pTemp = pTemp->right;
            }
            else
                printf("0 ");
        }
        printf("\n");
    }
}

int main(){
    CrossList M;
    CreateSMatrix_OL(&M);
    print(M);
    return 0;
}

在这里插入图片描述

  • 21
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0rta1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值