特殊矩阵的压缩存储

1. 对称矩阵

对于 n 阶对称矩阵,可以存储矩阵的上半部分(包括对角线)或下半部分,总共需要 \frac{n(n+1)}{2}个空间。

对于矩阵\begin{bmatrix} 1 &5 &1 & 3 & 7\\ 5& 0 &8 &0 &0 \\ 1& 8 & 9 & 2 &6 \\ 3& 0 &2 &5 &1 \\ 7& 0 &6 &1 &3 \end{bmatrix},可以只存储\begin{bmatrix} 1& & & & \\ 5& 0& & & \\ 1& 8& 9& & \\ 3& 0& 2& 5& \\ 7& 0& 6& 1&3 \end{bmatrix}或存储\begin{bmatrix} 1& 5 &1 &3 &7 \\ & 0& 8 & 0 & 0\\ & & 9& 2 &6 \\ & & & 5 &1 \\ & & & & 3 \end{bmatrix}

代码如下:

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

int main() {

	int arr[5][5] = { 1,5,1,3,7,5,0,8,0,0,1,8,9,2,6,3,0,2,5,1,7,0,6,1,3 };
	int row = sizeof(arr) / sizeof(arr[0]);
	int col = sizeof(arr[0]) / sizeof(int);
	int* ret = (int*)malloc(sizeof(int) * row * (row + 1) / 2);
	int p = -1;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j <= i; j++) {
			ret[++p] = arr[i][j];
		}
	}
	p = -1;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j <= i; j++) {
			printf("%d ", ret[++p]);
		}
		printf("\n");
	}

	return 0;
}

2. 三角矩阵

三角矩阵的下半部分(不包括对角线)或上半部分的所有元素相同,可以用数组中的第一个位置来记录,矩阵的其余位置存储方式与对称矩阵相同。

对于矩阵\begin{bmatrix} 1& 0& 0& 0& 0\\ 5& 0& 0& 0&0 \\ 1& 8& 9& 0&0 \\ 3& 0& 2& 5&0\\ 7& 0& 6& 1&3 \end{bmatrix}代码如下:

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

int main() {

	//下三角矩阵
	int arr[5][5] = { 1,0,0,0,0,5,0,0,0,0,1,8,9,0,0,3,0,2,5,0,7,0,6,1,3 };
	int row = sizeof(arr) / sizeof(arr[0]);
	int col = sizeof(arr[0]) / sizeof(int);
	int* ret = (int*)malloc(sizeof(int) * (row * (row + 1) / 2 + 1));
	int p = -1;
	ret[++p] = arr[0][3];
	for (int i = 0; i < row; i++) {
		for (int j = 0; j <= i; j++) {
			ret[++p] = arr[i][j];
		}
	}
	p = -1;
	printf("上三角部分的所有元素为:%d\n", ret[++p]);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j <= i; j++) {
			printf("%d ", ret[++p]);
		}
		printf("\n");
	}

	//该矩阵的上三角版本
	int arr2[5][5] = { 1,5,1,3,7,0,0,8,0,0,0,0,9,2,6,0,0,0,5,1,0,0,0,0,3 };
	row = sizeof(arr) / sizeof(arr[0]);
	col = sizeof(arr[0]) / sizeof(int);
	int* ret2 = (int*)malloc(sizeof(int) * (row * (row + 1) / 2 + 1));
	p = -1;
	ret2[++p] = arr2[3][0];
	for (int i = 0; i < row; i++) {
		for (int j = i; j < col; j++) {
			ret2[++p] = arr2[i][j];
		}
	}
	p = -1;
	printf("下三角部分的所有元素为:%d\n", ret[++p]);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (j >= i) {
				printf("%d ", ret2[++p]);
			}
			else {
				printf("  ");
			}
		}
		printf("\n");
	}

	return 0;
}

3. 对角矩阵

形如\begin{bmatrix} 2&1 &0 &0 \\ 3& 1 & 3 &0 \\ 0&5 &2 &7 \\ 0& 0 & 9 &0 \end{bmatrix}的矩阵称为三对角矩阵,由此可以推广到 n 对角矩阵,对于对角矩阵,可以只存储对角线,每个对角线占一行,所以对于三对角矩阵,就需要三行的矩阵来存储,如果对角线数量越少,矩阵阶数越高,则压缩后节省的空间越多。

代码如下:

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

