【C语言 数据结构】数组与对称矩阵的压缩存储

news2024/11/20 2:46:36

文章目录

  • 数组的定义
  • 数组的顺序表示和实现
    • 顺序表中查找和修改数组元素
  • 矩阵的压缩存储
    • 特殊矩阵
    • 稀疏矩阵

数组的定义

提到数组,大家首先会想到的是:很多编程语言中都提供有数组这种数据类型,比如 C/C++、Java、Go、C# 等。但本节我要讲解的不是作为数据类型的数组,而是数据结构中提供的一种叫数组的存储结构。

和线性存储结构相比,数组最大的不同是:它存储的数据可以包含多种“一对一”的逻辑关系。举个简单的例子:

在这里插入图片描述
上图中,{a1, a2, a3, a4}、{b1, b2, b3, b4}、{c1, c2, c3, c4}、{d1, d2, d3, d4} 中各自包含的元素具有“一对一”的逻辑关系,同时 a、b、c、d 这 4 个序列也具有“一对一”的逻辑关系。

这样存储不止一种“一对一”逻辑关系的数据,数据结构就推荐使用数组存储结构。


对于数组存储结构,我们可以这样理解它:数组是对线性表的扩展,是一种“特殊”的线性存储结构,用来存储具有多种“一对一”逻辑关系的数据。

实际场景中,存储具有 N 种“一对一”逻辑关系的数据,通常会建立 N 维数组:

  • 一维数组和其它线性存储结构很类似,用来存储只有一种“一对一”逻辑关系的数据:

在这里插入图片描述

  • 二维数组用来存储包含两种“一对一”逻辑关系的数据。二维数组可以看作是存储一维数组的一维数组
    在这里插入图片描述
  • n 维数组用来存储包含 n 种“一对一”逻辑关系的数据,可以看作是存储 n-1 维数组的一维数组;

数组存储结构还具有一些其它的特性,包括:

  • 无论数组的维度是多少,数组中的数据类型都必须一致;
  • 数组一旦建立,它的维度将不再改变;
  • 数组存储结构不会对内部的元素做插入和删除操作,常见的操作有 4 种,分别是初始化数组、销毁数组、取数组中的元素和修改数组中的元素。

数组的顺序表示和实现

数组可以是多维的,而顺序表只能是一维的线性空间。要想将 N 维的数组存储到顺序表中,可以采用以下两种方案:

  • 以列序为主(先列后行):按照行号从小到大的顺序,依次存储每一列的元素;
  • 以行序为主(先行后序):按照列号从小到大的顺序,依次存储每一行的元素。

多维数组中,最常用的是二维数组,接下里就以二维数组为例,讲解数组的顺序存储结构。
在这里插入图片描述
所示的二维数组按照“列序为主”的方案存储时,数组中的元素在顺序表中的存储状态如下图所示:
在这里插入图片描述
同样的道理,按照“行序为主”的方案存储数组时,各个元素在顺序表中的存储状态如图
在这里插入图片描述


顺序表中查找和修改数组元素

注意,只有在顺序表内查找到数组中的目标元素之后,才能对该元素执行读取和修改操作。

在 N 维数组中查找目标元素,需知道以下信息:

  • 数组的存储方式;
  • 数组在内存中存放的起始地址;
  • 目标元素在数组中的坐标。比如说,二维数组中是通过行标和列标来确定元素位置的;
  • 数组中元素的类型,即数组中单个数据元素所占内存的大小,通常用字母 L 表示;

根据存储方式的不同,查找目标元素的方式也不同。仍以二维数组为例,如果数组采用“行序为主”的存储方式,则在二维数组 anm 中查找 aij 位置的公式为:

LOC(i, j) = LOC(0, 0) + (i * m + j) * L;

其中,LOC(i, j) 为 aij 在内存中的地址,LOC(0, 0) 为二维数组在内存中存放的起始位置(也就是 a00 的位置)。

而如果采用以列存储的方式,在 anm 中查找 aij 的方式为:

LOC(i, j) = LOC(0, 0) + (j * n + i) * L;

根据以上两个公式,就可以在顺序表中找到目标元素,自然也就可以进行读取和修改操作了。


代码实现

