计算机科学A复习:Unit 8 - 二维数组2D Array

Haowen Liang

Unit 8 - 二维数组2D Array

如果Array是一排用于存储数据的盒子,这个单元介绍的2D Array就可以被比作一个表格,有横、竖两个维度。比如,你可以使用一个 2D Array 不变格式地存储一个九九乘法表。

一维、二维数组示意图

U8.1 二维数组 2D Arrays

「*2D Array 二维数组」本质上仍然是 Array,所以它们的操作语法是基本相同的。在第六单元我们学习操作 1D Array 的时候已经知道,要表示某个数组里面的某个位置要用数组名[ index ] 的格式。明显的,在 1D Array 里面只有一个纬度,只需要一个方向、也就是一个 index 就可以表示准确的位置。但是现在 2D Array 有了两个纬度的存储空间,所以就需要有两个 index 分别表示*横方向上的位置。

在AP考试中,只考虑横、纵数量一致的正方形 2D Array。比如,我创建一个这样的 2D Array :

1
2
3
4
5
int[][] b = {{21, 24, 51, 82},
{52, 81, 18, 64},
{61, 12, 25, 12},
{28, 21, 47, 29},
};

上面的命令仍然被赋值符号「=」分割成为两部分。

左起先定义新建的 2D Array 所存储数据的类型,这里就是整数类型「int」;数据类型后没有空格,紧跟两个空的中括号[][],表示这是拥有两个纬度的二维数组。方括号们之后空格,接着为新建的二维数组命名,这里就命名为 b 。

右侧是初始数据,这个位置输入的初始数据数量决定了这个二维数组的存储空间数量存储空间的数量一旦确定就不能再增删。

注意 2D Array 输入初始数据的格式和 1D Array 有所不同,1D Array 只需要用一个大括号包裹所有要初始输入的数据,每个单独的数据之间用逗号分隔即可。但是 2D Array 需要区分横排和竖列,因此使用两层大括号来包裹输入的初始数据。外层大括号仍然包裹所有的数据,但是其中还有第二层大括号,每个内层大括号代表一横排。例如这里 b 中的 {52,81,18,64} 就是第二横排。以及,每个横排的内层大括号之间还要用逗号隔开。比如 {52, 81, 18, 64},{61, 12, 25, 12} 两个横排的数据之间就必须有一个逗号进行分隔。

可见,上面这个可以被比作「表格」的 2D Array 被命名为 b。b 有 4 横排、4 纵列,每个方向上的 index 就有「0、1、2、3」,形成的「表格」共有 16 个存储位置。

要指示 2D Array 中的某个具体存储位置的时候,**使用「Array 名称 [横排] [竖列]」**格式的指令。比如指示 b 的第一行、第三竖列的存储位置就要写:

1
b[0][2]

记得 index 是从零开始计数的。如果把上面这段命令放进输出语句的表达式就可以输出 b 的第一行、第三竖列的存储位置当时存储的数据,这里就是 51 。

当然,如果不想在新建变量的同时就输入初始数据的话,也可以这样完成新建:

1
int[][] a = new int[4][4];

语句左侧的格式不变,但右侧使用 new 关键字后空格,再写上数组存储数据的类型和两个中括号。这次在右侧的两个中括号里面分别填写横排的数量以及纵列的数量,先横后竖,如果是四行四列就都写 4。使用 new 关键字新建的数组会自动用默认初始值填充所有的存储位置。默认值依据定义的存储数据类型而定,已经在U6.1介绍过。

U8.2 Traversing 2D Arrays 遍历二维数组

【遍历二维数组】

要访问、修改存储在二维数组里面的数据,需要用到**「*Traverse 遍历」。遍历*二维数组的根本逻辑和遍历一维数组并没有大的不同:一维数组只有横排的左右方向,就像一个数轴,所以用一层循环**就可以完成从 0 号存储位置到最后一个存储位置的遍历;二维数组唯一的区别是多了一个纵向的纬度,组成了既有横排又有纵列的表格,所以需要两层循环各自计算横、竖方向上的存储位置编号。

我们以最开始创建的二维数组 b 为例:

二维数组 b

第一横排的 index 从零开始计算,以此类推第二横排 index 为 1、第三横排 index 为 2、第四横排 index 为 3 。竖列同理。这样一来,每个存储位置就有了唯一的坐标编号。例如,第二横排左起第三个存储位置(现在存储的是 18)的坐标编号就是 b[1][2],其中 b 是这个二维数组的名称,在写作过程中要记得替换成自己定义的数组名。

