数组
数组的一般定义
数组是一种由相同数据类型构成的序列。数组的本质就是一个线性表。对于一维数组,该线性表中的元素类型就是需要存放的类型ElementType
,对于多维数组,其维度d≥2,则可以认为线性表的元素类型是d−1维数组。这样来看,数组也是一种递归的定义。
对于数组来说,通常只有以下两种操作:读取和写入。这两种操作需要一个输入向量来表示下标(即用来定位)。
数组中最常见的是一维数组和二维数组,分别可以对应线性代数中的向量(vector)和矩阵(matrix)。
在教材采用的C/C++语言中,可以很自然的使用形如int A[10]
这样的语句来定义一个数组。对于这样定义的数组,其长度是定长的,不允许在运行时改变。这样的数组定义也只能出现在栈内存区或者全局内存区。C99标准支持int n = 10; int A[n];
这样的用法,但是C++11标准不支持(因为STL容器是一个更好的替代)。对于堆内存的数组,可以使用指针,如int * A = (int *)malloc(sizeof(int) * 10);
来创建一个数组(指针指向数组的首地址);实际上,利用这种方式包装好一个容器,例如STL的std::vector
类,是更方便、更安全、更有效率的。顺带一提,STL的std::vector
类不支持直接存放二维(多维)数组,只能使用vector<vector<int>>
这种类似的方式进行存放。这样在使用push_back
方法将新的一维数组压入容器,可能存在反复执行构造函数的开销;对于C++11可以使用右值语义(R-Value)和std::move
以尽量减小这个问题带来的效率损失。
数组的存储结构
数组是一种线性表,因此需要连续的存放在存储器的相应位置。对于一维数组的存储结构,其实比较简单。在C/C++语言中,只需要一个指针指向数组的首地址,就能够表示一个数组。当然,必须要有一个标记,来指示数组的结束(例如使用length
字段标识数组的长度,亦或C风格字符串的\0
结束符)。
因此,对于一个一维数组A
,其元素A[i]
(按照C/C++的习惯i从0开始)的实际地址p=A+ki。
而对于一个二维数组,其输入是一个二元组,因此需要设法构造出一种映射,将其映射在存储器的连续的相应位置。常用的方法有行优先和列优先。
对于一个二维数组A[m][n]
,行优先是指,A[i]
取出的元素,是表示第i行的一个一维数组;而列优先A[i]
表示的是第i
列的一个一维数组。因此不难得出下面的计算公式:对于A[m][n]
,A[i][j]
元素的位置
行优先:
列优先:p=A+k(mi+j)(j个满列,紧接着的一列的第
C/C++的编译器默认按照行优先存放。
最后需要提及的是,并不是所有语言的数组都是使用一串连续存储空间实现的。之前提及过的PHP的数组,是使用的哈希表来处理键值对(字符串类型的键也可以作为数组的下标)。
数组与特殊矩阵
矩阵可以使用二维数组进行存储。对于特殊形式的矩阵(对称阵、上下三角阵),只需要存储一半的元素就可以了。
常见的存储映射是,将二维降维,映射在一维数组上。以下三角阵为例,对于Amn,用一个向量B=(b1,b2,...bmn2)来表示,则b1=a11,b2=a21,b3=a22,以此类推,则aij=bi(i−1)2+j(假设i,j都从1开始)。总计可以节省近一半的空间。
总之,特殊矩阵的存储,在于根据问题的情况找到一个合适的映射(能在
稀疏矩阵
首先给出稀疏矩阵的一个定义:一个m×n的矩阵,当非零元个数t≪mn,这样的矩阵称为稀疏矩阵(sparse matrix)。
稀疏矩阵的非零元分布具有随机性,因此不能简单作之前的映射。因此可以用键值对的线性表,来描述一个稀疏矩阵。
这里的键值对可以使用std::pair<std::pair<int, int>, ElementType>
来表示,也可以使用C++11的std::tuple<int, int, ElementType>
这个三元组类来表示。这里我更倾向使用键值对的表示,前一个pair
作为二元输入向量,而外层的pair
的第二个元素作为下标为输入向量的位置的值。
因此可以用std::vector<std::pair<std::pair<int, int>, ElementType>>
来表示一个稀疏矩阵。
初始情况下,这个线性表容器是空的,表示这个矩阵是零矩阵O(不含任何非零元)。对于一个非零的稀疏矩阵表示,修改
对于一个稀疏矩阵而言,其赋值操作的最坏运行时间取决于非零元的个数t而并非矩阵的实际元素个数std::map
还是O(1)的哈希表的std::unordered_map
都是会造成大量空间浪费,违背了稀疏矩阵的初衷。
一道不错的实验题
已知一个二维数组A[m][n],其每一行的元素都递增,且每一行的第一个元素都大于上一行的任意元素。求这个数组中的指定元素x。
最原始的办法是进行朴素搜索,需要
说起来很简单,但是编码还是很有难度的。尤其是第一层二分时,当元素不能在这一行时,必然满足小于第一个元素或者大于最后一个元素。除此之外,lower_bound
返回的是不小于指定值的最小下标。因此需要判断返回的值是否等于需要查找的值。
//Not Found Constant Define
const std::pair<int, int> not_found(-1, -1);
/**
* @brief 查找二维数组中指定元素的位置
* @param std::vector<std::vector<int>> & matrix 输入矩阵
* @param int val 待查找元素的值
* @return std::pair<int, int> 返回二元组表示的元素的位置
*/
std::pair<int, int> matrix_find(std::vector<std::vector<int> > & matrix, int val) {
//利用有序性二分查找,M*N矩阵,平均时间复杂度O(logM+logN)
int cbegin, cend, cmid;
cbegin = 0;
cend = matrix.size() - 1; //二分查找的end初始设置为最大的能取得值
while (cbegin < cend) {
cmid = (cbegin + cend) / 2;
if (val > matrix[cmid].back())
cbegin = cmid + 1;
else if (val < matrix[cmid].front())
cend = cmid - 1;
else
break;
}
//定位元素可能在的行
int row = (cbegin == cend) ? cend : cmid;
//利用lower_bound查找所在位置
std::vector<int>::iterator iter = lower_bound(matrix[row].begin(), matrix[row].end(), val);
if (iter != matrix[row].end() && *iter == val)
return make_pair(row, iter - matrix[row].begin());
else
return not_found;
}