#include<stdarg.h>
#include<malloc.h>
#include<stdio.h>
#include<stdlib.h> // atoi()
#include<io.h> // eof()
#include<math.h>

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW 3
#define UNDERFLOW 4
typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Boolean; //Boolean是布尔类型,其值是TRUE或FALSE
typedef int ElemType;

#define MAX_ARRAY_DIM 8 //假设数组维数的最大值为8
typedef struct
{
    ElemType* base; //数组元素基址,由InitArray分配
    int dim; //数组维数
    int* bounds; //数组维界基址,由InitArray分配
    int* constants; // 数组映象函数常量基址,由InitArray分配
} Array;

Status InitArray(Array* A, int dim, ...)
{
    //若维数dim和各维长度合法,则构造相应的数组A,并返回OK
    int elemtotal = 1, i; // elemtotal是元素总值
    va_list ap;
    if (dim<1 || dim>MAX_ARRAY_DIM)
        return ERROR;
    (*A).dim = dim;
    (*A).bounds = (int*)malloc(dim * sizeof(int));
    if (!(*A).bounds)
        exit(OVERFLOW);
    va_start(ap, dim);
    for (i = 0; i < dim; ++i)
    {
        (*A).bounds[i] = va_arg(ap, int);
        if ((*A).bounds[i] < 0)
            return UNDERFLOW;
        elemtotal *= (*A).bounds[i];
    }
    va_end(ap);
    (*A).base = (ElemType*)malloc(elemtotal * sizeof(ElemType));
    if (!(*A).base)
        exit(OVERFLOW);
    (*A).constants = (int*)malloc(dim * sizeof(int));
    if (!(*A).constants)
        exit(OVERFLOW);
    (*A).constants[dim - 1] = 1;
    for (i = dim - 2; i >= 0; --i)
        (*A).constants[i] = (*A).bounds[i + 1] * (*A).constants[i + 1];
    return OK;
}

Status DestroyArray(Array* A)
{
    //销毁数组A
    if ((*A).base)
    {
        free((*A).base);
        (*A).base = NULL;
    }
    else
        return ERROR;
    if ((*A).bounds)
    {
        free((*A).bounds);
        (*A).bounds = NULL;
    }
    else
        return ERROR;
    if ((*A).constants)
    {
        free((*A).constants);
        (*A).constants = NULL;
    }
    else
        return ERROR;
    return OK;
}

Status Locate(Array A, va_list ap, int* off) // Value()、Assign()调用此函数 */
{
    //若ap指示的各下标值合法,则求出该元素在A中的相对地址off
    int i, ind;
    *off = 0;
    for (i = 0; i < A.dim; i++)
    {
        ind = va_arg(ap, int);
        if (ind < 0 || ind >= A.bounds[i])
            return OVERFLOW;
        *off += A.constants[i] * ind;
    }
    return OK;
}

Status Value(ElemType* e, Array A, ...) //在VC++中,...之前的形参不能是引用类型
{
    //依次为各维的下标值,若各下标合法,则e被赋值为A的相应的元素值
    va_list ap;
    Status result;
    int off;
    va_start(ap, A);
    if ((result = Locate(A, ap, &off)) == OVERFLOW) //调用Locate()
        return result;
    *e = *(A.base + off);
    return OK;
}

Status Assign(Array* A, ElemType e, ...)
{
    //依次为各维的下标值,若各下标合法,则将e的值赋给A的指定的元素
    va_list ap;
    Status result;
    int off;
    va_start(ap, e);
    if ((result = Locate(*A, ap, &off)) == OVERFLOW) //调用Locate()
        return result;
    *((*A).base + off) = e;
    return OK;
}