所以,我们只需要写两层循环,外层循环就用来从上往下遍历、内层循环从左往右遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main (String[] args) {
int[][] b = {{21, 24, 51, 82},
{52, 81, 18, 64},
{61, 12, 25, 12},
{28, 21, 47, 29},
};
for (int i = 0; i < 4 ; i ++) {//横排(上下)位置计数
for (int j = 0; j < 4 ; j ++) {//竖列(左右)位置计数
System.out.print(b[i][j] + "\t");//这里为了输出格式好看在每个存储位置后面加了个 tab 空格
}
System.out.print("\n");//每个横排循环完要记得换行
}
}

这里用输出做展示。外层循环做上下方向计数,内层循环做左右方向位置计数。进入循环后,外层循环开始,进入第一圈外层循环,此时 i = 0,随后开始内层循环。可以看到内层循环的循环条件是 j < 4,也就是说要在 i = 0 时候 j 要挨个从 0 循环计数到 3 完成一整套内层循环。

这样一来,第一圈外层循环里就包括了 4 圈内层循环,这四圈内层循环都分别执行一次输出语句。第一次内层循环的时候 i = 0, j = 0 输出 21;第二次次内层循环的时候 i = 0, j = 1 输出 24;第三次内层循环的时候 i = 0, j = 2 输出 51;第四次内层循环的时候 i = 0, j = 3 输出 82。所以第一行数据就被输出为:

21 24 51 82

可以按以上逻辑推断,第二圈外层循环的时候 i 已经变成了 1,而 j 又要从零开始循环到 3 完成四圈内层循环,挨个遍历 b[1][0], b[1][1], b[1][2], b[1][3]。这样就输出了第二行数据:

52 81 18 64

再往后继续完成第三、第四次外层循环每次外层循环内依然分别包括四次内层循环。等所有的循环结束后,就输出了数组 b 存储的所有数据,完成了二维数组遍历。

你可以自己再推演一下遍历二维数组的逻辑过程,然后在自己的 eclipse 上面尝试新建一个二维数组并输入一些初始数据,尝试遍历你自己新建的这个二维数组并按格式输出里面所有存储位置上存储的数据。

刚才我们是用外层循环的计数变量来指示上下方向的坐标。这叫 Row-major order,表示依附每个横排进行遍历。此时,第一圈外层循环表示第一横排,第一圈外层循环包括四圈内层循环,这些内层循环依次输出第一横排所属的 1 - 4 列(左右方向)上存储的数据。

如果我们把刚才的 b[i][j] 换成 b[j][i],遍历时被访问到的数据顺序就会发生改变,现在每圈外层循环仍然包括四次内层循环,但是每次内层循环都是从上到下输出当前数列14的所有数据。这就成了 column-major order,基于每个竖列进行遍历。自己在 Eclipse 内尝试把 b[i][j] 换成 b[j][i] 看一下输出格式的区别。

当然,除了使用普通的 for 循环来完成循环以外,也可以使用更简洁的 enhanced for 循环。应用于二维数组遍历的 enhanced for 循环也需要两层,因为实际上 enhanced for 循环只能遍历一维数组。但是,又因为二维数组其实就是一个装着数组的数组,相当于一个一维数组存储的不是数据而是其他数组的地址:

所以我们可以使用外层 enhanced for 循环读取存储着每一横排的下属数组的位置,再用内层循环来遍历每一个下属数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main (String[] args) {
int[][] b = {{21, 24, 51, 82},
{52, 81, 18, 64},
{61, 12, 25, 12},
{28, 21, 47, 29},
};
for (int[] temp:b) {
for (int read:temp) {
System.out.print(read+"\t");
}
System.out.print("\n");
}
}

这里的外层 enhanced for 循环用作临时缓存的变量 temp 的数据类型是一维数组,b 则是我们要遍历的目标二维数组。这是因为在上面的图片已经提到,二维数组中的各个横排就像是下属的一维数组一样被存储,所以我们通过 temp 挨个读出这些下属数组的地址。比如,第一圈外层循环读出来的是第一横排的地址,第二圈外层循环就读出来第二横排的地址……

temp 缓存一旦缓存好当前对应横排的地址,就会进入内层循环。内层循环要做的是遍历 temp 缓存的地址指向的下属数组,写法和遍历普通的一维数组没有差别。数据类型为 int 的内层循环临时缓存变量 read 会把 temp 指示的下属变量所存储的数据挨个读出。第一圈内层循环读第一个、第二圈内层循环读第二个…… 

