【数据结构】特殊矩阵的压缩存储

news2025/1/11 11:05:11

🎇【数据结构】特殊矩阵的压缩存储🎇


🌈 自在飞花轻似梦,无边丝雨细如愁 🌈

在这里插入图片描述


🌟 正式开始学习数据结构啦~此专栏作为学习过程中的记录🌟


文章目录

  • 🎇【数据结构】特殊矩阵的压缩存储🎇
  • 🍰一.数组的存储结构
    • 🚀1.数组的定义
    • 🚀2.数组的存储结构
  • 🍰二.特殊矩阵的存储结构
    • 🚀1.普通矩阵的存储
    • 🚀2.特殊矩阵的压缩存储
      • 🔆1.对称矩阵
      • 🔆2.三角矩阵
      • 🔆3.三对角矩阵
      • 🔆4.稀疏矩阵

🍰一.数组的存储结构

🚀1.数组的定义

数组是由n个相同类型的数据元素所构成的有限序列

数组和线性表的关系:
数组是线性表的推广:一维数组可以看做是一个线性表,而对于二维数组而言,可以看成是有多个线性表组成的线性表

也就是每一行 / / /列视都为一个线性表,总的线性表内每一个元素也是一个定长的线性表

在这里插入图片描述


🚀2.数组的存储结构

  1. 对于一维数组的存储,就是线性表,一维数组的所有元素在内存空间中占用一段连续的内存空间

在这里插入图片描述


那么,对于多维数组的存储,在计算机中是如何实现的呢?

  1. 对于多维数组的存储,在计算机中仍表现为一维数组的形式,也就是一段连续的内存空间
    在这里插入图片描述

接下来,我们就要去尝试模拟计算机存放多维数组的过程:


有两种映射方式:行优先和列优先

①行优先:

以二维数组为例,以行优先存储的方式为:也就是逐行放入一维数组中
在这里插入图片描述

🌈 下标转换关系:

(我们默认下标从0开始)对于二维数组 A [ N ] [ M ] A[N][M] A[N][M] 中的元素 a i j a_{ij} aij ,若希望在行优先转化后的一维数组中访问它,我们可以确定其在一维数组中的下标为:

k = i ∗ M + j k=i*M+j k=iM+j


分解代码实现:

  1. 行优先二维转一维数组
void row_Reducedim(int a[][M],int *res, int row, int col)
{
	int p = 0;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			res[p++] = a[i][j];
		}
	}
}

  1. 按照索引从一维数组中取值
void visit(int res[], int i, int j)
{
	if (i <= N-1 && i >= 0 && j >= 0 && j <= M-1)
	{
		int k = i * M + j;
		cout << res[k] << endl;
	}
	else
		cout << "输入违规" << endl;
}

行优先完整代码实现:

#include<iostream>
using namespace std;

void row_Reducedim(int a[][4],int *res, int row, int col)
{
	int p = 0;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			res[p++] = a[i][j];
		}
	}
}


void Print(int a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << " ";
	}cout << endl;
}


void visit(int res[], int i, int j)
{
	if (i <= 1 && i >= 0 && j >= 0 && j <= 3)
	{
		int k = i * 4 + j;
		cout << res[k] << endl;
	}
	else
		cout << "输入违规" << endl;
}


int main() {
	int martix[2][4] =
	{ {1,2,3,4},
		{5,6,7,8} };
	int res[8];

	//二维转一维
	row_Reducedim(martix,res, 2, 4); //行优先

	cout << "行转化后的一维数组为:" << endl;
	Print(res,8);

	//二维数组元素在一维数组内的映射
	int i, j;
	cout << "对于行转换后的矩阵,输入要访问的元素在二维数组中的行,列下标数(>=0)" << endl;
	cin >> i >> j;
	visit(res, i, j);

	system("pause");
	return 0;
}


②列优先:

以二维数组为例,以行优先存储的方式为:也就是逐列放入一维数组中

🌈 下标转换关系:

对于二维数组 A [ N ] [ M ] A[N][M] A[N][M] 中的元素 a i j a_{ij} aij ,其在一维数组中的下标为:

k = j ∗ N + i k=j*N+i k=jN+i


分解代码实现:

  1. 列优先二维转一维数组
void col_Reducedim(int a[][4], int* res1, int row, int col)
{
	int p = 0;
	for (int j = 0; j < col; j++) {
		for (int i = 0; i < row; i++) {
			res1[p++] = a[i][j];
		}
	}
}

  1. 按照索引从一维数组中取值
void visit1(int res[], int i, int j)
{
	if (i <= 1 && i >= 0 && j >= 0 && j <= 3)
	{
		int k = j * 2 + i;
		cout << res[k] << endl;
	}
	else
		cout << "输入违规" << endl;
}

