Зубчатый массив хранит не прямоугольную область, а внешний массив ссылок на внутренние массивы. Каждая строка создаётся отдельно, может иметь собственную длину и до инициализации либо по предметному смыслу может быть равна null. Поэтому допустимость внутреннего индекса определяется не общей формой структуры, а существованием и длиной конкретной строки.
Внутренняя строка является самостоятельным объектом. Несколько позиций внешнего массива могут ссылаться на один объект, перестановка строк меняет только ссылки, а замена одной позиции не перенаправляет остальные псевдонимы. Поверхностная копия создаёт только новый внешний массив; независимая глубокая копия разрывает общие строки, а структурная глубокая копия создаёт новые объекты, но сохраняет топологию совместного использования внутри результата.
Выбор между T[,] и T[][] является частью спецификации данных. Прямоугольный массив гарантирует одинаковую длину строк и существование каждой позиции; зубчатый хранит только реальные элементы нерегулярной формы, но требует дополнительных объектов, ссылок и проверок. Свёртка, перенос в прямоугольную форму и транспонирование должны сопровождаться метаданными, достаточными для восстановления null, пустых строк, длин и при необходимости отношений псевдонимов.
Опора — Раздел 18: зубчатые массивы. Дополнительно используются вложенные циклы, инварианты и классы роста из Раздела 12, методы и кортежи из Раздела 13, одномерные массивы и бинарный поиск из Раздела 15, ссылочная семантика, идентичность, глубина копии и null из Раздела 16, прямоугольные массивы и координатные преобразования из Раздела 17.
Каждая задача оформляется отдельной консольной программой полным шаблоном курса и компилируется с
<Nullable>enable</Nullable>. Разрешены только конструкции Разделов 1–18. Для структуры, в которой строки могут отсутствовать, используется типint[]?[]; внешняя ссылка считается ненулевой, если условие не утверждает обратное. Коллекции,LINQ, собственныеstruct, дополнительные классы, делегаты, лямбда-выражения,Index,Range,Span<T>и обработка исключений запрещены. Внутренняя граница всегда получается изLengthтекущей строки после проверкиnull. Каждый метод фиксирует изменение либо сохранение входа, допустимость общих строк, глубину копирования и политику отсутствующих позиций. Потенциально аварийные обращения и намеренно некомпилируемые варианты исследуются только в отдельных копиях проекта.
База практических заданий
Уровень I. Базовый
Число внешних позиций, способ создания строк, их длины, порядок обхода и политика
nullзаданы условием. Требуется точно построить готовую структуру, применять собственную границу каждой строки и проследить указанные изменения ссылок и объектов. Ученик не выбирает представление и глубину копии: сложность возрастает от двухэтапного создания к агрегатам, псевдонимам, копированию, треугольным формам и заданным преобразованиям.
1. Двухэтапное создание лесенки: Реализовать:
static int[][] BuildStaircase(int rowCount)
для rowCount >= 0. Сначала создать внешний массив длины rowCount, затем для каждой позиции отдельно создать строку длины row + 1 и заполнить её последовательными целыми числами, продолжающими общую нумерацию от 0. Вывести внешнюю длину, длину каждой строки, общее число элементов и последний присвоенный номер. Инвариант внешнего цикла: строки [0, row) созданы, заполнены и содержат начальный участок сквозной последовательности.
2. Отсутствующая, пустая и непустая строка: Создать int[]?[] data, содержащий null, new int[0] и непустые строки. За один защищённый обход отдельно вычислить:
- число отсутствующих строк;
- число пустых строк;
- число непустых строк;
- общее число существующих элементов;
- сумму элементов.
Объяснить порядок проверок: обращение к row.Length допустимо только после установления row is not null, а пустая строка является существующим объектом с длиной 0.
3. Профиль каждой строки: Для int[]?[] data вывести по каждой внешней позиции один из трёх результатов:
- строка отсутствует;
- строка существует, но пуста;
- длина, сумма, минимум и максимум непустой строки.
Экстремумы инициализировать первым элементом текущей строки. Сформулировать инвариант внутреннего цикла и объяснить, почему одна затравка экстремума для всей структуры не заменяет отдельную обработку каждой строки.
4. Все координаты искомого значения: Для зубчатого массива с возможными null-строками и значения target вывести все пары (row, column), в которых встречается цель, и общее число вхождений. Внутренний цикл запускается только для существующей строки и ограничивается её длиной. При отсутствии совпадений вывести отдельный результат. Инвариант: после обработки внешнего префикса известны все вхождения в полностью просмотренных строках.
5. Одна строка под несколькими именами: Создать:
int[] shared = { 1, 2, 3 };
int[]?[] data =
{
shared,
new[] { 8, 9 },
shared
};
Изменить data[2]![1], затем вывести data[0] и проверить ReferenceEquals(data[0], data[2]). После этого заменить data[2] новым массивом и показать, что data[0] продолжает ссылаться на старую строку. Разделить изменение объекта строки и замену ссылки во внешней ячейке.
6. Перестановка строк без копирования элементов: Реализовать:
static void SwapRows(
int[]?[] data,
int first,
int second)
Метод обменивает две ссылки одной временной переменной и не обходит элементы внутренних массивов. До вызова сохранить псевдонимы двух непустых строк; после подтвердить через ReferenceEquals, что объекты и их содержимое не изменились, а только заняли другие позиции. Проверить также обмен null и пустой строки.
7. Поверхностная и позиционная глубокая копии: Реализовать:
static int[]?[] ShallowCopy(int[]?[] source)
static int[]?[] IndependentDeepCopy(int[]?[] source)
ShallowCopy создаёт новый внешний массив с теми же ссылками. IndependentDeepCopy сохраняет null, но для каждой существующей внешней позиции создаёт отдельную копию строки; если две позиции источника разделяли один объект, в результате они становятся независимыми. После копирования изменить исходную строку и строку глубокой копии, затем сравнить внешние и внутренние ссылки.
8. Разворот каждой существующей строки: Реализовать:
static void ReverseRows(int[]?[] data)
Каждая существующая строка разворачивается на месте двумя индексами; null, пустая и одноэлементная строки не требуют особого результата. Предусловие задачи: разные внешние позиции не ссылаются на одну строку. Объяснить, почему без этого предусловия один общий объект мог бы быть развёрнут несколько раз.
9. Треугольник Паскаля: Построить первые rowCount строк треугольника Паскаля как long[][]. Строка row имеет длину row + 1, крайние элементы равны 1, внутренние вычисляются:
Подтвердить симметрию каждой строки и сумму строки:
для диапазона, в котором результат представим в long. Объяснить, почему зубчатая форма не хранит несуществующие позиции над правой границей треугольника.
10. Прямоугольная таблица и массив строк: Реализовать:
static int[][] ToJagged(int[,] source)
static int[,] ToRectangular(int[][] source)
ToJagged создаёт по одной строке длины source.GetLength(1) для каждой строки таблицы. ToRectangular имеет предусловие: все строки существуют и обладают одинаковой длиной; пустой внешний массив восстанавливается как таблица формы 0 × 0. Выполнить полный круг преобразований и подтвердить форму и поэлементное равенство.
11. Удаление заполнителя из прямоугольной формы: Дана int[,] source, где в каждой строке значащие элементы образуют префикс, а первое значение -1 и весь хвост после него являются заполнителем. По заданному двухпроходному алгоритму построить int[][]:
- определить длину значащего префикса каждой строки;
- создать строку точного размера и перенести значения.
-1 не входит в результат; строка, начинающаяся с него, становится пустой. Подтвердить, что ни один элемент после первого заполнителя не переносится.
12. Компактная свёртка с массивом смещений: Для int[]?[] data построить:
static (
int[] values,
int[] offsets) Flatten(int[]?[] data)
offsets имеет длину data.Length + 1:
values содержит все существующие элементы в порядке строк. Для строки i её элементы занимают полуинтервал [offsets[i], offsets[i + 1]). Подтвердить неубывание offsets, равенство последнего смещения длине values и сохранение последовательности элементов. Зафиксировать, что одних values и offsets недостаточно для различения null и пустой строки.
Уровень II. Продвинутый
Условие описывает данные или требуемое преобразование, но не готовую форму, число проходов, политику пропусков и глубину копирования. Требуется самостоятельно выбрать прямоугольное либо зубчатое представление, вывести длины строк результата и определить метаданные, достаточные для восстановления. В отличие от Уровня I ученик проектирует структуру данных и доказывает, какая информация сохраняется, а какая осознанно теряется.
1. Группы переменного размера: Даны int[] sizes и int[] values, причём сумма неотрицательных размеров должна равняться values.Length. Спроектировать:
static bool TryBuildGroups(
int[] sizes,
int[] values,
out int[][] groups,
out long[] groupSums)
Отрицательный размер либо несогласованная общая длина дают false и пустые результаты. Нулевой размер создаёт пустую строку. При успехе каждый элемент values переносится ровно один раз и сохраняет порядок внутри своей группы.
2. Распределение длин строк: Для int[]?[] data вернуть:
- число
null-строк; - число пустых строк;
int?минимальной и максимальной длины существующей строки;int[] frequencies, где индекс равен длине существующей строки.
Самостоятельно определить число проходов: до создания frequencies требуется узнать максимальную длину. Если существующих строк нет, экстремумы равны null, а массив частот пуст.
3. Устойчивая сортировка строк по форме: Упорядочить внешний массив на месте по правилам:
- сначала
null; - затем существующие строки по неубыванию длины;
- при равной длине — по неубыванию суммы.
Использовать устойчивую сортировку вставками и перемещать только ссылки. До сортировки сохранить псевдонимы нескольких строк; после подтвердить идентичность объектов. При полном равенстве ключей исходный порядок должен сохраниться.
4. Вертикальные агрегаты нерегулярной структуры: Для int[]?[] data построить:
static (
long[] sums,
int[] counts,
double?[] means) BuildColumnProfiles(int[]?[] data)
Логический столбец column включает только строки, в которых эта позиция существует. Длина результатов равна максимальной длине существующей строки. Для столбца без участников среднее равно null; в корректно выбранной ширине такие хвостовые столбцы обычно отсутствуют, но контракт должен оставаться полным.
5. Свёртка с полным восстановлением состояния строк: Спроектировать:
static (
int[] values,
int[] offsets,
bool[] wasNull) Encode(int[]?[] source)
static int[]?[] Decode(
int[] values,
int[] offsets,
bool[] wasNull)
offsets задаёт длины строк, wasNull различает отсутствующую и существующую пустую строку. До декодирования проверить согласованность длин, начального и последнего смещения и неубывание массива. Выполнить полный круг и подтвердить состояния строк и значения. Отношения общих строк сохранять не требуется.
6. Координата по глобальному номеру элемента: Для корректного представления (values, offsets) найти координату элемента с глобальным номером position, где 0 <= position < values.Length. Из-за повторяющихся смещений пустых строк требуется найти индекс:
а затем:
Реализовать UpperBound бинарным поиском и вернуть (row, column). Подтвердить обратное равенство offsets[row] + column == position.
7. Прямоугольная форма с маской и обратное восстановление: Спроектировать преобразование int[]?[] в три объекта:
int[,] valuesминимальной достаточной ширины;bool[,] present, отмечающий реальные элементы;bool[] wasNull, различающийnullи существующую пустую строку.
Отсутствующие позиции в values заполняются заданным fillValue, который может совпадать с реальным значением. Обратное преобразование допускается при условии, что истинные значения present в каждой строке образуют префикс. Выполнить полный круг и доказать необходимость обоих видов метаданных.
8. Компактное транспонирование: Для зубчатого массива без null-строк построить результат, где строка column содержит source[row][column] только для тех исходных строк, у которых позиция существует. Пропуски удаляются. Самостоятельно определить:
- число строк результата;
- длину каждой строки;
- порядок элементов.
Показать две различные исходные формы, дающие одинаковый компактный результат, и сделать вывод о необратимости преобразования без метаданных.
9. Транспонирование с сохранением пропусков: Для int[]?[] source построить int?[][] result. Число строк равно максимальной длине существующей исходной строки; каждая строка результата имеет длину source.Length:
Отдельный bool[] wasNull сохраняет состояние исходных строк. Спроектировать обратное преобразование и подтвердить восстановление формы, null, пустых строк и значений.
10. Сжатое хранение симметричной матрицы: Квадратная симметричная таблица порядка n хранится нижним треугольником: строка row имеет длину row + 1 и содержит значения от (row, 0) до (row, row). Спроектировать:
- проверку симметрии и упаковку;
- восстановление полной таблицы;
- чтение элемента
(row, column)непосредственно из сжатой формы.
При column > row координаты меняются местами. Сравнить число хранимых элементов:
11. Умножение нерегулярных строк на вектор: Дан int[]?[] rows и вектор int[] weights, длина которого не меньше максимальной длины существующей строки. Построить long?[] result:
null-строка даётnull;- пустая строка даёт
0; - непустая строка даёт скалярное произведение с соответствующим префиксом
weights.
Самостоятельно сформулировать инвариант внутреннего цикла и объяснить, почему отсутствующая группа и существующая группа без элементов имеют разные предметные результаты.
12. Выбор формы по спецификации: Для трёх наборов данных письменно выбрать int[,] либо int[][], затем реализовать создание и один содержательный агрегат:
- плотная таблица измерений фиксированной ширины;
- группы наблюдений переменной длины;
- нижний треугольник симметричной матрицы.
Решение должно сравнить гарантии формы, число хранимых элементных ячеек, число объектов массива, необходимость проверок и сложность обхода, а не сводить выбор к различию синтаксиса.
Уровень III. Экспертный
Исследуется не прикладной агрегат, а граф внешних и внутренних объектов, достаточность метаданных либо цена выбранного представления. До сборки и запуска требуется нарисовать ссылки, предсказать идентичность строк, форму результата, число сохранённых элементов и возможную потерю информации; после — объяснить наблюдение через глубину копии, политику обработки псевдонимов и обратимость преобразования.
1. Полная трассировка графа строк: Построить пять внешних позиций:
- две ссылаются на одну строку;
- одна — на отдельную строку с тем же содержимым;
- одна равна
null; - одна содержит пустой массив.
Затем последовательно:
- изменить общий объект;
- переставить две внешние позиции;
- заменить одну позицию новым массивом;
- создать поверхностную копию внешнего массива;
- изменить строку через копию.
До запуска составить таблицу содержимого и ReferenceEquals после каждого шага. После объяснить, какие операции меняют внутренний объект, какие — внешнюю ячейку, а какие создают только новый внешний массив.
2. Две модели глубокой копии: Для структуры с общими строками реализовать:
- независимую копию, создающую отдельный объект для каждой существующей позиции;
- структурную глубокую копию, создающую одну новую строку на каждый уникальный исходный объект и сохраняющую отношения псевдонимов внутри результата.
Коллекции запрещены: уникальные исходные строки и соответствующие копии хранить в двух параллельных массивах и искать линейно. Результат не должен разделять строки с источником. Сравнить идентичность и оценить худшую сложность.
3. Равенство формы, содержимого и топологии ссылок: Спроектировать три независимых отношения для двух int[]?[]:
static bool HaveSameShape(...)
static bool HaveSameValues(...)
static bool HaveSameAliasPattern(...)
HaveSameShape сравнивает внешнюю длину, состояния null и длины существующих строк. HaveSameValues дополнительно сравнивает элементы. HaveSameAliasPattern требует, чтобы для любых внешних позиций i и j выполнялось:
До запуска построить примеры, совпадающие по первым двум отношениям, но различающиеся по третьему. Объяснить, почему поэлементное равенство не описывает граф объектов.
4. Обработка по внешним позициям и по уникальным объектам: Реализовать две операции прибавления delta:
- первая обходит внешние позиции и изменяет каждую встреченную строку;
- вторая изменяет каждый уникальный объект строки ровно один раз.
Для второй версии использовать массив ранее встреченных ссылок и линейный поиск через ReferenceEquals. На структуре с общими строками до запуска предсказать итог. Объяснить, почему первая операция имеет семантику «один раз на позицию», а вторая — «один раз на объект», и почему контракт обязан выбирать одну из них явно.
5. Обратимость двух транспонирований: На нескольких нерегулярных структурах сравнить компактное транспонирование и транспонирование с null-пропусками. Найти две различные исходные структуры с одинаковым компактным результатом. Затем доказать, что сохраняющая пропуски форма вместе с wasNull допускает обратное восстановление. Разделить потерю длин строк, положения пропусков и различия между null и пустой строкой.
6. Перестановка строк по циклам: Дан зубчатый массив и int[] permutation, где строка из старой позиции i должна перейти в новую позицию permutation[i]. Сначала проверить, что массив индексов является перестановкой. Затем выполнить преобразование на месте по циклам, используя bool[] visited и одну временную ссылку; элементы внутренних массивов не копируются. До запуска разложить перестановку на циклы. После доказать, что каждая внешняя позиция записана один раз и все исходные объекты и отношения псевдонимов сохранены.
7. Минимальные метаданные для точного восстановления: Последовательно исследовать четыре представления зубчатой структуры:
- только
values; values + offsets;values + offsets + wasNull;values + offsets + wasNull + representative.
representative[i] хранит индекс первой внешней позиции, с которой строка i разделяет объект; для null используется отдельное согласованное значение. Для каждого уровня указать, какие свойства можно восстановить: последовательность элементов, длины строк, различие null и пустой строки, топологию псевдонимов. Реализовать точное кодирование и декодирование четвёртого представления без коллекций.
8. Цена треугольного представления: Для нижнетреугольных данных порядка n сравнить:
- прямоугольный
int[n,n]; - зубчатый массив со строками длины
1,2,\ldots,n.
До запуска вычислить:
Для n = 1, 10, 100, 1000 определить долю исключённых элементных ячеек. Затем реализовать одинаковую сумму значимых элементов и подсчитать обращения к элементам. Объяснить компромисс: меньше отсутствующих ячеек против дополнительных объектов, ссылок и нерегулярных границ. Фактический объём памяти и скорость не измерять до Раздела 21.
Обязательные контрольные наборы
| Задача | Исходные данные |
|---|---|
| I.1 | rowCount = 0, 1, 5 |
| I.2 | [null, [], [3,4], null, [0]] |
| I.3 | [null, [], [4,-2,7], [5], [-1,-1]] |
| I.4 | [null, [2,5,2], [], [2], [7,2]], цели 2 и 9 |
| I.5 | общий ряд [1,2,3] в позициях 0 и 2 |
| I.6 | [[1,2], null, [], [7,8,9]], обмены (0,3) и (1,2) |
| I.7 | [null, [1,2], shared, shared, []], где shared = [7,8] |
| I.8 | [null, [], [1], [1,2,3,4], [5,6,7]] без общих строк |
| I.9 | rowCount = 0, 1, 6, 20 |
| I.10 | таблицы форм 0 × 0, 2 × 0, 2 × 3 |
| I.11 | [[1,2,-1,-1],[3,4,5,-1],[-1,-1,-1,-1],[6,7,8,9]] |
| I.12 | [null, [], [1,2,3], [4], null, [5,6]] |
| II.1 | sizes = [3,0,2,4], значения [1,2,3,4,5,6,7,8,9]; отрицательный размер и неверная общая длина |
| II.2 | [null, [], [1,2], [3], null, [4,5]] |
| II.3 | [null, [5], [1,2], [], [3,4], [9]] |
| II.4 | [null, [1,2,3], [], [4], [5,6]] |
| II.5 | [null, [], [1,2,3], [4], null, [5,6]]; повреждённые offsets |
| II.6 | представление предыдущего набора; все допустимые глобальные позиции |
| II.7 | [null, [7,-1], [], [-1,5,9]], fillValue = -1 |
| II.8 | [[1,2,3],[4],[5,6]]; затем [[1],[2,3]] и [[1,2],[3]] |
| II.9 | [null, [1,2,3], [], [4], [5,6]] |
| II.10 | симметричные таблицы порядков 0, 1, 4; несимметричная таблица |
| II.11 | [null, [], [1,2,3], [4,-1]], weights = [10,20,30] |
| II.12 | плотная форма 3 × 4, строки длин [2,5,1], нижний треугольник порядка 5 |
| III.1 | общий ряд [1,2], отдельная строка [1,2], null, [] |
| III.2 | [shared, [1,2], null, shared, []], где shared = [7,8,9] |
| III.3 | пары с одинаковой формой и значениями, но различной топологией общих строк |
| III.4 | [shared, [10], shared, null], где shared = [1,2], delta = 5 |
| III.5 | структуры из II.8–II.9 и две различные формы с одинаковым компактным транспонированием |
| III.6 | строки A,B,C,D,E; перестановки [2,0,1,4,3], тождественная и один цикл длины 5 |
| III.7 | [shared, null, [], shared, [1,2]], где shared = [7,8] |
| III.8 | n = 1, 10, 100, 1000 |
Итог модуля
Ученик создаёт внешний массив и внутренние строки отдельно, различает null, пустую и непустую строку и получает границу каждого внутреннего обхода из текущего объекта. Он прослеживает общие строки, переставляет ссылки без копирования элементов и различает поверхностную, независимую и структурную глубокую копии, а также обработку по внешним позициям и по уникальным объектам. Представление T[,] либо T[][] выбирается по гарантированной форме и цене хранения; свёртка, перенос в прямоугольную область и транспонирование сопровождаются явной политикой пропусков и метаданными, достаточными для восстановления длин, null, пустых строк и при необходимости топологии псевдонимов.
Покрытие опоры и границы
- Синтаксис
T[][], внешний массив ссылок и отдельное создание строк: I.1–I.2. null, пустая и непустая строка, внешняя и внутренняя длина: I.2–I.4, II.2, III.1.- Защищённый вложенный обход и собственная граница каждой строки: I.2–I.4, I.8–I.12, II.1–II.12.
- Несколько ссылок на одну строку, изменение объекта и замена внешней ссылки: I.5–I.8, III.1–III.4.
- Перестановка строк без копирования элементов: I.6, II.3, III.6.
- Поверхностная, независимая и структурная глубокая копии: I.7, III.2–III.3.
- Треугольные и иные нерегулярные структуры: I.9, II.10, II.12, III.8.
- Перенос между
T[,]иT[][]: I.10–I.11, II.7. - Свёртка,
offsets, обратное восстановление и поиск координаты: I.12, II.5–II.6, III.7. - Вертикальные агрегаты и алгоритмы над логическими столбцами: II.4, II.11.
- Компактное и сохраняющее пропуски транспонирование: II.8–II.9, III.5.
- Выбор прямоугольной или зубчатой формы: II.12, III.8.
- Достаточность метаданных и обратимость преобразований: II.5, II.7–II.9, III.5 и III.7.
- Сравнение формы, содержимого и топологии ссылок: III.3.
- Цена дополнительных объектов, ссылок, проверок и исключённых ячеек: II.10–II.12, III.2, III.4 и III.8.
За границей: строки как текстовые последовательности рассматриваются в Модуле 9, а собственные структуры — в Модуле 10. Массивы произвольной вложенности, рекурсивные графы объектов, обобщённые коллекции, LINQ, Span<T>, специализированные разреженные форматы и низкоуровневая оценка фактической памяти относятся к последующим курсам. Зубчатый массив не считается автоматически более быстрым или более экономным: вывод ограничивается числом элементных позиций, объектов, ссылок и выполняемых операций до проведения измерений в Разделе 21.

