数组和广义表

数组和广义表

什么是数组存储结构
  • 前面学习数据结构的过程中,总是使用数组作为顺序表的底层实现,给我们一种 “数据结构中,数组的作用就是实现顺序表” 的错误认识。其实,数组的作用远不止于此。现在,我们将从数据结构的角度讲解数组存储结构。
  • 现在所讲的数组,要将其视为一种存储结构,与平时使用的数组基本数据类型区分开。
  • 从本质上讲,数组与顺序表、链表、栈和队列一样,都用来存储具有 “一对一” 逻辑关系数据的线性存储结构。只因各编程语言都默认将数组作为基本数据类型,使初学者对数组有了 “只是基本数据类型,不是存储结构” 的误解。(数组其实也是一种存储结构)
  • 不仅如此,数组和其他线性存储结构不同,顺序表、链表、栈和队列存储的都是不可再分的数据元素(如数字 5、字符 ‘a’ 等),而数组既可以用来存储不可再分的数据元素,也可以用来存储像顺序表、链表这样的数据结构。
  • 比如说,数组可以直接存储多个顺序表。我们知道,顺序表的底层实现还是数组,因此等价于数组中继续存储数组。这与平时使用的二维数组类似
  • 根据数组中存储数据之间逻辑结构的不同,数组可细分为一维数组、二维数组、…、n 维数组:
    在这里插入图片描述
  • 由此,我们可以得出这样一个结论,一维数组结构是线性表的基本表现形式,而 n 维数组可理解为是对线性存储结构的一种扩展。
数组的顺序存储
  • 数组作为一种线性存储结构,对存储的数据通常只做查找和修改操作,一般不进行插入和删除的操作(因为需要进行元素的搬移,很麻烦,要知道,对数组中存储的数据做插入和删除操作,算法的效率是很差的),因此数组结构的实现使用的是顺序存储结构。
  • 由于数组可以是多维的,而顺序存储结构是一维的,因此数组中数据的存储要制定一个先后次序。通常,数组中数据的存储有两种先后存储方式:
    在这里插入图片描述
  • C 语言中,多维数组的存储采用的是以行序为主的顺序存储方式。
多维数组查找指定元素

在这里插入图片描述

  • a[3][4][2] 表示3页4行2列
矩阵压缩存储

数据结构中,提供针对某些特殊矩阵的压缩存储结构。这里所说的特殊矩阵,主要分为以下两类:

  • 含有大量相同数据元素的矩阵,比如对称矩阵;
  • 含有大量 0 元素的矩阵,比如稀疏矩阵、上(下)三角矩阵;
  • 针对以上两类矩阵,数据结构的压缩存储思想是:矩阵中的相同数据元素(包括元素 0)只存储一个。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
得出结论:上(下)三角矩阵存储元素和提取元素的过程和对称矩阵相同。
稀疏矩阵

在这里插入图片描述

矩阵压缩存储的 3 种方式
  • 对于以上 3 种特殊的矩阵,对阵矩阵和上下三角矩阵的实现方法是相同的,且实现过程比较容易,仅需套用上面给出的公式即可。
  • 稀疏矩阵的压缩存储,数据结构提供有 3 种具体实现方式:
  • 1.三元组顺序表
  • 2.行逻辑链接的顺序表
  • 3.十字链表

我们知道,稀疏矩阵的压缩存储,至少需要存储以下信息:
在这里插入图片描述

三元组顺序表
typedef struct 
{
    int i,j;//行标i,列标j
    int data;//元素值
}triple;

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

#define number 20
//矩阵的结构表示
typedef struct 
{
    triple data[number];    //存储该矩阵中所有非0元素的三元组
    int n,m,num;            //n和m分别记录矩阵的行数和列数,num记录矩阵中所有的非0元素的个数
}TSMatrix;

可以看到,TSMatrix 是一个结构体,其包含一个三元组数组,以及用于存储矩阵总行数、总列数和非 0 元素个数的变量。
假设采用 TSMatrix 结构体存储图 1 中的稀疏矩阵,其 C 语言实现代码应该为:

#include<stdio.h>
#define number 3
typedef struct 
{
    int i,j;
    int data;
}triple;
typedef struct 
{
    triple data[number];
    int n,m,num;
}TSMatrix;
//输出存储的稀疏矩阵
void display(TSMatrix M);
int main() 
{
    TSMatrix M;
    M.m=3;
    M.n=3;
    M.num=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)
{
    for(int i=1;i<=M.n;i++)
    {
        for(int j=1;j<=M.m;j++)
        {
            int value =0;
            for(int k=0;k<M.num;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");
    }
}
行逻辑链接的顺序表(压缩存储稀疏矩阵)详解
  • 前面学习了如何使用三元组顺序表存储稀疏矩阵,其实现过程就是将矩阵中各个非 0 元素的行标、列标和元素值以三元组的形式存储到一维数组中。通过研究实现代码你会发现,三元组顺序表每次提取指定元素都需要遍历整个数组,运行效率很低。
  • 本节将学习另一种存储矩阵的方法——行逻辑链接的顺序表。它可以看作是三元组顺序表的升级版,即在三元组顺序表的基础上改善了提取数据的效率。
  • 行逻辑链接的顺序表和三元组顺序表的实现过程类似,它们存储矩阵的过程完全相同,都是将矩阵中非 0 元素的三元组(行标、列标和元素值)存储在一维数组中。但为了提高提取数据的效率,前者在存储矩阵时比后者多使用了一个数组,专门记录矩阵中每行第一个非 0 元素在一维数组中的位置
    在这里插入图片描述
    在这里插入图片描述
  • 代码实现
#include <stdio.h>
#define MAXSIZE 12500
#define MAXRC 100
#define ElemType int
typedef struct
{
    int i,j;//行,列
    ElemType e;//元素值
}Triple;

typedef struct
{
    Triple  data[MAXSIZE+1];
    int rpos[MAXRC+1];//每行第一个非零元素在data数组中的位置
    int mu,nu,tu;//行数,列数,元素个数
}RLSMatrix;
//矩阵的输出函数
void display(RLSMatrix M){
    for(int i=1;i<=M.mu;i++){
        for(int j=1;j<=M.nu;j++){
            int value=0;
            if(i+1 <=M.mu){
                for(int k=M.rpos[i];k<M.rpos[i+1];k++){
                    if(i == M.data[k].i && j == M.data[k].j){
                        printf("%d ",M.data[k].e);
                        value=1;
                        break;
                    }
                }
                if(value==0){
                        printf("0 ");
                    }
            }else{
                for(int k=M.rpos[i];k<=M.tu;k++){
                    if(i == M.data[k].i && j == M.data[k].j){
                        printf("%d ",M.data[k].e);
                        value=1;
                        break;
                    }

                }
                if(value==0){
                    printf("0 ");
                }
            }

        }
        printf("\n");
    }
}
int main(int argc, char* argv[])
{
    RLSMatrix M;

    M.tu = 4;
    M.mu = 3;
    M.nu = 4;

    M.rpos[1] = 1;
    M.rpos[2] = 3;
    M.rpos[3] = 4;

    M.data[1].e = 3;
    M.data[1].i = 1;
    M.data[1].j = 2;

    M.data[2].e = 5;
    M.data[2].i = 1;
    M.data[2].j = 4;

    M.data[3].e = 1;
    M.data[3].i = 2;
    M.data[3].j = 3;

    M.data[4].e = 2;
    M.data[4].i = 3;
    M.data[4].j = 1;
    //输出矩阵
    display(M);
    return 0;
}
十字链表法,十字链表压缩存储稀疏矩阵详解
  • 对于压缩存储稀疏矩阵,无论是使用三元组顺序表,还是使用行逻辑链接的顺序表,归根结底是使用数组存储稀疏矩阵。介于数组 “不利于插入和删除数据” 的特点,以上两种压缩存储方式都不适合解决类似 “向矩阵中添加或删除非 0 元素” 的问题。
  • 例如,A 和 B 分别为两个矩阵,在实现 “将矩阵 B 加到矩阵 A 上” 的操作时,矩阵 A 中的元素会发生很大的变化,之前的非 0 元素可能变为 0,而 0 元素也可能变为非 0 元素。对于此操作的实现,之前所学的压缩存储方法就显得力不从心。
  • 用十字链表存储稀疏矩阵,该存储方式采用的是 “链表+数组” 结构
    在这里插入图片描述
  • 代码表示
#include<stdio.h>
#include<stdlib.h>
typedef struct OLNode
{
    int i, j, e; //矩阵三元组i代表行 j代表列 e代表当前位置的数据
    struct OLNode *right, *down; //指针域 右指针 下指针
}OLNode, *OLink;
typedef struct
{
    OLink *rhead, *chead; //行和列链表头指针
    int mu, nu, tu;  //矩阵的行数,列数和非零元的个数
}CrossList;
CrossList CreateMatrix_OL(CrossList M);
void display(CrossList M);
int main()
{
    CrossList M;
    M.rhead = NULL;
    M.chead = NULL;
    M = CreateMatrix_OL(M);
    printf("输出矩阵M:\n");
    display(M);
    return 0;
}
CrossList CreateMatrix_OL(CrossList M)
{
    int m, n, t;
    int i, j, e;
    OLNode *p, *q;
    printf("输入矩阵的行数、列数和非0元素个数:");
    scanf("%d%d%d", &m, &n, &t);
    M.mu = m;
    M.nu = n;
    M.tu = t;
    if (!(M.rhead = (OLink*)malloc((m + 1) * sizeof(OLink))) || !(M.chead = (OLink*)malloc((n + 1) * sizeof(OLink))))
    {
        printf("初始化矩阵失败");
        exit(0);
    }
    for (i = 1; i <= m; i++)
    {
        M.rhead[i] = NULL;
    }
    for (j = 1; j <= n; j++)
    {
        M.chead[j] = NULL;
    }
    for (scanf("%d%d%d", &i, &j, &e); 0 != i; scanf("%d%d%d", &i, &j, &e)) {
        if (!(p = (OLNode*)malloc(sizeof(OLNode))))
        {
            printf("初始化三元组失败");
            exit(0);
        }
        p->i = i;
        p->j = j;
        p->e = e;
        //链接到行的指定位置
        if (NULL == M.rhead[i] || 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 (NULL == M.chead[j] || 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;
        }
    }
    return M;
}
void display(CrossList M) {
    for (int i = 1; i <= M.nu; i++)
    {
        if (NULL != M.chead[i])
        {
            OLink p = M.chead[i];
            while (NULL != p)
            {
                printf("%d\t%d\t%d\n", p->i, p->j, p->e);
                p = p->down;
            }
        }
    }
}
矩阵(稀疏矩阵)的转置算法详解
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值