列优先完整代码实现:

#include<iostream>
using namespace std;


void col_Reducedim(int a[][4], int* res1, int row, int col)
{
	int p = 0;
	for (int j = 0; j < col; j++) {
		for (int i = 0; i < row; i++) {
			res1[p++] = a[i][j];
		}
	}
}

void Print(int a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << " ";
	}cout << endl;
}


void visit1(int res[], int i, int j)
{
	if (i <= 1 && i >= 0 && j >= 0 && j <= 3)
	{
		int k = j * 2 + i;
		cout << res[k] << endl;
	}
	else
		cout << "输入违规" << endl;
}

int main() {
	int martix[2][4] =
	{ {1,2,3,4},
		{5,6,7,8} };
	int res1[8];

	//二维转一维
	col_Reducedim(martix, res1, 2, 4); //列优先

	cout << "列转化后的一维数组为:" << endl;
	Print(res1, 8);

	int a, b;
	cout << "对于列转换后的矩阵,输入要访问的元素在二维数组中的行,列下标数(>=0)" << endl;
	cin >> a >> b;
	visit1(res1, a, b);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述



🍰二.特殊矩阵的存储结构

🚀1.普通矩阵的存储

对于普通的矩阵,我们可以将其视为二维数组进行处理,也就是按行优先和列优先的方式存储,在之前也已经提到过

在这里插入图片描述


🚀2.特殊矩阵的压缩存储

压缩存储:指为多个值相同的元素所分配一个存储空间,对零元素不分配存储空间,用于节省空间

接下来,我们来看几个典型的特殊矩阵:

🔆1.对称矩阵

对于一个n阶的方阵,我们可以将其划分为主对角线,上三角区和下三角区,而对于对称矩阵来说,有 a i j = a j i a_{ij}=a_{ji} aij=aji,因此,若仍然采用二维数组存储,会造成一半的空间浪费

策略:

因此,我们其实只需要 存主对角线和下三角块 即可:
在这里插入图片描述


🌊 对称矩阵与存储后一维数组的映射关系:

我们规定矩阵元素的下标 i , j i,j ij的范围为 [ 1 , n ] [1,n] [1,n],而一维数组的下标默认是从0开始的

在这里插入图片描述

  1. 一维数组大小:
    第一行:一个元素, a 11 a_{11} a11
    第二行:两个元素, a 21 , a 22 a_{21},a_{22} a21,a22

    共有 n n n 行,则元素总数 k = n ∗ ( n + 1 ) / 2 k=n*(n+1)/2 k=n(n+1)/2

在这里插入图片描述


  1. 压缩存储后的访问:

    又回到了,压缩完成之后,我们应该如何获取这些数据呢?

    我们只需要定义一个映射函数即可:

在这里插入图片描述

策略:

不难发现,如果我们希望取出二维数组内的元素 a i j a_{ij} aij,我们已知了它的行号和列号:

①先看行向:
a i j a_{ij} aij上面的元素(即前 i − 1 i-1 i1行)的元素个数为:

i ∗ ( i − 1 ) / 2 i*(i-1)/2 i(i1)/2

②再看列向:
a i j a_{ij} aij前面的元素(即前 j − 1 j-1 j1行)的元素个数为:

j − 1 j-1 j1


由于一维数组下标是从 0 0 0开始的,因此: a i j 的一维数组下标 = 在 a i j 前的元素个数 a_{ij}的一维数组下标=在a_{ij}前的元素个数 aij的一维数组下标=aij前的元素个数

再由 a i j = a j i a_{ij}=a_{ji} aij=aji,因此,映射函数为:

在这里插入图片描述


完整代码实现:

#include<iostream>
using namespace std;

//打印下三角
void triangle(int a[][3], int* res, int row, int col)
{
	int p = 0;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (i >= j)
				res[p++] = a[i][j];
		}
	}
}

//访问
void visit(int res[], int i, int j)
{
	if (i >= j)
	{
		int k = i * (i - 1) / 2 + j - 1;
		cout << res[k] << endl;
	}
	else
	{
		int k = j * (j - 1) / 2 + i - 1;
		cout << res[k] << endl;
	}
}

//打印一维数组
void Print(int a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << " ";
	}cout << endl;
}

