C# 数据结构和算法 :03 数组和排序(二)
xsobi 2024-12-17 17:06 2 浏览
多维数组
C#语言中的数组不必只有一个维度。也可以创建二维数组。正如你将看到的,多维数组非常有用,并且在开发各种应用程序时经常被使用。
想象一个二维数组
如果你想想象一个二维数组,可以休息一下,闭上眼睛,玩数独游戏。如果你不知道这是什么,数独是一种流行的游戏,它要求你用1到9的数字填满一个9x9棋盘的空格。然而,每一行、每一列以及每一个3x3的格子只能包含唯一的数字。惊喜——这个棋盘形成了一个二维数组!你可以通过指定它的行和列来指向棋盘上的任何位置,就像在二维数组的情况下一样。如果你有点厌倦了用铅笔和纸解决这样的谜题,那么请看第9章,动手实践,你将学习如何创建一个解决数独谜题的算法!
下面展示了一个存储整数值的二维数组示例:
首先,你需要声明并初始化一个具有5行3列的二维数组,如下所示的代码行:
int[,] numbers = new int[5, 3];
numbers[0, 0] = 9; (...)
你也可以以一种略有不同的方式将声明与初始化结合起来:
int[,] numbers = new int[,]
{
{ 9, 5, -9 },
{ -11, 4, 0 },
{ 6, 115, 3 },
{ -12, -9, 71 },
{ 1, -6, -1 }
};
访问二维数组中特定元素的方式需要一个小的解释。让我们来看以下示例:
int number = numbers[2, 1];
numbers[1, 0] = 11;
在代码的第一行中,获取了第三行(索引等于2)和第二列(索引等于1)的值(即115),并将其设置为number变量的值。另一行将第二行和第一列中的-11替换为11。
现在你已经了解了一维和二维数组,让我们继续学习三维数组。你知道如何理解这个结构吗?
想象一个三维数组
如果你想更好地想象一个三维数组,启动一个你可以用积木创建建筑物的游戏。你将每个积木放置在棋盘上指定的位置,使用X和Y坐标。然而,你还可以建造建筑物的下一层,所以你也可以指定积木的Z坐标。在这种情况下,你在一个三维世界中操作,使用三维数组!
下面的图中展示了一个三维数组的例子:
如果你想创建一个三维数组,你可以使用以下代码:
int[,,] numbers = new int[3, 2, 3];
剩余的操作可以像处理不同维度的数组一样进行。当然,在访问数组的特定元素时,你需要指定三个索引。
到目前为止,你已经了解了一维、二维和三维数组。但是,使用四维数组可能吗?当然可以!
想象一个四维数组
想象一个四维数组可能不太容易,但让我们尝试一下!再次想象我们之前提到的三维游戏棋盘,但是内容会根据你在游戏中的等级而变化。这样,你可以使用X、Y和Z坐标来访问三维世界中的特定方块。为了得到目标值,你需要使用另一个维度,即提供你当前的等级。这样,根据第四维度,你会得到不同的结果。不那么难,对吧?
你可以使用以下代码行声明这样一个数组:
int[,,,] numbers = new int[5, 4, 3, 2];
如果你需要更多的维度,你可以应用它们。然而,请记住,使用更多的维度可能会难以理解,而且你的代码在未来可能更难以跟踪和维护。
介绍多维数组的话题已经结束,让我们来看一些例子。这些例子将向你展示如何在现实世界中使用这样的数据结构。
例子 - 乘法表
这个第一个例子展示了在二维数组上执行基本操作以呈现乘法表。它将1到10范围内所有整数值的乘积结果存储在数组中,并在控制台中呈现它们:
让我们来看看数组的声明和初始化:
int[,] results = new int[10, 10];
这里,创建了一个具有10行和10列的二维数组,并将其元素初始化为默认值——即零。当数组准备好后,你用乘法的结果填充它,并在控制台中展示结果。这样的任务可以使用两个for循环来完成,如下所示:
for (int i = 0; i < results.GetLength(0); i++)
{
for (int j = 0; j < results.GetLength(1); j++)
{
results[i, j] = (i + 1) * (j + 1);
Console.Write(#34;{results[i, j],4}");
}
Console.WriteLine();
}
在前面的代码中,你可以看到调用在结果数组上的 GetLength 方法。这个方法返回特定维度中的元素数量——也就是说,第一个(当传递0作为参数时)和第二个(传递1作为参数时)。在这两种情况下,根据数组初始化时指定的值,都返回了10。代码的另一个重要部分是设置元素值的方式。为此,你必须提供两个索引。
将乘法结果转换为字符串值后,它们的长度各不相同,从一个字符(如2*2的结果4)到三个字符(10*10的结果100)。为了改善它们的展示效果,你需要将每个结果都写成4个字符的长度。因此,如果整数值占用的空间较少,就应该添加前导空格。例如,1将显示为带有三个前导空格(___1,其中_代表空格),而100将只显示一个空格(_100)。你可以通过在插值字符串中使用适当的复合格式字符串(即,,4)来实现这一目标。
示例 - 游戏地图
另一个例子是一个程序,它展示了一个游戏的地图。这个地图是一个长方形,有6行和8列。数组中的每个元素指定了一种地形类型,比如草地、沙地、水或砖块(也被称为墙)。地图上的每个位置都应该用特定的颜色显示(比如草地用绿色),同时使用一个自定义字符来描绘地形类型(比如水用≈表示),如下图所示:
让我们开始创建两个辅助方法,这些方法使得根据地形类型获取特定颜色和字符成为可能(分别命名为GetColor和GetChar)。这些方法的代码如下:
ConsoleColor GetColor(char terrain)
{
return terrain switch
{
'g' => ConsoleColor.Green,
's' => ConsoleColor.Yellow,
'w' => ConsoleColor.Blue,
_ => ConsoleColor.DarkGray
};
}
char GetChar(char terrain)
{
return terrain switch
{
'g' => '\u201c',
's' => '\u25cb',
'w' => '\u2248',
_ => '\u25cf'
};
}
正如你所见,GetColor方法的代码是不言自明的。然而,GetChar方法根据字符的值(g、s、w或b)返回适当的Unicode字符。例如,在水的情况下,返回的是'\u2248'值,这是≈字符的表示。
让我们来看看代码的其余部分。在这里,你配置了地图,并且在控制台中展示它。代码如下:
using System.Text;
char[,] map = {
{ 's', 's', 's', 'g', 'g', 'g', 'g', 'g' },
{ 's', 's', 's', 'g', 'g', 'g', 'g', 'g' },
{ 's', 's', 's', 's', 's', 'b', 'b', 'b' },
{ 's', 's', 's', 's', 's', 'b', 's', 's' },
{ 'w', 'w', 'w', 'w', 'w', 'b', 'w', 'w' },
{ 'w', 'w', 'w', 'w', 'w', 'b', 'w', 'w' }
};
Console.OutputEncoding = Encoding.UTF8;
for (int r = 0; r < map.GetLength(0); r++)
{
for (int c = 0; c < map.GetLength(1); c++)
{
Console.ForegroundColor = GetColor(map[r, c]);
Console.Write(GetChar(map[r, c]) + " ");
}
Console.WriteLine();
}
Console.ResetColor();
这段代码不应该需要额外的注释或解释。只需记住,在控制台输出中使用Unicode值时,不要忘记通过将OutputEncoding属性设置为Encoding.UTF8来选择UTF-8编码。你可以使用ForegroundColor属性为控制台设置前景色。如果你想将这种颜色重置为默认颜色,只需像最后一行中展示的那样调用ResetColor方法即可。
到目前为止,你已经了解了单维和多维数组,但本书还剩下一种变体需要介绍,那就是锯齿数组。让我们继续阅读以了解更多关于它们的信息。
锯齿数组
本书中要描述的数组的最后一种变体是锯齿数组,也被称为数组的数组。听起来很复杂,但幸运的是,它非常简单。锯齿数组可以理解为一个单维数组,其中每个元素都是另一个数组。当然,这些内部数组可以有不同的长度,甚至可以未初始化。
想象一个不规则数组
如果你想更好地想象一个不规则数组,暂时停止阅读这本书,打开你的日历,并切换其视图以展示整个年份。它包含365或366个盒子,取决于年份。对于每一天,你都有不同数量的会议。在某些日子里,你有三个会议,而在其他日子,只有一个甚至零个。你的假期在日历中标记并为会议预留。你可以很容易地想象到不规则数组在这个情况下的应用。每一天的盒子都是这个数组的一个元素,它包含一个数组,里面是特定一天组织的会议数据。如果这一天是在你的假期中,相关的条目没有初始化。这使得不规则数组更容易可视化。
一个不规则数组的例子在下图中展示:
这个锯齿数组包含四个元素。第一个包含一个有两个元素的数组(9和5)。第二个元素包含一个有三个元素的数组(0, -3和12)。第三个未初始化(null),而最后一个是一个只有一个元素的数组(54)。
在继续示例之前,值得一提的是声明和初始化锯齿数组的方式,因为它与我们已经描述的数组有所不同。让我们来看一下以下代码片段:
int[][] numbers = new int[4][];
numbers[0] = new int[] { 9, 5 };
numbers[1] = new int[] { 0, -3, 12 };
numbers[3] = new int[] { 54 };
这段代码可以用集合表达式简化,如下所示:
int[][] numbers = new int[4][];
numbers[0] = [9, 5];
numbers[1] = [0, -3, 12];
numbers[3] = [54];
在第一行中,我们声明了一个包含四个元素的一维数组。每个元素都是另一个整数值的一维数组。当执行第一行代码时,numbers数组被初始化为默认值,即null。因此,我们需要手动初始化特定的数组和元素,如下三行代码所示。值得注意的是,第三个元素没有被初始化。
你也可以用不同的方式编写前面的代码,如下所示:
int[][] numbers =
{
new int[] { 9, 5 },
new int[] { 0, -3, 12 },
null!,
new int[] { 54 }
};
不仅如此——还有一个更短的变体可用:
int[][] numbers =
[
[9, 5],
[0, -3, 12],
null!,
[54]
];
如何从不规则数组中访问特定元素?让我们看看:
int number = numbers[1][2];
numbers[1][1] = 50;
第一行代码将number变量的值设置为12——即,从数组中获取第三个元素(索引等于2)的值,这是锯齿数组的第二个元素。另一行代码将数组中的第二个元素的值从-3更改为50,这是锯齿数组的第二个元素。
现在我们已经介绍了锯齿数组,让我们来看一个例子。
示例 – 年度运输计划
在这个示例中,你将学习如何开发一个程序,该程序为你全年的交通制定计划。对于每个月的每一天,应用程序都会选择一种可用的交通方式,比如开车、乘公交、乘地铁、骑自行车,或者干脆步行。最终,程序将展示生成的计划,如下图所示:
首先,让我们声明一个枚举类型,其中包含代表各种交通工具类型的常量:
public enum MeanEnum { Car, Bus, Subway, Bike, Walk }
代码的下一部分如下:
Random random = new();
int meansCount = Enum.GetNames<MeanEnum>().Length;
int year = DateTime.Now.Year;
MeanEnum[][] means = new MeanEnum[12][];
for (int m = 1; m <= 12; m++)
{
int daysCount = DateTime.DaysInMonth(year, m);
means[m - 1] = new MeanEnum[daysCount];
for (int d = 1; d <= daysCount; d++)
{
int mean = random.Next(meansCount);
means[m - 1][d - 1] = (MeanEnum)mean;
}
}
首先,创建了Random类的一个新实例。这将用于从可用的交通方式中随机选择一种。
接下来的一步是获取可用交通类型的数量。
然后,创建了一个不规则数组。假设它有12个元素,代表当前年份的所有月份。
接下来,使用for循环遍历一年中的所有月份。在每次迭代中,使用DateTime的DaysInMonth静态方法获取天数。不规则数组的每个元素是一个单维数组,包含MeanEnum值。这样的内部数组的长度取决于一个月中的天数。例如,一月设置为31个元素,四月设置为30个元素。
下一个for循环遍历月份中的所有天数。在这个循环内,你随机选择一种交通方式,并将其设置为不规则数组中某个元素的值。
代码的下一部分与在控制台中展示计划有关:
string[] months = GetMonthNames();
int nameLength = months.Max(n => n.Length) + 2;
for (int m = 1; m <= 12; m++)
{
string month = months[m - 1];
Console.Write(#34;{month}:".PadRight(nameLength));
for (int d = 1; d <= means[m - 1].Length; d++)
{
MeanEnum mean = means[m - 1][d - 1];
(char character, ConsoleColor color) = Get(mean);
Console.ForegroundColor = ConsoleColor.White;
Console.BackgroundColor = color;
Console.Write(character);
Console.ResetColor();
Console.Write(" ");
}
Console.WriteLine();
}
首先,使用GetMonthNames方法创建一个包含月份名称的一维数组,稍后将展示并描述该方法。然后,将nameLength变量的值设置为存储月份名称所需的最长文本长度。为此,使用Max扩展方法从包含月份名称的集合中找到最长的文本长度。得到的结果增加了2,以预留冒号和空格的空间。
使用for循环遍历锯齿数组的所有元素——即所有月份。在每次迭代中,将月份名称显示在控制台上。接下来的for循环用于遍历锯齿数组当前元素的所有项——即月份的所有天数。对于每一天,都会设置适当的前景色和背景色,并显示一个合适的字符。这两个颜色和字符由Get方法返回,该方法以MeanEnum值作为参数。稍后将展示这个方法的实现。
现在,让我们来看看GetMonthNames方法的实现:
string[] GetMonthNames()
{
CultureInfo culture = new("en");
string[] names = new string[12];
foreach (int m in Enumerable.Range(1, 12))
{
DateTime firstDay = new(DateTime.Now.Year, m, 1);
string name = firstDay.ToString("MMMM", culture);
names[m - 1] = name;
}
return names;
}
这段代码是不言自明的,但让我们专注于调用Range方法的那一行。它返回一个从1到12的整数集合。因此,我们可以将它与foreach循环一起使用,而不是使用简单的从1到12的for循环。只需将其视为解决同一问题的另一种方式。
最后,值得一提的是Get方法。它允许我们使用一个方法而不是两个,即为给定的运输类型返回一个字符和颜色。通过以值元组的形式返回数据,代码更短更简单,如下所示:
(char Char, ConsoleColor Color) Get(MeanEnum mean)
{
return mean switch
{
MeanEnum.Bike => ('B', ConsoleColor.Blue),
MeanEnum.Bus => ('U', ConsoleColor.DarkGreen),
MeanEnum.Car => ('C', ConsoleColor.Red),
MeanEnum.Subway => ('S', ConsoleColor.Magenta),
MeanEnum.Walk => ('W', ConsoleColor.DarkYellow),
_ => throw new Exception("Unknown type")
};
}
数组在本章中无处不在!现在我们已经学习了这种数据结构及其C#实现的相关主题,我们可以专注于一些与数组紧密相关的算法,即排序算法。你准备好了解其中的一些了吗?如果是的话,让我们继续进入下一节。
相关推荐
- 好用的云函数!后端低代码接口开发,零基础编写API接口
-
前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...
- 快速上手:Windows 平台上 cURL 命令的使用方法
-
在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...
- 使用 Golang net/http 包:基础入门与实战
-
简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
-
你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...
- window使用curl命令的注意事项 curl命令用法
-
cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...
- Linux 系统curl命令使用详解 linuxctrl
-
curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...
- Tornado 入门:初学者指南 tornados
-
Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...
- PHP Curl的简单使用 php curl formdata
-
本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
-
本章涵盖使用Actix提供静态网页...
- 我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些
-
这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...
- linux文件下载、服务器交互(curl)
-
基础环境curl命令描述...
- curl简单使用 curl sh
-
1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...
- 常用linux命令:curl 常用linux命令大全
-
//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...
- 三十七,Web渗透提高班之hack the box在线靶场注册及入门知识
-
一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...
- 一周热门
- 最近发表
-
- 好用的云函数!后端低代码接口开发,零基础编写API接口
- 快速上手:Windows 平台上 cURL 命令的使用方法
- 使用 Golang net/http 包:基础入门与实战
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
- window使用curl命令的注意事项 curl命令用法
- Linux 系统curl命令使用详解 linuxctrl
- Tornado 入门:初学者指南 tornados
- PHP Curl的简单使用 php curl formdata
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- webgl开发 (24)
- parse (24)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)