int main() {

	int arr[4][4] = { 2,1,0,0,3,1,3,0,0,5,2,7,0,0,9,0 };
	int row = sizeof(arr) / sizeof(arr[0]);
	int col = sizeof(arr[0]) / sizeof(int);
	int ret[3][4];
	//假设对角矩阵中没有负数,将ret矩阵初始化为-1,输出的时候-1就代表该位置不存储对角线上的元素
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++)
			ret[i][j] = -1;
	}
	int p = 0, q = 0;//ret的行和列
	int i, j;//arr的行和列
	int ilimit, jlimit;//分别表示方向为左上到右下的对角线的最大起始横坐标和最大起始纵坐标
	//在此矩阵中,上半部分和下半部分都只有一个对角,最大起始纵坐标和最大起始横坐标都为1
	jlimit = ilimit = 1;

	//存储上半部分对角
	for (int k = jlimit; k > 0; k--) {
		i = 0;
		j = k;
		q = 0;
		while (j < col) {
			ret[p][q++] = arr[i++][j++];
		}
		p++;
	}

	//存储主对角线
	i = j = 0;
	q = 0;
	while (j < col) {
		ret[p][q++] = arr[i++][j++];
	}
	p++;

	//存储下半部分对角线
	for (int k = 1; k <= ilimit; k++) {
		i = k;
		j = 0;
		q = 0;
		while (i < row) {
			ret[p][q++] = arr[i++][j++];
		}
		p++;
	}

	for (i = 0; i < 3; i++) {
		for (j = 0; j < row; j++) {
			printf("%d ", ret[i][j]);
		}
		printf("\n");
	}

	return 0;
}

4.  稀疏矩阵

稀疏矩阵指矩阵中的大部分都是零(也可以指无意义的数据),少部分值是非零元素。

如矩阵\begin{bmatrix} 3 & 0 &0 &5 \\ 0&-1 & 0 &0 \\ 2 &0 &0 &0 \\ \end{bmatrix},压缩稀疏矩阵有两种方法:顺序表法(三元组和链表)和十字链表法。

三元组代码:

int** SanYuanZu(int** arr, int row, int col) {//为了通用性,最好传递使用动态内存分配建立的矩阵,否则形参需要指定矩阵列数
	
	//获得矩阵中非零元素的数量
	int num = 0;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (arr[i][j]) {
				num++;
			}
		}
	}

	//建立三元组,表示稀疏矩阵,每个非零元素可以用横坐标、纵坐标以及其值表示
	//所以可以使用一个有3列的二维数组表示,第一列表示行,第二列表示列,第三列表示值
	//三元组的第一行的元素表示矩阵的行、列以及非零元素的个数
	int** matrix = (int**)malloc(sizeof(int*) * (num + 1));
	for (int i = 0; i <= num; i++) {
		matrix[i] = (int*)malloc(sizeof(int) * 3);
	}
    //第一行为矩阵信息
	matrix[0][0] = row;//行数
	matrix[0][1] = col;//列数
	matrix[0][2] = num;//非零元素数量
	int m = 1;

    //将非零元素存入三元组
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (arr[i][j]) {
				matrix[m][0] = i;
				matrix[m][1] = j;
				matrix[m][2] = arr[i][j];
				m++;
			}
		}
	}

	//由三元组恢复原矩阵,这个过程可以放到单独的函数中,此处为了方便测试
	int** M = (int**)malloc(sizeof(int) * matrix[0][0]);
	for (int i = 0; i < matrix[0][0]; i++)
		M[i] = (int*)calloc(matrix[0][1], 4);
	for (int i = 1; i <= matrix[0][2]; i++) {
		M[matrix[i][0]][matrix[i][1]] = matrix[i][2];
	}
	for (int i = 0; i < matrix[0][0]; i++) {
		for (int j = 0; j < matrix[0][1]; j++) {
			printf("%2d ", M[i][j]);
		}
		printf("\n");
	}
	return matrix;
}

链表代码: 

typedef struct list {
	int row;
	int col;
	int val;
	list* next;
}list;