int main() {
	int martix[3][3] =
	{	{1,2,3},
		{2,1,2},
		{3,2,1} };
	int res[6];

	triangle(martix, res, 3, 3);
	cout << "下三角的一维数组为:" << endl;
	Print(res,6);

	int i, j;
	cout << "请输入需要访问的元素aij中的下标i,j(>=1):" << endl;
	cin >> i >> j;
	visit(res, i, j);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述


🔆2.三角矩阵

三角矩阵就是有一个三角区全为常量的矩阵

在这里插入图片描述


策略:

其储存思维和对称矩阵类似,不同之处就在于:

存储完下三角区和主对角线后,紧接着存储对角线上方的常量,也就是要在对称矩阵构造的一维数组后面添加一个常数项

在这里插入图片描述


  1. 一维数组的大小:

    k = n ∗ ( n + 1 ) / 2 k=n*(n+1)/2 k=n(n+1)/2

  2. 映射函数为:

在这里插入图片描述


完整代码实现:

#include<iostream>
using namespace std;

//打印下三角
void triangle(int a[][3], int* res, int row, int col,int n)
{
	int p = 0;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (i >= j)
				res[p++] = a[i][j];
		}
	}
	//存常量
	int c = a[0][n-1];
	res[n * (n + 1) / 2] = c;
}

//访问
void visit(int res[], int i, int j,int n)
{
	if (i >= j)
	{
		int k = i * (i - 1) / 2 + j - 1;
		cout << res[k] << endl;
	}
	else
	{
		int k = n * (n + 1)/2;
		cout << res[k] << endl;
	}
}

//打印一维数组
void Print(int a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << " ";
	}cout << endl;
}

int main() {
	int martix[3][3] =
	{ {1,4,4},
		{2,1,4},
		{3,2,1} };
	int res[6];

	triangle(martix, res, 3, 3,3);
	cout << "下三角的一维数组为:" << endl;
	//加一个常数项
	Print(res, 6+1);

	int i, j;
	cout << "请输入需要访问的元素aij中的下标i,j(>=1):" << endl;
	cin >> i >> j;
	visit(res, i, j,3);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述


🔆3.三对角矩阵

三对角矩阵也称带状矩阵,对于n阶方阵的A中的任一元素 a i j a_{ij} aij,当 ∣ i − j ∣ > 1 |i-j|>1 ij>1时, a i j = 0 a_{ij}=0 aij=0

在这里插入图片描述


策略:

将三条对角线上的元素按行优先原则存放在一维数组中(零元素不存)

在这里插入图片描述


  1. 一维数组的大小:
    由于只有第一行和最后一行只有两个元素,因此,一维数组大小为:

    l e n = 3 n − 2 len=3n-2 len=3n2

  2. 映射函数为:
    由于前 i − 1 i-1 i1行有 3 i − 1 3i-1 3i1个元素,当前行前面有 j − i + 1 j-i+1 ji+1个元素

    k = 2 i + j − 3 k=2i+j-3 k=2i+j3


反之,若我们已知 a i j a_{ij} aij存放于一维数组中的第 k个位置,怎么推出行数和列数呢?

在这里插入图片描述


i = ⌈ ( k + 2 ) / 3 ⌉ i=⌈(k+2)/3⌉ i=⌈(k+2)/3 再由公式 k = 2 i + j − 3 k=2i+j-3 k=2i+j3可以推出: j = k − 2 i + 3 j=k-2i+3 j=k2i+3


完整代码实现:

#include<iostream>
#include<math.h>
using namespace std;


//转一维矩阵
void Three(int a[][4], int* res, int row, int col)
{
	int p = 0;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (a[i][j] != 0)
				res[p++] = a[i][j];
		}
	}
}

void Print(int a[],int n) {
	for (int i = 0; i < n; i++) {
		cout << a[i] << " ";
	}cout << endl;
}

void visit1(int a[], int i, int j)
{
	int k = 2*i + j - 3;
	printf("a%d%d=%d\n", i,j,a[k]);
}

void visit2(int a[], int k)
{
	int i, j;
	i = ceil((k + 2) / 3);
	j = k - 2 * i + 3;
	printf("访问的元素为:a%d%d\n", i, j);
}


int main() {
	int martix[4][4] =
	{
		{1,2,0,0},
		{1,2,3,0},
		{0,2,3,4},
		{0,0,3,4}
	};
	int res[10];

	//转一维
	Three(martix, res, 4, 4);
	cout << "三对角的一维矩阵为:" << endl;
	Print(res, 10);

	//正向访问
	int i, j;
	cout << "请输入需要访问的元素aij中的下标i,j(>=1):" << endl;
	cin >> i >> j;
	visit1(res, i, j);

	//反向访问
	int k;
	cout<<"请输入该元素在一维数组中的下标:" << endl;
	cin >> k;
	visit2(res, k);


	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述



🔆4.稀疏矩阵

还有一种特殊矩阵,其矩阵内的非0元素远远少于零元素,则称其为稀疏矩阵

e . g e.g e.g 如下矩阵:
在这里插入图片描述

我们只存取非零元素,但其分布往往没有规律,因此,我们还应该记录它的位置

策略:

将非零元素的行,列,值构成一个三元组 (行标,列标,值) (行标,列标,值) (行标,列标,值)

我们用结构体定义这个三元组:

#define Maxsize 100

//结构体定义三元组
typedef struct {
	int i;
	int j;
	int val;
}Triple[Maxsize]; //结构体数组

在这里插入图片描述


完整代码实现:

#include<iostream>
#define Maxsize 100
using namespace std;

//结构体定义三元组
typedef struct {
	int i;
	int j;
	int val;
}Triple[Maxsize]; //结构体数组


//稀疏矩阵转三元组
void triple(Triple &T,int a[][5],int row,int col,int &p)
{
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (a[i][j] != 0) {
				T[p].i = i+1;
				T[p].j = j+1;
				T[p].val = a[i][j];
				p++;
			}
		}
	}
}

void Print(Triple &T,int len) {
	for (int i = 0; i < len; i++) {
		printf("a%d%d=",T[i].i,T[i].j);
		cout << T[i].i<< " " << T[i].j << " " << T[i].val << endl;
	}
}

int main() {
	int martix[5][5] =
	{
		{0,2,0,0,0},
		{0,0,1,0,2},
		{3,0,0,0,1},
		{0,5,0,0,0},
		{1,0,4,0,0}
	};
	Triple T;
	int p = 0;

	//稀疏矩阵转三元组
	triple(T, martix, 5, 5, p);
	cout << "转化后的三元组为(矩阵元素下标从1开始):\n" << endl;
	Print(T, p);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述


🎇本节详细讲解了数组相关操作及各种特殊矩阵的压缩存储方式~🎇

如有错误,欢迎指正~!


在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/680158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言学习(二十六)---指针练习题(二)

在上节的内容中&#xff0c;我们进一步学习了有关指针的内容&#xff0c;并做了一些关于指针的题目&#xff0c;今天我们将继续练习一些指针的题目&#xff0c;以便大家更好的理解和掌握指针的知识&#xff0c;好了&#xff0c;话不多说&#xff0c;开整&#xff01;&#xff0…

【c++11】 左值引用和右值引用

c11特性 右值引用左值引用和右值引用左值引用右值引用比较 右值引用的应用左值引用的短处右值引用解决问题移动构造 STL的改动move()函数结语 右值引用 c从出现就有着引用的语法&#xff0c;但是在c11后又新增了右值引用的新特性&#xff0c;以往所学的引用成了左值引用。非左…

代码随想录算法训练营第四十二天| 背包问题

标准背包问题 有n件物品和一个最多能背重量为w 的背包。 第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 举一个例子&#xff1a; 背包最大重量为4。 物品为&#xff1a; 重量价…

5.2.3目录与文件之权限意义

现在我们知道了Linux系统内文件的三种身份&#xff08;拥有者、群组与其他人&#xff09;&#xff0c;知道每种身份都有三种权限&#xff08;rwx&#xff09;&#xff0c; 已知道能够使用chown, chgrp, chmod去修改这些权限与属性&#xff0c;当然&#xff0c;利用ls -l去观察文…

《C++高级编程》读书笔记(十:揭秘继承技术)

1、参考引用 C高级编程&#xff08;第4版&#xff0c;C17标准&#xff09;马克葛瑞格尔 2、建议先看《21天学通C》 这本书入门&#xff0c;笔记链接如下 21天学通C读书笔记&#xff08;文章链接汇总&#xff09; 1. 使用继承构建类 1.1 扩展类 当使用 C 编写类定义时&#xf…

WMS中Choreographer 配合 VSYNC 中断信号

WMS中Choreographer 配合 VSYNC 中断信号 1、了解SurfaceFlinger中VSYNC信号刷新2、Choreographer 舞蹈编导2.1 Choreographer初始化2.2 FrameHandler中处理任务2.3 FrameDisplayEventReceiver初始化3.4 简易流程图 3、ViewRootImpl中scheduleTraversals3.1 postCallback 通过n…

java——IO与NIO

文章目录 1. 传统IO模型字节流字符流 2. NIO模型 Java中的IO&#xff08;输入输出&#xff09;是用于在程序中读取和写入数据的一种机制。Java提供了两种不同的IO模型&#xff1a;传统的IO模型和NIO&#xff08;New IO&#xff09;模型。 1. 传统IO模型 在传统的IO模型中&…

WPF本地化/国际化,多语言切换

之前写过winformwinform使用本地化&#xff0c;中英文切换_winform 中英文切换_故里2130的博客-CSDN博客 基本的技术差不多&#xff0c;但是后来又发现了一个ResXManager工具&#xff0c;可以更好方便快捷的使用。 首先下载&#xff0c;网络不好的话&#xff0c;去官网下载&a…

01背包简介

01背包问题&#xff08;0/1 Knapsack problem&#xff09;是一个经典的动态规划问题&#xff0c;它描述了在给定容量限制的情况下&#xff0c;如何选择一组物品放入背包&#xff0c;以使得物品的总价值最大化。 问题描述&#xff1a; 假设有一个背包&#xff0c;其容量为C。现…

VulnHub项目:Fawkes

1、靶机地址 HarryPotter: Fawkes ~ VulnHub 该篇为哈利波特死亡圣器系列最终部&#xff0c;也是最难的一个靶机&#xff0c;难度真的是逐步提升&#xff01;&#xff01;&#xff01; 2、渗透过程 确认靶机IP&#xff0c;kali IP&#xff0c;探测靶机开放端口 详细的扫描…

ICLR 23 | 工业视觉小样本异常检测最新网络:Graphcore

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://openreview.net/pdf?idxzmqxHdZAwO 论文代码&#xff1a;尚未开源 1.背景 随着人工智能中深度视觉检测技术的快速发展&#xff0c;检测工业产品表面的异常/缺陷受到了前所未有…

scratch lenet(11): C语言实现 squashing function

文章目录 1. 目的2. Sigmoidal Function2.1 S2 用到 Sigmoidal Function2.2 Sigmoidal Function 的定义 3. Squashing Function3.1 改用 Sigmoid Suahsing function 术语3.2 具体到 hyperlolic tangent 这一 squahsing function 4. Squahsing function 的实现References 1. 目的…

设计模式之观察者模式笔记

设计模式之观察者模式笔记 说明Observer(观察者)目录观察者模式示例类图抽象主题角色类抽象观察者类具体主题角色类具体的观察者角色类测试类 说明 记录下学习设计模式-观察者模式的写法。JDK使用版本为1.8版本。 Observer(观察者) 意图:定义对象间的一种一对多的依赖关系&a…

Gradle构建系统macOS安装与使用

1.打开gradle.org并点击安装 2.先决条件 ,确认安装JDK1.8或者更高版本已安装 在终端输入brew install gradle进行安装 安装成功如下: 查看安装版本号gradle -v 使用gradle 1.创建目录demo并进入该目录 mkdir demo cd demo 2.gradle init 使用Gradle开始构建 输入2开始构建应…

DevOps系列文章之 docker插件实现多实例部署(IDEA插件)

1. Docker的安装以及开启远程访问 1.1 安装 # 检查虚拟机内核版本&#xff0c;必须是3.10及以上 uname -r # 安装docker yum install docker # 输入y确认安装 # 启动docker systemctl start docker # 查看docker版本 docker -v # 开机启动docker systemctl enable docker # 停…

Golang学习日志 ━━ gin-vue-admin换机重新配置的记录,很愚蠢,很傻瓜,很机械...自己使用

最近一直在弄AI&#xff0c;没时间搞gva&#xff0c;所以有点忘记了&#xff0c;代码升级管它呢&#xff0c;全部重来一遍~ 一、备份保存 根据经验和个人喜好&#xff0c;我特别不喜欢在框架下把一个应用分散在module、api、service等等目录下&#xff0c;这种目录分配方案将把…

图上作业法

目录 交通示意图的表示方法 图上作业法 &#xff08;1&#xff09;对流 &#xff08;2&#xff09;迂回 物资调运问题的图上作业法 交通路线不成圈 交通路线成圈 交通示意图的表示方法 交通示意图是用来表明收发点的大致位置、收发量、交通路线长度的图形。 图形表示…

java mail发送、接收邮件

java mail接收邮件 1、引入java mail依赖 <dependency><groupId>org.eclipse.angus</groupId><artifactId>angus-mail</artifactId><version>2.0.2</version> </dependency>2、编写代码 注意&#xff1a;下述代码中的服务器…

从BNO055传感器获取IMU数据-2

在前面的文章 从BNO055传感器获取IMU数据-1 中介绍了BNO055传感器&#xff0c;今天继续讲解应用示例。 传感器与Arduino接口 我从某宝购买了固定在带有支持组件的开发板上的 BNO055 传感器。从 Digi-Key 或贸泽购买 BNO055 并将其焊接到 7.54.4mm 28 引脚 LGA 至 DIP 转换器上…