Java数据结构–数组、矩阵、广义表

一、简介

1.1 数组的概念

是n(n ≥ 1)个相同数据类型的数据元素a0,a1,…,an-1构成的占用一块联系地址的内存单元的有限集合。

1.2 特点

(1)数组中数据元素的数据类型相同;
(2)数组是一种随机存取结构,只要给定一组下标,就可以访问与其对应的数组元素;
(3)数组中数据元素的个数是固定的;

1.3 数组的存储

在计算机中,表示数组最普通的方式是采用一组连续的存储单元顺序地存放数组元素。由于内存是一维的,而数组是多维结构,我们可以认为二维数组是一个每个数据元素是一维数组的一维数组;三维数组是每个数据元素是二维数组的一维数组;四维数组是个每个数据元素是三维数组的一维数组,以此类推。
当然,三维数组也可以看成是一个每个数据元素是一维数据的二维数据,而四维数组也可以看成是每个数据元素是一维数组的三维数据或是一个每个数据元素是二维数组的二维数组等。
也就是说,n(n > 1)维数组可以看成是一个n - i(i ≥ 1) 维数组,每个数据元素是 i 维数组。
对于一个m×n的二维数组,可以看成一个矩阵,如图1.1 所示,也可以看成一个行向量的一维数组,如图1.2所示,还可以看成一个列向量的一维数组,如图1.3所示
对于一维数组A[n] = {a0, a1…,an-1}的存储,直接采用内存中一段连续的存储单元一次进行存储,如图1.4所示
一维数组任意数据元素ai的存储单元地址:Loc(ai) = Loc(a0) + i * k (0 ≤ i < n),其中k为单个元素所占空间。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于二维数组A[m][n]的表示有两种方法,对应地用一组连续的存储单元存放数组的元素就有两种存储次序:一种是一行序为主序的存储方法,即先存储第0行元素,再存储第1行元素,如图1.5所示
一行序为主序二维数组的任一数据元素ai,j的存储单元地址:Loc(ai,j) = Loc(a0, 0) + (i * n + j) *k (0 ≤ i < m,0 ≤ j < n),其中k为单个元素所占空间。
另一个是以列为主序的存储方式,即先存储第0列元素,在存储第1列元素,…,如图1.6所示
以列序为主序二维数组的任一数据元素ai,j的存储单元地址:Loc(ai,j) = Loc(a0,0) + (j * m + i)*k(0 ≤ i < m,0 ≤ j < n),其中k为单个元素所占空间。
对于更多维数组,数组元素在内存中的地址可以依此类推。

在这里插入图片描述

在这里插入图片描述

综上所述,我们可以看出数组的两个特点。第一,对同一个数组的任何一个元素,由下标求存储地址的运算时间是一样的,也就是说任何一个数组元素的访问过程是平等的,这是随机存取结构的一个优点。第二,为了在内存中给数组开辟足够的存储单元,数组的维数和大小必须事先给出。这对于数组大小不能预先确定的问题就不方便,数组大小规定过大,浪费空间,规定过小,运行时出现越界,使程序无法运行,这就是数组的一个缺点。

二、矩阵的压缩

矩阵的压缩存储就是对矩阵的存储采取相同值元素只存储一次,对零值元素不分配内存空间的策略。压缩存储的矩阵通常是特殊矩阵和稀疏矩阵。

2.1 特殊矩阵

特殊矩阵是指矩阵中有许多值相同的元素或有许多零元素,且值相同的元素或零元素的分布有一定的规律,如三角矩阵、对角矩阵等。一般采用二维数组来存储矩阵元素。对于特殊矩阵,可以通过找出矩阵中所有值相同元素的数学映射公式,只存储相同元素的一个副本,从而达到压缩存储数据量的目的。

2.1.1 对称矩阵

若一个n阶方阵A=(ai,j) 中的元素满足性质:ai,j = aj,i; 1 ≤ i,j ≤ n 且 i ≠ j,则 A 为对称矩阵,对称矩阵中的元素关于主对角线对称,因此,让每一对对称元素ai,j 和 aj,i ( i ≠ j)分配一个存储空间,则 n^2 个元素压缩存储到 n(n + 1) / 2个存储空间,能节约近一半的存储空间。
不失一般性,假设按行序为主序存储下三角形(包括对角线)中的元素。设用一维数组sa[0…n(n+1)/2]存储n阶对称矩阵,为了便于访问,必须找出矩阵A中第i行第j列的元素的下标值(i,j)和向量sa[k]的下标值k之间的对应关系。图2.1所示的对称矩阵元素在数组对应元素中的存储如图2.2所示

在这里插入图片描述
在这里插入图片描述

矩阵元素ai,j位于矩阵A的下三角(包括对角线)时 (i ≥ j),k=[i*(i+1)/2]+j;矩阵元素ai,j位于矩阵A的上三角时(i < j),可取其对称元素aji,k=[j*(j+1)/2]+1;由此可以推出n阶矩阵A中的任一数据元素ai,j的存储地址:Loc(ai,j) = Loc(Sa[k]) = Loc(Sa[0])+k*L,其中L为每个数据元素所占的存储单元数。

2.1.2 三角矩阵

以对角线划分,三角矩阵有上三角和下三角两种。
若一个n阶矩阵A满足条件:下三角(不包括对角线)中数据元素均为常数C或零元素,或上三角(不包括对角线)中的数据元素均为常数C或零元素,则分别称为上三角矩阵和下三角矩阵。
三角矩阵中的重复元素C可共用一个存储空间,其余的元素正好有n(n+1)/2个因此,三角矩阵可压缩存储到向量sa[0…n(n+1)/2]中,其中C存放在向量的最后一个分量中。图2.3所示三角矩阵元素在数组对应元素中的存储如图2.4所示

在这里插入图片描述
在这里插入图片描述

以行序为主序存储n阶三角矩阵A的上三角(包括对角线)元素,ai,j元素之前有i行(从第n行到第 i - 1行),一共有1*(2n-i+1)/2个元素,在第i行上ai.j之间有j - i个元素,因此,ai,j是第i*(2n-i+1)/2+j-i+1个元素,由于一维数组的下标是从0开始,因而sa[i*(2n-i+1)/2+j-i] = ai,j。所以,矩阵A中位于第i行第j列的元素ai,j的下标i,j与其存储在数组中的位置下标k存在如下对应关系:当 i ≤ j 时,k = i * (2n - i + 1)/2+j-i;当i>j时,k = n*(n+1)/2。

2.1.3 对角矩阵

矩阵中,除了主对角线和主对角线上方或下方若干条对角线上的元素之外。其余元素皆为零。即所有的非零元素集中在一对角线为中心的带状区域中,如图2.5所示,其中d为半带宽。
对角矩阵也可以按某个原则(或以行序为主,或以对角线的顺序)将其压缩存储到一维数组中,并只需存储主对角线及其两侧若干主对角线上的元素。其他所有元素不用存储。图2.5对角矩阵的存储如图2.6所示。

在这里插入图片描述
在这里插入图片描述

以行序为主序存储n阶对角矩阵,其中对角矩阵中的非零元素ai,j在一维数组中的存放位置k(k=0,1,2 … (2d+0) * n - (1 + d) * d- 1) 的对应关系为:
k=i*(2d+1)+d+(j - i) -1

2.2 稀疏矩阵

稀疏矩阵是矩阵中的一种特殊情况,其非零元素的个数远小于零元素的个数。设m行n列的矩阵A含t个非零元素,如果 t << m*n 时,则称A为稀疏矩阵。
稀疏矩阵压缩存储有两种方式:三元组表示法和十字链表示法。

2.2.1 三元组表示法

三元组表示法就是在存储非零元素的同时,存储该元素对应的行下标和列下标。稀疏矩阵中的每一个非零元素由一个三元组(i, j , ai.j)唯一确定。矩阵中所有非零元素存放在由三元组组成的数组中。这样能把一个稀疏矩阵转换为三元组线性表。如图2.7所示的稀疏矩阵A,其对应的三原作者表示为:( (6, 7, 6), (1, 3, 11), (1, 5, 17), (2, 2, 5), (4, 1, 19), (5, 4, 37), (6, 7, 50) )。其中第一个元组表示矩阵共6行、7列、6个元素。
图2.7所示的稀疏矩阵用顺序表存储,其三元组顺序存储结构如图2.8所示
图2.7所示的稀疏矩阵用链表存储,其三元组链式存储结构如图2.9所示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2.2 十字链表表示法

对于m * n的稀疏矩阵A,每个非零元素用一个结点表示,每个结点有五个成员:行号、列号、值、列后续引用和行后续引用,分别用来连接同列和同行中的下一个非零元素结点。也就是说,稀疏矩阵中同一列的所有非零元素都通过 列后续引用 链接成一个列链表,同一行的所有非零元素都通过 行后续引用 连接成一个行链表。每个非零元素好像一个十字路口,故称为十字链表。图 2.7所示的稀疏矩阵用十字链表存储,其存储结构如图2.10所示。

在这里插入图片描述

三、广义表

3.1 广义表的定义

广义表是由n(n ≥ 0)个相互具有线性关系的数据元素构成的一个有限序列,是线性表的推广。一般记作:LS=(a1,a2,a3, … ai, …, an)
其中,LS为广义表(a1,a2,a3, … ai, …, an)的名称,n表示广义表的长度,即广义表的包含元素的个数;当n=0时,则称为空表。如果ai是单个元素,则ai是广义表LS的原子;如果ai是广义表,则ai是广义表LS的子表。当广义表不为空时,称第一个数据元素为该广义表的表头,称其余数据元素组成的表为该广义表的表尾。广义表的深度指表中所包含括号的层数。注意,原子的深度为0。
为了区分原子和表,规定用小写字母表示原子,用大写字母表示广义表的表名。

3.2 广义表的特性

1)广义表是一种线性结构。广义表的数据元素之间有着固定的相对次序,如同线性表。但广义表并不等价于线性表,仅当广义表的数据元素全都是原子时,该广义表为线性表。广义表示线性表的扩展,而线性表示广义表的特例。如广义表A(a,b)就是线性表。
2)广义表也是一种多层次的结构。当广义表的数据元素中包含子表时,该广义表就是一种多层次的结构。
3)广义表可为其他广义表共享。当一个广义表可以为其他广义表共享时,共享的广义表称为再入表。在应用问题中,利用广义表的共享特性可以减少存储结构中数据冗余,以节约存储空间。
4)广义表可以是一个递归表,即广义表也可以是其本身的一个子表。
5)任何一个非空广义表LS均可分解为表头head(LS)=a0和表为tail(LS)=(a2,a3,…an)两部分,显然,一个广义表的表尾始终是一个广义表。空表无表头表尾。

3.3 广义表的存储结构

由于广义表中的数据元素具有不同的结构,通常是一种递归的数据结构,很难为每个广义表分配固定大小的存储空间,一般用链式存储结构表示,有头尾表示法和孩子兄弟表示法两种存储方式。

在这里插入图片描述

3.3.1 头尾表示法

任意非空的广义表,可分解为表头和表尾,反之,一对确定的表头和表尾可唯一确定一个广义表。在头尾表示法中需要有两种结构的结点:一种是表结点,如图3.2a所示,用以表示子表;一种是原子结点,如图3.2b所示,用以表示单元素。

在这里插入图片描述

在这里插入图片描述

在表结点中有三个域组成:标志域、指向表头的指针域和指向表尾的指针域;而原子结点需要两个域:标志域和值域。标志域是用来区分这两种结点的。

3.3.1 孩子兄弟表示法

在孩子兄弟表示法中,原子结点和表结点用相似的两种结点来表示。如图3.4所示,其中表结点是有孩子结点,cp和bp分别指向第一个孩子换一个兄弟的指针域;原子结点是无孩子结点,data和bp分别是指域和指向兄弟的指针域。tag是标志域,用来区分这两类结点,如tag为1,则表示该结点为表结点即有孩子的结点;tag为0,则表示该节点为原子结点即无孩子结点。

在这里插入图片描述

在这里插入图片描述