【数据结构学习记录12】——多维数组的顺序实现

一.前言

对于数组,我们大家肯定不陌生。单维数组肯定很熟悉,那多维数组呢?实际上就是多个单维数组所构成的特殊的数组的数组。这一内容,在C++的文章里也提到过。那么,我们如何通过用线性结构来模拟一个多维数组呢?这个内容就是本节所着重讨论的地方。

二.数组的结构再探

1.降维

假设我们有一个多维数组,它的大小是2行3列,那么我们数组可以表示为:A = { {1,2},{3,4},{5,6}}。如果看图的话,可以把它“按行降维”和“按列降维”,把它变成一个一维数组。按行降维可以得到a={1,2,3,4,5,6},按列降维可以得到a={1,3,5,2,4,6}。
在这里插入图片描述

2.下标计算

在严佬的这本《数据结构》中,通过一串很长的公式来推导一维数组计算下标的方法,其实也很简单,只是数学公式看着烦。现在可以来推导一下,假设我们的数组的维度是3,每维的长度分别是2,2,3。那么我们那把这12个元素的下标用(1,1,1)(1,1,2)(1,1,3)(1,2,1)(1,2,2)(1,2,3)(2,1,1)(2,1,2)(2,1,3)(2,2,1)(2,2,2)(2,2,3)表示。那么我们第三维的角标+1的时候(其他两个维的角标没变,第三维角标+1),序号增加了1;第二维的元素角标+1的时候,序号增加了3;第一维的元素角标+1的时候,序号增加了6 。
举例(1,1,1)=1、(1,1,2) =2、(1,2,1)=4、(2,1,1)=7。
假设我们这个下标用(a,b,c)表示,那么它的序号的计算公式因为等于(a-1)*6+(b-1)*3+(c-1) + 1 。当然,如果序号从0开始,就可以不用末尾的+1了。
假设我们用.s表示数组维度的长度,序号从0开始,那么我们这个公式就可以表示为:(a-1)*(b.s*c.s)+(b-1)*(c.s)+(c-1)*(1)

3.名词解释

有了刚才那些描述,我们就可以很好的解释一下书上的这些名词了。

  1. 元素基址:就是我们(1,1,1)那个元素的地址。
  2. 数组维度:也就是我们这个括号里,有几个数,就有几个维度。
  3. 数组维度基址:假设我们三个维度的长度是2,2,3,那么我们可以用顺序表的方式,把每个数组维度的长度的存起来。当然存放第一个维度的长度的地址就是数组维度基址了。
  4. 数组映像函数:按照2小节的内容,我们把比它高的维度的长度的积,叫做数组映像函数。
  5. 数组映像函数常数:把该维度的数组映像函数求值的结果,就是该常数。比如(a,b,c)中,a所在的维度(第一维)的常数是,bc所在维度(二三维度)的长度的积。

有了数组映像函数常数(记为X.c),我们求某个高维数组[x,y,z]元素的一维数组序号就可以变为(x-1)×(x.c)+(y-1)×(y.c)+(z-1)×(z.c)。
好了,这样我们就可以开始对高维数组进行降维打击了。

三.实现原理

1.数组的结构

对于一个数组,我们肯定要包含以下几个部分:

  1. 元素区,用于存储元素。
  2. 数组维度,储存一共有多少维度。
  3. 维度区,存一下每个维度的长度。
  4. 数组映像函数常数区,存一下每个维度的数组映像函数常数。

2.数组的初始化

毕竟要初始化,我们肯定要有总维度,和每个维度的长度。
然后根据数组维度,去对维度区赋值,此时在做个累乘,就可以把总元素长度求出来,可以动态申请元素区的长度。然后通过维度区的内容,循环去求数组函数常数区。这样,就初始化了数组。

3.数组的取地址

通过判断下标是否合法,然后做出计算,返回数组元素的相对地址。

4.数组的取值与修改

通过取地址,然后取值或者修改

四.代码实现

1.stdarg.h

因为我们维度是不定长的,所以我们需要用这个库去求取不定长的参数。

1.1 va_list

本质上是char*指针,实际上是用于声明可变长度参数序列。
eg va_list args;

1.2 va_start()

两个参数,第一个参数是va_list申明的“变量”,第二个参数,函数参数列表...前的一个参数。
eg printfint(int level, int test, ...)
那么这个就是va_start(args, test)

1.3 va_arg()

两个参数,第一个参数是va_list声明的“变量”,第二个参数,是参数类型。
eg int context = va_arg(args, int);

1.4 va_end()

一个参数,清空va_start,传递内容是刚才va_start()的va_list

1.6 样例代码

#include <stdio.h>
#include <stdarg.h>

void printfint(int level, int arg, ...);

void printfint(int level, int arg, ...)
{
    
    
    int temp=-1;
    va_list args;
    va_start(args, arg);

    printf("level(%d):", level);
    temp = va_arg(args, int);
    while(temp != 0)
    {
    
    
        printf(" %d", temp);
        temp = va_arg(args, int);
    }
    printf("\n");
    va_end(args);
}

int main()
{
    
    
    printfint(0,1,2,3,4,5,0);
    printfint(6,5,4,3,2,1,0);
    return 0;
}

2.主代码

因为实现了初始化和取下标,那么对于修改值和查询值就很容易了,所以我为了偷懒,只写出了前两个函数:

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

#define     OK      1
#define     ERROR   0

typedef struct dataType{
    
    
    int context;
}dataType;

typedef struct Array{
    
    
    dataType *base;
    int dim;
    int *bounds;
    int *constants;
}Array;

Array *ArrayInit(int dim, ...);
int ArrayDeInit(Array *array);
int ArrayGetAddr(Array *array, ...);

int main()
{
    
    
    Array *array = ArrayInit(3,5,10,10);        // 这个就是初始化函数
    printf("addr=%d",ArrayGetAddr(array, 1, 2, 3)); // 这个是取出指定高维数组下标的顺序数组下标
    return 0;
}

Array *ArrayInit(int dim, ...)
{
    
    
    int temp, cont=1;
    int i;
    va_list args;       // 初始化变长参数
    Array *array = NULL;
    
    if (dim < 0) exit(1);
    
    array = (Array*)malloc(sizeof(Array));
    if (array == NULL)
    {
    
    
        exit(1);
    }
    // 初始化数组

    array->dim = dim;
    array->bounds = (int*)malloc(sizeof(int)*dim);
    array->constants = (int*)malloc(sizeof(int)*dim);
    if (array->constants == NULL || array->bounds == NULL)
    {
    
    
        exit(1);
    }
    //初始化数组信息

    va_start(args, dim);
    for (i = 0; i < dim; ++i)   // 获取每个维度的长度
    {
    
    
        *(array->bounds+i) = va_arg(args, int);     // 保存每个维度的长度
        cont *= *(array->bounds+i);                 // 统计一共有多少个元素
    }

    array->base = (dataType *)malloc(sizeof(dataType)*cont);
    // 动态生成有多少个元素,开辟储存空间

    *(array->constants+dim-1) = 1;  // 最高维度的数组映像函数常数为1

    for (i = dim - 2; i >= 0; --i)  //  最高维度已经手动统计啦
    {
    
    
        *(array->constants+i) = *(array->constants+i+1)*(*(array->bounds+i+1));
        // 当前维度的数组映像函数常数 = 比当前高一维度的常数*高一维度的长度
    }
    va_end(args);   // 保护动态参数指针

    return array;   // 数组生成好了
}

int ArrayDeInit(Array *array)
{
    
    
    if (!array->base) return ERROR;
    free(array->base);
    if (!array->constants) return ERROR;
    free(array->constants);
    if (!array->bounds) return ERROR;
    free(array->bounds);
    return OK;
}

int ArrayGetAddr(Array *array, ...)
{
    
    
    int i = 0, temp;
    int addr = 0;
    va_list args;
    va_start(args, *array);

    for (; i < array->dim; ++i)     // 从每一个维度遍历
    {
    
    
        temp = va_arg(args, int);
        if (temp < 1 || temp > *(array->bounds+i))
        {
    
    
            exit(2); // 当前维度下标越界
        } 
        addr += (temp-1)*(*(array->constants+i)); // (每个维度下标-1)乘以常数的和,就是下标
    }

    return addr;
}

猜你喜欢

转载自blog.csdn.net/u011017694/article/details/109608615