ГЛАВА 9. МНОГОМЕРНЫЕ МАССИВЫ
В предыдущей главе была рассмотрены аспекты обработки одномерных массивов данных в языке C#, но в повседневной практике приходится разрабатывать алгоритмы значительно сложнее. Очень большой класс задач предполагает работу с массивами более высокой размерности: двумерными, трехмерными и т.д. Язык C# имеет достаточно мощные средства для обработки массивов различной размерности.
Прямоугольные массивы
Прямоугольный массив имеет более одного измерения. Многомернымназывается такой массив, который характеризуется двумя или более измерениями, а доступ к отдельному элементу осуществляется посредством двух или более индексов.
Двумерные массивы являются наиболее простыми из многомерных массивов. К тому же и решение очень многих задач может быть часто сведено к обработке двумерных массивов, поэтому технологии использования двумерных массивов в программах языка С# посвящается целая глава.
Объявления и инициализация
В двумерном массиве позиция любого его элемента определяется двумя индексами. Если представить двумерный массив в виде таблицы данных, то один индекс означает строку, а второй – столбец. Чтобы объявить двумерный массив целочисленных значений размером n * m, достаточно записать следующее:
int[,] имя_массива = new int[n,m];
Следует обратить внимание на то, что синтаксически конструкция вида [,] указывает на создание ссылочной переменной двумерного массива.
Чтобы получить доступ к элементу двумерного массива, используется конструкция переменная с индексами, где необходимо указать оба индекса, разделив их запятой. Например, для присвоения элементу двумерного массива, позиция которого определяется координатами i и j, значение а можно использовать следующую инструкцию:
|
имя_массива[i,j] = a;
Многомерный массив можно инициализировать, заключив список инициализаторов каждой размерности в собственный набор фигурных скобок. Формат инициализации двумерного массива:
тип[,] имя_массива = {
{val00, val01, val02, …, val0m}
{val10, val11 val12, …, val1m}
…
{valn0, valn1, valn2, …, valnm}};
Здесь элемент valij – значение инициализации для конкретного элемента массива. Каждый внутренний блок означает строку. В каждой строке первое значение будет сохранено в первой позиции массива, второе значение – во второй и т.д.
Ниже приведены различные варианты описания двумерного массива.
Определяются только тип и имя массива, а размер массива и его элементы не заданы. Синтаксис конструкции:
тип[,] имя;
Пример использования для объявления целочисленного массива:
int[,] а;
Определяются не только тип и имя массива, но и с помощью оператора new заданы обе размерности массива и его элементам присвоены значения равные 0. Синтаксис конструкции:
тип[,] имя = new тип [ разм_1, разм_2 ];
Пример использования для объявления целочисленного массива:
int[,] b = new int [2, 3];
Размерность явно не задана, но она вычисляется компилятором по количеству значений в списке инициализации, элементам массива присваиваются значения из списка инициализации:
тип[,] имя = new тип [,] { список_инициализаторов };
Пример использования для объявления целочисленного массива:
int[,] c = new int[,] {{1,2,3},{4,5,6}};
Объявлен массив с типа int, состоящий из двух строк и трех столбцов. Оператор new опущен, но по умолчанию он подразумевается, размерность массива определяется списком инициализаторов, элементам массива присваиваются значения из списка инициализации:
|
тип[,] имя = { список_инициализаторов };
Пример использования для объявления целочисленного массива:
int[,] с = {{1,2,3},{4,5,6}};
Избыточное описание – размерность определяется явно и через список инициализации. Необходимо следить, чтобы полученные двумя путями значения размерностей не конфликтовали между собой.
тип[,] имя = new тип [ разм1, разм2]{ список_инициализации};
Пример использования для объявления целочисленного массива:
int[,] d = new int[2,3]] {{1,2,3),{4,5,6}}
Если список инициализации не задан, размерности могут быть не только константами, но и выражениями типа, приводимого к целому. К элементу двумерного массива обращаются, указывая индексы строки и столбца, на пересечении которых он расположен, например:
а[1,4] b[i, j] b[j, i]
Необходимо помнить, что компилятор воспринимает первый индекс как номер строки, второй индекс всегда – индекс столбца, независимо от того, как они ни были обозначены в программе.
Примеры
Пример 1. Для целочисленной матрицы размером m*n вычислить сумму элементов в каждой строке (рис. 9.1).
Рисунок 9.1– Матрица из m строк и n столбцов
Нахождение суммы элементов каждой строки требует просмотра матрицы по строкам. Схема алгоритма приведена на рисунке 9.2, программа – в листинге 1.
Листинг 1- Программа к примеру 1
using System;
namespace ConsoleApplication1
{ class Class1
{ static void Main()
{ const int m = 3, n = 4;
int[,] a = new int[m, n] {
{ 2, 2, 8, 9 },
{ 4, 5, 6, 2 },
{ 7, 0, 1, 1 }
};
Console.WriteLine("Исходный массив:");
for (int i = 0; i < m; ++i)
|
{ for (int j = 0; j < n; ++j)
Console.Write(" " + a[i, j]);
Console.WriteLine();
}
for (int i = 0; i < m; ++i)
{ int sum = 0;
for (int j = 0; j < n; ++j)
sum += a[i, j];
Console.WriteLine("В строке {0}сумма элементов {1}",i, sum);
}
Console.Read();
}
}
}
Рисунок 9.2– Схема алгоритма к примеру 1
Результат работы программы:
Следует обратить внимание на то, что переменная sum обнуляется перед циклом просмотра очередной строки матрицы, поскольку для каждой строки его вычисление начинается заново.
Пример 2. Поиск максимального элемента в двумерном массиве.
Схема алгоритма решения данной задачи представлена на рисунке 9.3, а программа в листинге 2.
Решение поставленной задачи осуществляется с помощью цикла по параметру i, изменяющегося шагом +1 от 0 до m-1 с вложенным циклом по параметру j, изменяющемуся шагом +1 от 0 до n-1.
Перед входом в цикл переменной max присваивается значение у[0,0] элемента массива. С каждым изменением параметра j происходит переход к новому элементу той же строки массива и сравнение его значения со значением переменной max.
Если значение элемента массива больше, то max присваивается значение элемента массива с текущим порядковым номером. Когда при данном значении параметра i завершается цикл по параметру j, происходит изменение параметра i и переход к новой строке двумерного массива.
Рисунок 9.3– Схема алгоритма к примеру 2
Листинг 2 – Программа к примеру 2
using System;
namespace ConsoleApplication1
{ class Program
{ static void Main(string[] args)
{ const int m = 3, n = 4;
int[,] y = new int[m, n] {
{ 2, 2, 8, 9 },
{ 4, 5, 6, 2 },
{ 7, 0, 1, 1 }
};
Console.WriteLine("Исходный массив:");
for (int i = 0; i < m; ++i)
{ for (int j = 0; j < n; ++j)
Console.Write(" " + y[i, j]);
Console.WriteLine();
}
int max=y[0,0];
for (int i = 0; i < m; ++i)
{ for (int j = 0; j < n; ++j)
if (y[i, j] > max) max = y[i, j];
}
Console.WriteLine("Наибольший элемент в матрице "+ max);
}
}
}
Результаты работы программы:
Ступенчатые массивы
В языке С# можно создавать двумерный массив специального типа, который называется ступенчатым массивом.
Ступенчатый или невыровненный массив – это массив одномерных массивов, каждый из которых представляет собой строку. Количество элементов в разных строках может различаться. В памяти ступенчатый массив также хранится иначе, чем прямоугольный: в виде нескольких внутренних массивов, каждый из которых имеет свой размер. Для хранения ссылок на каждый из внутренних массивов выделяется отдельная область памяти.
Объявления и инициализация
При объявлении ступенчатого массива каждая его размерность обозначается парой квадратных скобок:
тип[ ] [ ] имя;
Общая форма записи объявления ступенчатого массива с выделением памяти под ссылки на три строки, содержащих значения целого типа:
int[][] b = new int [3][];
Не смотря на то, что такое объявление указывает количество строк в массиве, память для строк массива еще не выделена. Строки ступенчатого массива в памяти размещаются индивидуально, поэтому под каждый из одномерных массивов, составляющих ступенчатый массив, память требуется выделять явным образом, например:
Выделение памяти под 0-ю строку (5 элементов):
b[0]=new int [5];
Выделение памяти под 1-ю строку (3 элемента):
b[1] =new int [3];
Выделение памяти под 2-ю строку (4 элемента):
b[2] = new int [4];
Здесь b[0 ], b[1] и b[2] – это отдельные массивы, к которым можно обращаться по имени, ниже приведен пример работы со ступенчатым массивом с использованием выделения памяти под каждую строку (лист. 3).
Другой способ выделения памяти:
int[] [] b = {new int[5], new int[3], new int[4]};
K элементу ступенчатого массива обращаются, указывая каждую размерность в своих квадратных скобках, например:
b[1][2] b[i][j] b[j][i]
В остальном использование ступенчатых массивов не отличается от использования прямоугольных.
Примеры
В листинге 3 продемонстрировано применение элементов класса Аrrау при работе со ступенчатым массивом.
Листинг 3 - Использование методов класса А rrау для обработки ступенчатого массива
using System;
namespace ConsoleApplication3
{ class Program
{ static void Main(string[] args)
{ int[][] b = new int[3][];
b[0] = new int[5] { 4, 5, 8, 3, 6 };
b[1] = new int[3] { 7, 9, 1 };
b[2] = new int[4] { 6, 5, 3, 1 };
Console.WriteLine("Исходный массив:");
for (int i = 0; i < b.Length; ++i)
{ for (int j = 1; j < b[i].Length; ++j)
Console.Write(" " + b[i][j]);
Console.WriteLine();
}
Console.WriteLine(Array.IndexOf(b[0], 8));
}
}
}
Необходимо обратить внимание на то, как внутри цикла по строкам определяется длина каждого одномерного массива (длина каждой строки массива).
Результат работы программы: