基础数据结构之数组与链表(二)

        本文主要学习数组的一个变种——多维数组(Multi-Dimensional Arrays)。

一、多维数组

multi-dimensional array  of dimension n (i.e., an n-dimensional array or simply n-D array) is a collection of items which is accessed via n subscript expressions. E.g., the  element of the two-dimensional array x is accessed by writing x[i][j].

The C++ programming language provides built-in support for multi-dimensional arrays. However, the built-in multi-dimensional arrays suffer the same indignities that simple one-dimensional arrays do: Arrays in C++ are not first-class data types. There is no such thing as an array-valued expression. Consequently, you cannot use an array as an actual value parameter of a function; you cannot return an array value from a function; and, you cannot assign one array to another. The subscript ranges all start at zero and there is no bounds checking of array subscript expressions. Finally, the size of an array is static and fixed at compile time, unless dynamic memory allocation is explicitly used by the programmer.

多维数组也是C++的内置数据类型,同一维数组一样,它也不是第一类内置数据类型。它们有共同的不足:

1,不可作为函数参数和返回值;

2,数组之间不可直接赋值;

3,不会执行下标越界检查;

4,数组尺寸在编译期确定,不可在运行期修改。


二、多维数组下标计算

    计算机内存实际上是一个一维数组,内存地址即数组下标。而多维数组在计算机内存中存储时,也是按一维数组的方式存储的(这方面,同MATLAB的矩阵,它即可以按下标索引,也可以按idex索引)。因此,就需要设计一个映射,从多维数组的下标转为一维数组的索引值。这个机制的核心是一个映射函数,入参是多维数组的下标,出参是内存地址或索引。这个函数也决定了多维数组实际在内存中的存储方式,比如顺序存储,或链表存储。

    当然,最常见的存储方式是“row-major order,also known as lexicographic order”。原文中使用,“基地址+偏移量”的方式来计算数组元素地址。下面以2D Arrays为例,来学习多维数组的地址转换(映射)。

基础数据结构之数组与链表(二)_第1张图片

上图就是一个2 * 3结构的2维数组的内存地址分布示意图。

PS:基于内存地址的分布原理,下面展示一种“使用一次循环遍历二维数组”的方法。

int* array2D = new int[m][n];    // m and n are both integers
for (unsigned int i = 0; i < m * n; ++i)
{
    DoSomething(array2D[i/2][i%2]);
}

上述代码应用了数学中的整除(求模)和求余运算,按行遍历二维数组(反过来,也可以按列遍历)。该代码并不能提升运算速度,但可以使代码更简洁。

下面继续分析n维数组的的地址计算原理。与二维数组同理,它也是应用“基地址+偏移量”来计算数组项地址,只不过这个偏离量不能直接计算,需要拆解到各个维度,先计算各个维度的偏移量,再求和(“维度跳跃”)。如下图:

基础数据结构之数组与链表(二)_第2张图片


     n-Dimensional Array元素地址的运算时间是O(n^2),因为它实际上是n项的和,而每一项又要求O(n)的消耗。但是,在实际计算的时候,可以根据C++编译器的特点和运用一些数学运算技巧,提升该项运算的速度为O(n)。

The running time required to calculate the address appears to be  since the address is the sum of n terms and for each term we need to compute tex2html_wrap_inline61149, which requires O(n) multiplications in the worst case. However, the address calculation can in fact be done in O(n) time using the following algorithm:

unsinged int product = 1;
T * address = a;
for (int j = n; j >= 1; --j)
{
    address += product * i[j];    // i[j] -- the jth dimensional current subscript
	product *= delta[j];          // delta[j] -- the number of elements in the jth dimensional
}

This algorithm makes subtle use of the way that address arithmetic  is done in C++. Since the variable address is of type T*, it is not necessary to scale the computation by sizeof(T). In C++ whenever an integer value is added to a pointer variable, it is automatically scaled by the compiler before the addition.

这里利用了C++编译器在进行指针算术运算的一项特点。在C++的算术运算中,将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。例如,将指向double的指针加1后,如果系统对double使用了8个字节存储,则数值加增加8;将指向short的指针加1后,如果系统对short使用了2个字节存储,则指针值将增加2。(参考《C++PRIME PLUS》第4.8节“指针、数组和指针算术”。)故上述代码中,声明address的类型为指针,在实际计算时没有再乘上sizeof(T)。


三、二维数组实现

#ifndef TWO_DIMENTIONAL_ARRAY_H
#define TWO_DIMENTIONAL_ARRAY_H

#include "DynamicArray.h"

namespace FoundationalDataStructure
{
    template <typename T>
    class Array2D
    {
    public:
        class Row
        {
        public:
            Row(Array2D & _array2D, unsigned int _row) :
                array2D(_array2D), row(_row){}
            T & operator[](unsigned int column) const
            {
                return array2D.Select(row, column);
            }
        private:
            Array2D & array2D;
            unsigned int const row;
        };

        Array2D(unsigned int, unsigned int);
        T & Select(unsigned int, unsigned int);
        Row operator[](unsigned int);
    protected:
        unsigned int numberOfRows;
        unsigned int numberOfColumns;
        Array<T> array;
    };

    template <typename T>
    Array2D<T>::Array2D(unsigned int m, unsigned int n)
        : numberOfRows(m)
        , numberOfColumns(n)
        , array(m * n)
    {}

    template <typename T>
    T & Array2D<T>::Select(unsigned int i, unsigned int j)
    {
        if (i >= numberOfRows)
            throw std::out_of_range("invalid row");
        if (j >= numberOfColumns)
            throw std::out_of_range("invalid colunm");
        return array[i * numberOfColumns + j];
    }

    template <typename T>
    typename Array2D<T>::Row Array2D<T>::operator[](unsigned int row)
    {
        return Row(*this, row);
    }
} //namespace FoundationalDataStructure

#endif // TWO_DIMENTIONAL_ARRAY_H
该实现用到我之前一篇博客实现的一个动态数组:点击打开链接
测试代码如下:

    // Array2D test start
    Array2D<int> array2D(3, 4);
    for (int i = 0; i < 3 * 4; ++i)
        array2D[i / 4][i % 4] = i;

    for (int i = 0; i < 3 * 4; ++i)
    {
        if (i % 4 == 0)
            cout << "\n";
        cout << array2D.Select(i / 4, i % 4) << "  ";
        //cout << array2D[i / 4][i % 4] << "  ";
    }
    
    // Array2D test end

你可能感兴趣的:(基础数据结构之数组与链表(二))