特殊矩阵的压缩存储(详细版 通俗易懂 含c语言稀疏矩阵十字链表代码 )

前言

此文章是本人第一篇博客,目的在于巩固过去所学的知识,同时可能会给大家带来一丝丝帮助,但由于没有经验加上本人能力极其有限,文章中可能存在不足之处,还请读者能够指正(`・ω・´)。

这篇文章首先会介绍矩阵压缩存储和特殊矩阵的基本概念,然后将会比较详细地说明几种特殊的矩阵:对称矩阵、三角矩阵、带状矩阵(三对角矩阵)、稀疏矩阵的压缩存储的策略,包括定量计算和十字链表的c语言代码和效果图,让我们开始吧!

注意,本文所用数组下标均为从0开始

正文

一.基本概念

压缩存储

在一个矩阵中,若多个数据的值相同,为了节省内存空间,则只分配一个元素值的空间,且零元素不占存储空间

特殊矩阵

可不是所有的矩阵都适合压缩存储!比如下面这个

1234
5678
9101112
13141516

下面介绍的几个特殊矩阵,才是将要重点介绍的对象:

1.对称矩阵

对称矩阵是指元素以主对角线为对称轴对应相等的n阶矩阵,即aᵢ,ⱼ=aⱼ,ᵢ  

比如

51234
15678
265910
379512
4810125

2.三角矩阵

三角矩阵是方阵的一种,分为上三角矩阵和下三角矩阵,上三角矩阵是指下三角区所有元素均为同一常量,下三角矩阵是指上三角区所有元素均为同一常量

上三角矩阵
下三角矩阵

 3.带状矩阵(三对角矩阵)

带状矩阵指所有非零元素都集中在以主对角线为中心的3条对角线的区域,其他区域均为零的矩阵特点:当|i-j|>1时,有aᵢ,ⱼ=0  或者说主对角线的相邻的上下左右元素可以是非0元素,再往外肯定是0元素的矩阵

如下图所示:

带状矩阵

4.稀疏矩阵 

在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵,但是对于这个“远远多于”并没有准确的定量规定。

比如

稀疏矩阵的例子

行优先与列优先   映射

对于一个二维数组,外观上看是一个矩阵,但是电脑内存是线性存储的,所以电脑会自动按照一定的规则,去把二维数组转换为地址连续的块放到内存中,这些规则分为行优先和列优先

比如下面这个矩阵

1234
5678
9101112
13141516

如果按行优先,放到内存就是1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

列优先就是1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16

我们在矩阵的压缩存储中,通常的策略就是把二维矩阵通过一定的下标(i,j)的映射规则放入到一维数组中,比如说呢,下面这个对称矩阵

24
42

放到一维数组a中只需包含 2 4,分别为a[0]和a[1],数组的下标0与1与矩阵Ai,j的元素下标i和j的关系就是所谓的映射,这也是定量计算的重点!

二.特殊矩阵存储策略 

1.对称矩阵

设有对称矩阵A[i][j]如下图:(aᵢ,ⱼ=aⱼ,ᵢ )

 存储策略:只存储主对角线+下三角区(或主对角线+上三角区),以主对角线+下三角区为例,按照行优先把这些元素放入到一维数组中,就得到了下面的样子的一维数组:

a₁₁a₂₁a₂₂a₃₁...aₙ,ₙ₋₂aₙ,ₙ₋₁aₙ,ₙ

这个一维数组的大小是1+2+3+...+n=n*n(n+1)/2

下面来把行号和列号转换为数组下标: 

aᵢ,ⱼ是第i行的元素,所以前面还有i-1行,元素总数为1+2+...+i-1=i*(i-1)/2

j代表其属于第j列,所以最终确定按照行优先的存储aᵢ,ⱼ应该在一维数组的第i*(i-1)/2+j个位置,但是由于数组是从0开始存的,所以对应下标为i*(i-1)/2+j-1

上述讨论的均为在下三角区的情形,此时i>=j

现在根据性质aᵢ,ⱼ=aⱼ,ᵢ可以得到矩阵所有元素与一维数组下标k的对应关系如下:

 2.三角矩阵

给出下面的下三角矩阵A[i][j]

存储策略:按照行优先将下三角区域(橙色部分)放入一位数组中,并将常数c放到最后一个位置

得到下图的一位数组

a₁₁a₂₁a₂₂a₃₁...aₙ,ₙ₋₁aₙ,ₙc

这个一位数组的大小为:1+2+3+...+n+1=n*n(n+1)/2+1

这个映射关系和对称矩阵几乎一样,唯一不同的是当i<j,即取上三角的元素时,都得到存在一维数组最后一个位置的常数c,如下图

 再来看上三角矩阵

给出下面的上三角矩阵A[i][j]

可以按照行优先原则,把上三角区域的元素(绿色区域),放入一位数组中,并在最后一个位置放入常数c,即可得到下面的一维数组

a₁₁a₁₂a₁₃...a₁,ₙ...aₙ,ₙc

这个的数组大小与下三角的一样,对于一个元素aᵢ,ⱼ,第i行的元素要存n-i+1个元素,第i-1行要存n-i+2个元素,所以前i-1行的元素总数为n+n-1+...+n-i+2,由于在第j列,所以对应第i行的第j-i+1个元素,总的来说,就是数组中的第n+n-1+...+n-i+2+j-i+1个元素,在数组中下标从0开始所以为n+n-1+...+n-i+2+j-i

综上,可以得到如下的映射关系

 3.带状矩阵

给出下面的带状矩阵A[i][j]

 存储策略,按照行优先的原则,只存储带状部分,便可以得到下面的一维数组:

a₁₁a₁₂a₂₁a₂₂...aₙ₋₁,ₙaₙ,ₙ₋₁aₙ,ₙ

先计算一维数组的大小,每行三个元素,共n行,3*n,但第一行和最后一行仅仅2个元素,所以总共个数为3*n-2

下面研究映射关系:

当|i-j|>1时,值就是0

当当|i-j|<=1时,对于一个元素aᵢ,ⱼ,前i-1行,每行有三个,第一行少了一个,总共为3*(i-1)-1个元素,而且找规律可以知道,这个元素在第i行是第j-i+2个元素,所以总共是数组的第3*(i-1)-1+j-i+2个元素即第2*i+j-2个元素,对应数组下标为2*i+j-3

所以映射关系为

下面再来探讨一个逆问题,就是如果给出数组下标k,想要得到这个元素在第几行第几列怎么办呢?

首先,这个元素是第k+1个元素,假设它在第i行第j列,那么前i-1行元素总数为3*(i-1)-1,前i行元素总数为3*i-1,满足下面的不等式

 3*(i-1)-1<k+1<=3*i-1

解得:i>=(k+2)/3

为了让这个i“刚好大于(k+2)/3”,让其向上取整,即i=⌈(k+2)/3⌉(提示:⌈⌉是向上取整,⌊⌋是向下取整)

然后再根据我们上面得出的关系式:k=2*i+j-3综合可得j的值,从而得到i和j即为元素行数和列数

4.稀疏矩阵

给出下面这个稀疏矩阵

 给出两个方法去存储它

第一个方式是顺序存储,通过一个包含行号、列号、元素值的三元组,去进行存储,比如这个矩阵,我们可以用三元组表示为:

行数  i列数  j值  v
134
165
223
249
357
422

可以定义包括i j v 的结构体数组

第二个方法是链式存储的十字链表法

可以表示为下图:

 对于每一个非0元素的结点,包含3个数据域行数i、列数j、元素值v,和两个指针域,左边的指针指向同列的下一个元素,右边的指针指向同行的下一个元素

下面给出十字链表的c语言实现代码以及效果图,如果代码有不足之处,可以指出!:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OVERFLOW -1
#define OK 1
typedef  int ElemType;
typedef  int Status;
typedef struct OLNode{
	int i,j;
	ElemType e;
	struct OLNode *right,*down;
}OLNode,*OLink;
typedef struct {
	OLink *rhead,*chead; //行和列链表头指针向量基地址由CreatSMatrix分配 
	int mu,nu,tu;//稀疏矩阵行数,列数和非0元素个数 
}CrossList,*CrossListPtr; 

Status CreatSMatrix_OL (CrossListPtr M)
{
	//创建稀疏矩阵M,采用十字链表存储表示
	if(M) free(M);//释放M原有的内存
	M=(CrossListPtr)malloc(sizeof(CrossList));
	int m,n,t,a;
	int i,j,e;
	int cnt; 
	OLNode *p,*q;
	printf("请分别输入行数,列数,非零元素的个数:");
	scanf("%d%d%d",&m,&n,&t);//输入M的行数列数和非零元素个数
    M->mu=m;M->nu=n;M->tu=t;//赋到M里面
	if(!(M->rhead=(OLink *)malloc((m+1)*sizeof(OLink)))) exit(OVERFLOW);
	if(!(M->chead=(OLink *)malloc((n+1)*sizeof(OLink)))) exit(OVERFLOW);
	//分别为行链表头和列链表头分配内存
	for(a=1;a<=m;a++) M->rhead[a]=NULL;
	for(a=1;a<=n;a++) M->chead[a]=NULL;
	//使各行列链表都为空表
	
	for(cnt=1;cnt<=t;cnt++)//按任意次序输入非0元素,
	{
		printf("请分别输入第%d个元素的行数,列数和值:",cnt);
		scanf("%d%d%d",&i,&j,&e);
		if(!(p=(OLNode *)malloc(sizeof(OLNode)))) exit(OVERFLOW);
		p->i=i;p->j=j;p->e=e;//生成结点
		if(M->rhead[i]==NULL||M->rhead[i]->j>j) 
		{
			p->right=M->rhead[i]; M->rhead[i]=p;
		} //如果行链表数组的第i行的头结点右边是空或者右边元素的列数比j大,让p左边被链表的rhead指针指住
		else
		{
			for(q=M->rhead[i];(q->right)&&q->right->j<j;q=q->right);//找到将要插入的合适位置,要么是右边为空,要么q->right->j<j
			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;
		 }//列插入 
	} 
	//下面检验是否存储成功 
    int check[10][10];
	memset(check,0,sizeof(check));
	int i1,j1;
	OLink p1;
	for(i1=1;i1<=M->mu;i1++)
	{
		for(p1=M->rhead[i1];p1;p1=p1->right)
		{
			check[i1][p1->j]=p1->e;
		}	
		
    } 
    for(i1=1;i1<=M->mu;i1++)
    {
    	for(j1=1;j1<=M->nu;j1++)
		{
			printf("%d ",check[i1][j1]);
		} 
		printf("\n");
	}
	return OK;
}
int main()
{
	CrossListPtr M=NULL;
	CreatSMatrix_OL(M);
	return 0;
}

这是效果图:

 今天的文章就到这里了(`・ω・´)