list* List(int** arr, int row, int col) {//为了通用性,最好传递使用动态内存分配建立的矩阵,否则形参需要指定矩阵列数
	
	//首先建立链表头节点,便于链表的管理,头节点的行和列表示矩阵的行和列,val表示非零元素的个数
	list* head = (list*)malloc(sizeof(list));
	head->next = NULL;
	head->col = col;
	head->row = row;
	head->val = 0;

	//遍历矩阵,将非零元素的信息存入链表,由于数组的修改和查找可以直接使用下标
	//所以存入链表的数据没有顺序要求,就在头部进行插入
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (arr[i][j]) {
				head->val++;
				list* tmp = (list*)malloc(sizeof(list));
				tmp->row = i;
				tmp->col = j;
				tmp->val = arr[i][j];
				tmp->next = head->next;
				head->next = tmp;
			}
		}
	}

	//由链表还原并打印矩阵
	int** M = (int**)malloc(sizeof(int*) * head->row);
	for (int i = 0; i < head->row; i++)
		M[i] = (int*)calloc(head->col, sizeof(int));
	//接下来遍历链表,将非零元素放入M矩阵
	list* p = head->next;
	while (p) {
		M[p->row][p->col] = p->val;
		p = p->next;
	}
	for (int i = 0; i < head->row; i++) {
		for (int j = 0; j < head->col; j++)
			printf("%2d ", M[i][j]);
		printf("\n");
	}
	return head;
}

十字链表代码:

//十字链表节点定义,节点有一个指向同行中下一个非零元素的指针和一个
//指向同列中下一个节点的指针,以及一个存储非零元素值的域
typedef struct crossnode {
	int val;
	int row;
	int col;
	crossnode* right;
	crossnode* down;
}crossnode;

//十字链表实现
crossnode*** CrossList(int** arr, int row, int col) {

	//先建立行和列的头,分别用一个数组表示,表头中指向该行或该列的第一个非零元素
	crossnode** Row = (crossnode**)calloc(row, sizeof(crossnode*));
	crossnode** Col = (crossnode**)calloc(col, sizeof(crossnode*));

	//遍历链表,先将每行的非零元素连接起来
	for (int i = 0; i < row; i++) {
		for (int j = col - 1; j >= 0; j--) {//使用逆序可以在头部插入节点,这样链表的顺序依然是正确的,否则就要在尾部插入
			if (arr[i][j]) {//当该元素非零时,为它建立一个节点
				crossnode* tmp = (crossnode*)malloc(sizeof(crossnode));
				tmp->col = j;//保存列
				tmp->row = i;//保存行
				tmp->val = arr[i][j];//保存值
				tmp->right = NULL;
				tmp->down = NULL;
				if (Row[i] == NULL) {//如果第i行的头还没有指向任何节点,说明现在要插入第一个节点
					Row[i] = tmp;
				}
				else {//否则,使头指针指向这个节点
					tmp->right = Row[i];
					Row[i] = tmp;
				}
			}
		}
	}

	//下面根据矩阵将每一列中的非零元素连接起来
	//从最后一行开始遍历建立好的行链表
	for (int i = row - 1; i >= 0; i--) {
		crossnode* tmp = Row[i];
		while (tmp) {//遍历每一行的非零节点,插入方式与上面一样
			int j = tmp->col;
			if (Col[j] == NULL) {
				Col[j] = tmp;
			}
			else {
				tmp->down = Col[j];
				Col[j] = tmp;
			}
			tmp = tmp->right;
		}
	}

	//建立一个数组用来存放行和列的头,并作为函数返回值
	crossnode*** ret = (crossnode***)malloc(sizeof(crossnode**) * 2);
	ret[0] = Row;
	ret[1] = Col;

	//为了方便测试,将原矩阵的还原过程写在函数里面
	//先建立一个空矩阵,并初始化为0
	int** a = (int**)calloc(row, sizeof(int*));
	for (int i = 0; i < row; i++)
		a[i] = (int*)calloc(col, sizeof(int));
	//按行或者列遍历十字链表,并将非零元素放在对应位置上
	for (int i = 0; i < row; i++) {
		crossnode* tmp = Row[i];
		while (tmp) {
			a[tmp->row][tmp->col] = tmp->val;
			tmp = tmp->right;
		}
	}
	//打印矩阵
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++)
			printf("%2d ", a[i][j]);
		printf("\n");
	}

	return ret;
}

测试代码: 

int main() {

	int row, col;
	printf("请输入数组行和列:");
	scanf("%d %d", &row, &col);
	int** arr = (int**)malloc(sizeof(int) * row);
	for (int i = 0; i < row; i++)
		arr[i] = (int*)malloc(sizeof(int) * col);
	printf("请输入矩阵元素,一行中的元素用空格隔开,不同行用换行符隔开:\n");
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++)
			scanf("%d", &arr[i][j]);
	}
	
	SanYuanZu(arr, row, col);
	List(arr, row, col);
	CrossList(arr, row, col);

	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值