int main()
{
    Array A;
    int i, j, k, * p, dim = 3, bound1 = 3, bound2 = 4, bound3 = 2; //a[3][4][2]数组
    ElemType e, * p1;
    InitArray(&A, dim, bound1, bound2, bound3); //构造3*4*2的3维数组A
    p = A.bounds;
    printf("A.bounds=");
    for (i = 0; i < dim; i++) //顺序输出A.bounds
        printf("%d ", *(p + i));
    p = A.constants;
    printf("\nA.constants=");
    for (i = 0; i < dim; i++) //顺序输出A.constants
        printf("%d ", *(p + i));
    printf("\n%d页%d行%d列矩阵元素如下:\n", bound1, bound2, bound3);
    for (i = 0; i < bound1; i++)
    {
        for (j = 0; j < bound2; j++)
        {
            for (k = 0; k < bound3; k++)
            {
                Assign(&A, i * 100 + j * 10 + k, i, j, k); // 将i*100+j*10+k赋值给A[i][j][k]
                Value(&e, A, i, j, k); //将A[i][j][k]的值赋给e
                printf("A[%d][%d][%d]=%2d ", i, j, k, e); //输出A[i][j][k]
            }
            printf("\n");
        }
        printf("\n");
    }
    p1 = A.base;
    printf("A.base=\n");
    for (i = 0; i < bound1 * bound2 * bound3; i++) //顺序输出A.base
    {
        printf("%4d", *(p1 + i));
        if (i % (bound2 * bound3) == bound2 * bound3 - 1)
            printf("\n");
    }
    DestroyArray(&A);
    return 0;
}

矩阵的压缩存储

特殊矩阵

这里所说的特殊矩阵,主要分为以下两类:

  • 含有大量相同数据元素的矩阵,比如对称矩阵;
  • 含有大量 0 元素的矩阵,比如稀疏矩阵、上(下)三角矩阵;

针对以上两类矩阵,数据结构的压缩存储思想是:矩阵中的相同数据元素(包括元素 0)只存储一个
在这里插入图片描述
数据元素沿主对角线对应相等,这类矩阵称为对称矩阵,矩阵中有两条对角线,对角线称为主对角线,另一条从左下角到右上角的对角线为副对角线。对称矩阵指的是各数据元素沿主对角线对称的矩阵。

对称矩阵的实现过程是,若存储下三角中的元素,只需将各元素所在的行标 i 和列标 j 代入下面的公式:
在这里插入图片描述
存储上三角的元素要将各元素的行标 i 和列标 j 代入另一个公式:

在这里插入图片描述
最终求得的 k 值即为该元素存储到数组中的位置(矩阵中元素的行标和列标都从 1 开始)。

例如,在数组 skr[6] 中存储图 1 中的对称矩阵,则矩阵的压缩存储状态如图所示(存储上三角和下三角的结果相同):
在这里插入图片描述
注意,以上两个公式既是用来存储矩阵中元素的,也用来从数组中提取矩阵相应位置的元素。例如,如果想从图中的数组提取矩阵中位于 (3,1) 处的元素,由于该元素位于下三角,需用下三角公式获取元素在数组中的位置,即:
在这里插入图片描述


稀疏矩阵

在这里插入图片描述
如果矩阵中分布有大量的元素 0,即非 0 元素非常少,这类矩阵称为稀疏矩阵。

压缩存储稀疏矩阵的方法是:只存储矩阵中的非 0 元素,与前面的存储方法不同,稀疏矩阵非 0 元素的存储需同时存储该元素所在矩阵中的行标和列标。

例如,存储上图中的稀疏矩阵,需存储以下信息:

  • (1,1,1):数据元素为 1,在矩阵中的位置为 (1,1);
  • (3,3,1):数据元素为 3,在矩阵中的位置为 (3,1);
  • (5,2,3):数据元素为 5,在矩阵中的位置为 (2,3);
  • 除此之外,还要存储矩阵的行数 3 和列数 3;

在这里插入图片描述
**若对其进行压缩存储,矩阵中各非 0 元素的存储状态如图 **
在这里插入图片描述
三元组的结构体

//三元组结构体
typedef struct {
    int i,j;//行标i,列标j
    int data;//元素值
}triple;

由于稀疏矩阵中非 0 元素有多个,因此需要建立 triple 数组存储各个元素的三元组。除此之外,考虑到还要存储矩阵的总行数和总列数,因此可以采用以下结构表示整个稀疏矩阵:

#define number 20
//矩阵的结构表示
typedef struct {
    triple data[number];//存储该矩阵中所有非0元素的三元组
    int mu, nu, tu;//mu和nu分别记录矩阵的行数和列数,tu记录矩阵中所有的非0元素的个数
}TSMatrix;
#include<stdio.h>
#define NUM 3
//存储三元组的结构体
typedef struct {
    int i, j;
    int data;
}triple;
//存储稀疏矩阵的结构体
typedef struct {
    triple data[NUM];
    int mu, nu, tu;
}TSMatrix;
//输出存储的稀疏矩阵
void display(TSMatrix M);
int main() {
    TSMatrix M;
    M.mu = 3;
    M.nu = 3;
    M.tu = 3;
    M.data[0].i = 1;
    M.data[0].j = 1;
    M.data[0].data = 1;
    M.data[1].i = 2;
    M.data[1].j = 3;
    M.data[1].data = 5;
    M.data[2].i = 3;
    M.data[2].j = 1;
    M.data[2].data = 3;
    display(M);
    return 0;
}
void display(TSMatrix M) {
    int i, j, k;
    for (i = 1; i <= M.mu; i++) {
        for (j = 1; j <= M.nu; j++) {
            int value = 0;
            for (k = 0; k < M.tu; k++) {
                if (i == M.data[k].i && j == M.data[k].j) {
                    printf("%d ", M.data[k].data);
                    value = 1;
                    break;
                }
            }
            if (value == 0)
                printf("0 ");
        }
        printf("\n");
    }
}

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

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

相关文章

frp构建多级网络代理

简介frp 是一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c;采用 Golang 编写&#xff0c;支持跨平台&#xff0c;仅需下载对应平台的二进制文件即可执行&#xff0c;没有额外依赖。frp可以将内网服务以安全、便捷的方…

Idea 中【Maven】的环境配置

目录 一 maven 项目管理工具软件二.首先要安装Jdk1.7/8 和IDEA三.在IDEA中配置maven四.在MavenDemo01下 创建多个模块项目四.Jar包依赖 插件五.运用一 maven 项目管理工具软件 1 . Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具…

MQ如何保证消息不丢失

如何保证消息不丢失 哪些环节会造成消息丢失 其实主要就是跨网络的环境中需要考虑消息的丢失&#xff0c;主要是有以下几个方面 生产者往MQ发送消息MQ的Broker是集群有主从的&#xff0c;主节点把消息同步到从节点时也需要考虑消息丢失问题消息从内存持久化到硬盘时&#xf…

软考高级系统架构师背诵要点---软件架构设计

软件架构设计 软件架构的概念&#xff1a; 软件架构为软件系统提供了一个结构、行为和属性的高级抽象&#xff0c;由构成系统的元素的描述、这些元素的相互作用、指导元素集成的模式及这些模式的约束组成 软件架构41视图&#xff1a; 逻辑视图&#xff1a;主要是整个系统的抽…

Java基础:面向对象

一、设计对象并使用 二、封装 对象代表什么,就得封装对应的数据,并提供数据对应的行为。 1.private关键字&#xff1a;priviate修饰的成员变量只能在本类中访问。 2.this关键字&#xff1a;能够直接对应成员变量&#xff08;当局部变量名相同时&#xff09;。 3. 构造方法…

【Linux】十分钟快速了解Linux常用指令(建议收藏)

目录&#x1f496;一. 关机指令01. shutdown02. halt03. reboot&#x1f496;二. 常用指令04. ls05. pwd06. cd07. touch08. mkdir09. rm10. man11. cp(复制)12. mv指令13. nano14. cat15. less16. head17. tail18. find19. grep20. zip/unzip21. tar&#x1f496;三、 日期指令…

JS 中 reduce()方法及使用详解

reduce()方法可以搞定的东西特别多&#xff0c;就是循环遍历能做的&#xff0c;reduce都可以做&#xff0c;比如数组求和、数组求积、统计数组中元素出现的次数、数组去重等等。 reduce() 方法对数组中的每个元素执行一个由您提供的reduce函数(依次执行)&#xff0c;将其结果汇…

Python字符串分割方法【心得总结】

Python中字符串分割的常用方法 是直接调用字符串的str.split方法&#xff0c; 但是其只能指定一种分隔符&#xff0c; 如果想指定多个分隔符拆分字符串需要用到re.split方法 &#xff08;正则表达式的split方法&#xff09; 源码资料电子书:点击此处跳转文末名片获取 str.spli…

OAuth2简单介绍

目录 一、什么是OAuth2 二、OAuth2中的角色 1、资源所有者 2、资源服务器 3、客户 4、授权服务器 三、认证流程 四、生活中的OAuth2思维 五、令牌的特点 六、OAuth2授权方式 1、授权码 2、隐藏式 3、密码式 4、凭证式 一、什么是OAuth2 OAuth2.0是目前使用非常广…