### 如何在C语言中实现任意维度的矩阵相乘 在C语言中实现任意维度的矩阵相乘,需要考虑以下几个方面: 1. **输入验证**:确保两个矩阵能够相乘。即第一个矩阵的列数等于第二个矩阵的行数[^1]。 2. **动态内存分配**:由于矩阵可能是任意大小,因此通常会使用`malloc`函数来动态分配存储空间[^5]。 3. **核心逻辑**:利用三重嵌套循环完成矩阵乘法的操作。 下面是一个完整的示例代码,展示了如何在C语言中实现任意维度的矩阵相乘功能: ```c #include <stdio.h> #include <stdlib> // 定义一个函数用于释放二维数组的空间 void freeMatrix(int **matrix, int rows) { for (int i = 0; i < rows; ++i) { free(matrix[i]); } free(matrix); } // 矩阵相乘函数 int **matrixMultiply(const int **A, const int **B, int A_rows, int A_cols_B_rows, int B_cols) { // 动态分配结果矩阵的空间 int **result = malloc(A_rows * sizeof(int *)); for (int i = 0; i < A_rows; ++i) { result[i] = calloc(B_cols, sizeof(int)); } // 执行矩阵乘法 for (int i = 0; i < A_rows; ++i) { for (int j = 0; j < B_cols; ++j) { for (int k = 0; k < A_cols_B_rows; ++k) { result[i][j] += A[i][k] * B[k][j]; } } } return result; } int main() { // 输入矩阵A的行列数 int A_rows, A_cols; printf("请输入矩阵A的行数和列数: "); scanf("%d%d", &A_rows, &A_cols); // 动态分配矩阵A的空间并读取数据 int **A = malloc(A_rows * sizeof(int *)); for (int i = 0; i < A_rows; ++i) { A[i] = malloc(A_cols * sizeof(int)); printf("请输入矩阵A的第%d行元素: ", i + 1); for (int j = 0; j < A_cols; ++j) { scanf("%d", &A[i][j]); } } // 输入矩阵B的行列数 int B_rows, B_cols; printf("请输入矩阵B的行数和列数: "); scanf("%d%d", &B_rows, &B_cols); if (A_cols != B_rows) { printf("错误:无法执行矩阵乘法,因为矩阵A的列数不等于矩阵B的行数。\n"); return 1; } // 动态分配矩阵B的空间并读取数据 int **B = malloc(B_rows * sizeof(int *)); for (int i = 0; i < B_rows; ++i) { B[i] = malloc(B_cols * sizeof(int)); printf("请输入矩阵B的第%d行元素: ", i + 1); for (int j = 0; j < B_cols; ++j) { scanf("%d", &B[i][j]); } } // 调用矩阵乘法函数 int **result = matrixMultiply(A, B, A_rows, A_cols, B_cols); // 输出结果矩阵 printf("结果矩阵如下:\n"); for (int i = 0; i < A_rows; ++i) { for (int j = 0; j < B_cols; ++j) { printf("%d\t", result[i][j]); } printf("\n"); } // 释放所有动态分配的内存 freeMatrix(A, A_rows); freeMatrix(B, B_rows); freeMatrix(result, A_rows); return 0; } ``` #### 关键点解析 - 上述程序首先定义了一个辅助函数 `freeMatrix` 来释放二维数组占用的堆内存[^5]。 - 使用了标准库中的 `malloc` 和 `calloc` 函数来进行动态内存管理[^5]。 - 核心部分是三层嵌套循环,其中外层两层控制遍历结果矩阵的位置,最内层负责累加对应的乘积项[^1]。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值