同时还有输出语句把每次 read 读出的数据输出,所以程序开始运行后发生的,首先进入外层循环第一圈:temp 指向存储第一横排数据的一维数组。再由四圈内层循环依次访问并输出 21、24、51、82 。结束第一圈外层循环,进入第二圈外层循环, temp 此时指向存储第二横排数据的下属数组。内层循环再次完整的循环四次,输出52、81、18、64。第二圈外层循环结束。此后第三圈外层循环开始…… 依次类推,直到最后一个横排上的数据被遍历完,就完成了使用enhanced for 循环进行二维数组遍历的所有过程。

【在二维数组内进行搜索

掌握了遍历二维数组的方法,就是掌握了访问和修改二维数组内的每一个数据的方法。既然可以访问二维数组内的每一个数据,那么也就可以在二维数组里面搜索你想要的数据。我们之前了解过对一维数组进行搜索的语法,当时使用的就是最简单的搜索算法 —— Linear Search,也叫 Sequential Search。这种方法是在遍历数组的过程中,按 index 顺序逐一判断是否有与搜索目标相符的数据存在于数组内。同样的,我们也可以在遍历二维数组的过程中进行 Linear Search:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main (String[] args) {
int[][] b = {//被搜索的数组 b
{21, 24, 51, 82},
{52, 81, 18, 64},
{61, 12, 25, 12},
{28, 21, 47, 29},
};
int searchTar = 12;//这里输入搜索目标

for (int i = 0; i < 4 ; i++) {//遍历
for (int j = 0; j < 4 ; j++) {
if (searchTar == b[i][j]) {
System.out.println((i+1)+" 横排,"+(j+1)+" 竖列的"+b[i][j]+"与搜索目标一致");
}
}
}
}

在遍历的基础上,我使用了一个名为 searchTar 的变量来存储搜索目标,以及一个位于内层循环内部的 if 语句用来把每一个数组内存储的数据逐个与 searchTar 进行判断 —— 如果两个数据完全一致,表示找到了与搜索目标一致的数据,则输出提示语;如果被判断的数据与搜索目标不一致,程序不做输出提示,继续进行后续比对。

这段程序运行的输出结果是:

上面的示例程序中,我的搜索目标是12,所以当比对发现第三横排的第2、4个数据与目标一致时,分别输出了以上的提示语句。

注意,我的输出语句表达式里面引用 i 和 j 来表达横排、纵列的计数时都**加了一。**这是因为 i 和 j 都是 index 计数,从 0 开始计算;而横排、竖列的要用人类数数的计数方式,从 1 开始数,这就造成 i 和 j 的计数与横排、纵列都差一。比如,第3横排第2竖列的12的存储位置 index 应该是 [2][1]。

但后面用来代表数组中数据的 b[i][j] 并不需要加一,因为 i 和 j 本身就是 index 的计数,二维数组后的中括号也是要求填入 index 的号码。那么直接填入 b[2][1] 访问到的就是第三横排的第二竖列上存储的 12 。

总结

这个单元介绍了二维数组的基本操作语法以及最基础的搜索算法。你还可以把其他一维数组能用的算法都用于二维数组,非常方便。现在你可以处理更复杂的数据。

练习

2008-9

参考答案

这道题分别创建了一个一维数组和一个二维数组。随后用 for 循环来给二维数组 newArray 的各个存储位置赋值。

这里题干用了 row 和 col 两个变量分别指代二维数组 newArray 的横、纵两个 index,所以在看到循环中 row 会在每次赋值后递增,以及只有 row 递增到 3 才会触发 col 的递增并同时重置 row 的计数为 0 ,就应该想到这是一个 column-major order 的遍历赋值。赋值的顺序因此是第一竖列从上往下再第二竖列从上往下,最后第三竖列从上往下。最后完成了赋值的 newArray 应该是这样的:

可见,输出 newArray [0][2] 的时候,对应的数据是 7。选D。

  • Title: 计算机科学A复习:Unit 8 - 二维数组2D Array
  • Author: Haowen Liang
  • Created at : 2023-02-19 00:00:00
  • Updated at : 2025-07-03 02:55:41
  • Link: https://www.liang-haowen.online/f183d2a80f14/
  • License: This work is licensed under CC BY-NC-SA 4.0.