7个高频出现的面试题

收集了2022年所有学生的面试题后&#xff0c;我整理出了7个高频出现的面试题&#xff0c;一起来看看。 高频问题1&#xff1a;请自我介绍下&#xff1f; 高频问题2&#xff1a;请介绍下最近做过的项目&#xff1f; 高频问题3&#xff1a;请介绍下你印象深刻的bug&#xff1f; 高…

Spring Batch 步骤对象-Chunk Tasklet

引言 接着上篇&#xff1a;Spring Batch 步骤对象-步骤Step与Tasklet 了解step步骤概念及其使用之后&#xff0c;本篇再来讲解spring batch使用更广&#xff0c;功能更强大的tasklet&#xff1a;居于块的批处理步骤&#xff1a;Chunk Tasklet 简介 居于chunk(块)的Tasklet相…

江西/杭州/黑龙江/深圳DAMA-CDGA/CDGP数据治理认证招生简章

2023年2月江西/杭州/黑龙江/深圳DAMA-CDGA/CDGP数据治理认证招生简章 DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践…

【笔记】质量保障体系

一、什么叫质量保障体系&#xff1f;质量保障体系,是指指贯穿研发流程进行的一系列质量活动。通过方案选型、策略决策、工具支撑、组织协同分工等&#xff0c;把质量活动进行系统化、标准化、流程化。其目的是保障业务质量。质量保障体系质量活动工具平台质量流程。二、紧贴业务…

【My Electronic Notes系列——正弦波振荡电路】

目录 序言&#xff1a; &#x1f3c6;&#x1f3c6;人生在世&#xff0c;成功并非易事&#xff0c;他需要破茧而出的决心&#xff0c;他需要永不放弃的信念&#xff0c;他需要水滴石穿的坚持&#xff0c;他需要自强不息的勇气&#xff0c;他需要无畏无惧的凛然。要想成功&#…

每天10个前端小知识 【Day 1】

前端面试基础知识题 1. 什么是尾调用优化和尾递归&#xff1f; 尾调用的概念非常简单&#xff0c;一句话就能说清楚&#xff0c;就是指某个函数的最后一步是调用另一个函数。 function f(x){ return g(x); }上面代码中&#xff0c;函数f的最后一步是调用函数g&#xff0c;这…

Axios网络请求

哈喽~大家好&#xff0c;这篇来看看Axios网络请求。 ​文章推荐链接SpringCloud Sentinel 使用SpringCloud Sentinel 使用将Nacos注册到springboot使用以及Feign实现服务调用将Nacos注册到springboot使用以及Feign实现服务调用微服务介绍与 SpringCloud Eureka微服务介绍与 Sp…

Spark07: 宽窄依赖、Stage的划分

一、宽依赖和窄依赖 1. 窄依赖 窄依赖(Narrow Dependency)&#xff1a;指父RDD的每个分区只被子RDD的一个分区所使用&#xff0c;例如map、filter等这些算子。 一个RDD&#xff0c;对它的父RDD只有简单的一对一的关系&#xff0c;也就是说&#xff0c;RDD的每个partition仅仅…

Python分支循环规范:if elif for while

分支与循环 条件是分支与循环中最为核心的点&#xff0c; 解决的问题场景是不同的问题有不同的处理逻辑。 当满足单个或者多个条件或者不满足条件进入分支和循环&#xff0c; 这里也就说明这个对相同问题处理执行逻辑依据具体参数动态变化&#xff0c; 由此产生多种可能性&…

GAMES101笔记:辐射度量学(下)

Irradiance 定义&#xff1a;irradiance是单位面积上的power&#xff0c;这个单位面积是和入射光线垂直的方向上的单位面积。如果受光表面不垂直于光线&#xff0c;需要投影到垂直方向上进行计算&#xff08;cosθ\thetaθ&#xff09;。 Irradiance Falloff 光的Intensity…

零入门容器云网络实战-8->veth pair设备介绍

在介绍veth pair之间&#xff0c;先看一下下面的图&#xff0c; 这类东西有没有见过&#xff1f; 如果没有见过&#xff0c;赶紧看看你方圆10米之内有没有&#xff1f; 这就是网线&#xff0c;最明显的特征是有两端!即&#xff0c;两个水晶头 一端可以链接普通的电脑&#…