目 录CONTENT

文章目录

CSharp(十六) 交错数组

CSharp(十六) 交错数组

C# 中的交错数组(Jagged Array)是数组的数组,即每个元素本身也是一个数组。与矩形多维数组不同,交错数组的每个子数组可以具有不同长度。

定义

交错数组(Jagged Array),也称为锯齿数组,是一种数组的数组。它的每个元素都是一个一维数组,且这些子数组的长度可以各不相同。

声明语法

// 二维交错数组
dataType[][] arrayName;

// 三维交错数组(数组的数组的数组)
dataType[][][] arrayName;

// 更复杂的嵌套形式
dataType[][][,] arrayName;  // 交错数组,每个元素是矩形二维数组

创建与初始化

// 1. 分步创建:先声明外层数组,再逐个创建内层数组
int[][] jagged = new int[3][];  // 外层数组有3个元素
jagged[0] = new int[4];         // 第1个子数组有4个元素
jagged[1] = new int[2];         // 第2个子数组有2个元素
jagged[2] = new int[5];         // 第3个子数组有5个元素

// 2. 声明时直接初始化
int[][] jagged2 = new int[][] {
    new int[] {1, 2, 3, 4},
    new int[] {5, 6},
    new int[] {7, 8, 9, 10, 11}
};

// 3. 简写形式(省略 new int[][])
int[][] jagged3 = {
    new int[] {1, 2, 3, 4},
    new int[] {5, 6},
    new int[] {7, 8, 9, 10, 11}
};

// 4. 更复杂的交错数组
int[][][] threeD = new int[2][][];
threeD[0] = new int[][] {
    new int[] {1, 2},
    new int[] {3, 4, 5}
};
threeD[1] = new int[][] {
    new int[] {6, 7, 8},
    new int[] {9}
};

使用(增删改查)

查(访问元素)

int[][] jagged = {
    new int[] {1, 2, 3, 4},
    new int[] {5, 6},
    new int[] {7, 8, 9, 10, 11}
};

// 访问单个元素
int value = jagged[0][2];  // 第0个子数组的第2个元素:3
Console.WriteLine(jagged[1][0]);  // 输出:5
Console.WriteLine(jagged[2][4]);  // 输出:11

// 获取子数组长度
int len0 = jagged[0].Length;  // 4
int len1 = jagged[1].Length;  // 2
int len2 = jagged[2].Length;  // 5

// 获取外层数组长度
int outerLen = jagged.Length;  // 3

// 安全访问(防止 NullReferenceException)
if (jagged[1] != null && jagged[1].Length > 0)
{
    Console.WriteLine(jagged[1][0]);
}

改(修改元素)

int[][] jagged = {
    new int[] {1, 2, 3, 4},
    new int[] {5, 6},
    new int[] {7, 8, 9, 10, 11}
};

// 修改现有元素
jagged[0][0] = 100;  // 将第0行第0列改为100
jagged[1][1] = 200;  // 将第1行第1列改为200

// 替换整个子数组
jagged[1] = new int[] {50, 60, 70};  // 第1行现在变为3个元素

// 使用 Array 方法修改
Array.Reverse(jagged[0]);  // 反转第0个子数组:{4, 3, 2, 1}
Array.Sort(jagged[2]);   // 排序第2个子数组

增(添加元素/扩展)

交错数组本身大小固定,但可以通过创建新数组或操作子数组来实现"添加"效果:

// 方法1:替换子数组为更大的数组(添加元素到某一行)
int[][] jagged = {
    new int[] {1, 2, 3},
    new int[] {4, 5}
};

// 扩展第0行:创建新数组并复制
int[] newRow = new int[jagged[0].Length + 2];
Array.Copy(jagged[0], newRow, jagged[0].Length);
newRow[jagged[0].Length] = 100;     // 添加新元素
newRow[jagged[0].Length + 1] = 200;
jagged[0] = newRow;  // 替换原数组
// 结果:jagged[0] = {1, 2, 3, 100, 200}

// 方法2:添加新行(创建更大的外层数组)
int[][] newJagged = new int[jagged.Length + 1][];
Array.Copy(jagged, newJagged, jagged.Length);
newJagged[jagged.Length] = new int[] {10, 20, 30};
jagged = newJagged;
// 结果:jagged 现在有3行

// 方法3:使用 List<int[]> 作为动态替代(更灵活)
List<int[]> list = new List<int[]>();
list.Add(new int[] {1, 2, 3});
list.Add(new int[] {4, 5});
list.Add(new int[] {6, 7, 8, 9});  // 动态添加行
list[0] = new int[] {10, 20};      // 修改某行

删(删除元素)

int[][] jagged = {
    new int[] {1, 2, 3, 4},
    new int[] {5, 6, 7},
    new int[] {8, 9},
    new int[] {10, 11, 12, 13}
};

// 方法1:删除某行(创建新数组,跳过要删除的行)
int removeIndex = 1;  // 删除第1行
int[][] newJagged = new int[jagged.Length - 1][];
for (int i = 0, j = 0; i < jagged.Length; i++)
{
    if (i != removeIndex)
    {
        newJagged[j++] = jagged[i];
    }
}
jagged = newJagged;

// 方法2:清空某行(将子数组设为 null 或空数组)
jagged[2] = null;        // 设为 null(需注意后续访问判空)
jagged[2] = new int[0];  // 设为空数组

// 方法3:删除某行中的某个元素
int[] row = jagged[0];
int removeCol = 2;  // 删除第2列
int[] newRow = new int[row.Length - 1];
Array.Copy(row, 0, newRow, 0, removeCol);
Array.Copy(row, removeCol + 1, newRow, removeCol, row.Length - removeCol - 1);
jagged[0] = newRow;

遍历交错数组

int[][] jagged = {
    new int[] {1, 2, 3, 4},
    new int[] {5, 6},
    new int[] {7, 8, 9, 10, 11}
};

// 方法1:使用嵌套 for 循环(推荐,可处理 null 子数组)
for (int i = 0; i < jagged.Length; i++)
{
    if (jagged[i] == null) continue;

    for (int j = 0; j < jagged[i].Length; j++)
    {
        Console.Write($"{jagged[i][j]} ");
    }
    Console.WriteLine();
}
// 输出:
// 1 2 3 4
// 5 6
// 7 8 9 10 11

// 方法2:使用 foreach(更简洁)
foreach (int[] row in jagged)
{
    if (row == null) continue;

    foreach (int item in row)
    {
        Console.Write($"{item} ");
    }
    Console.WriteLine();
}

// 方法3:使用 LINQ(需要 using System.Linq)
var allItems = jagged.SelectMany(row => row ?? Array.Empty<int>());
Console.WriteLine(string.Join(", ", allItems));
// 输出:1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

交错数组作为方法参数

// 接收交错数组
void PrintJaggedArray(int[][] jagged)
{
    for (int i = 0; i < jagged.Length; i++)
    {
        if (jagged[i] == null) continue;

        for (int j = 0; j < jagged[i].Length; j++)
        {
            Console.Write($"{jagged[i][j]} ");
        }
        Console.WriteLine();
    }
}

// 返回交错数组
int[][] CreateTriangle(int rows)
{
    int[][] triangle = new int[rows][];
    for (int i = 0; i < rows; i++)
    {
        triangle[i] = new int[i + 1];
        for (int j = 0; j <= i; j++)
        {
            triangle[i][j] = j + 1;
        }
    }
    return triangle;
}
// 结果:
// 1
// 1 2
// 1 2 3

注意事项

1. 交错数组 vs 矩形数组

特性 交错数组 int[][] 矩形数组 int[,]
内存布局 不连续(数组的数组) 连续内存块
每行长度 可以不同 必须相同
声明方式 int[][] int[,]
初始化 new int[3][] new int[3,4]
访问语法 arr[i][j] arr[i,j]
性能 访问略慢(两次寻址) 访问更快(缓存友好)
灵活性 高(每行可独立调整) 低(结构固定)

2. 子数组可能为 null

交错数组创建后,子数组默认为 null,必须单独初始化:

int[][] jagged = new int[3][];
// 此时 jagged[0], jagged[1], jagged[2] 都是 null

// 必须逐个初始化
jagged[0] = new int[4];
jagged[1] = new int[2];
jagged[2] = new int[5];

// 访问前必须检查 null
// Console.WriteLine(jagged[0][0]);  // 如果未初始化会抛出 NullReferenceException

3. 索引访问方式不同

交错数组使用两个方括号,矩形数组使用逗号分隔:

int[][] jagged = {{1, 2}, {3, 4, 5}};
int[,] rect = {{1, 2, 3}, {4, 5, 6}};

// 交错数组
int a = jagged[0][1];  // 正确
// int b = jagged[0, 1];  // 错误!编译失败

// 矩形数组
int c = rect[0, 1];    // 正确
// int d = rect[0][1];   // 错误!编译失败

4. 初始化器语法差异

// 交错数组:每个子数组必须显式使用 new int[]
int[][] jagged = {
    new int[] {1, 2, 3},
    new int[] {4, 5}
};

// 矩形数组:可以直接使用嵌套花括号
int[,] rect = {
    {1, 2, 3},
    {4, 5, 6}
};

5. 交错数组本身是固定大小的

交错数组的外层数组长度固定,不能直接添加或删除行:

int[][] jagged = new int[3][];
// jagged.Length 永远是 3

// 需要"添加"行时,必须创建新数组
int[][] newJagged = new int[4][];
Array.Copy(jagged, newJagged, 3);
newJagged[3] = new int[] {1, 2, 3};
jagged = newJagged;  // 重新引用

6. 子数组可以替换为不同长度的数组

虽然外层数组固定,但子数组可以被替换:

int[][] jagged = {
    new int[] {1, 2, 3},
    new int[] {4, 5}
};

// 替换第0行为更长的数组
jagged[0] = new int[] {10, 20, 30, 40, 50};
// 现在 jagged[0].Length 是 5

7. 内存布局与性能

交错数组在内存中不连续,访问需要两次寻址:

int[][] jagged = new int[1000][];
for (int i = 0; i < 1000; i++)
{
    jagged[i] = new int[1000];
}

// 访问 jagged[i][j] 的过程:
// 1. 先找到 jagged[i](外层数组索引)
// 2. 再在 jagged[i] 中找到第 j 个元素
// 比矩形数组 arr[i, j] 多一次寻址

// 遍历时,矩形数组通常更快(缓存友好)
int[,] rect = new int[1000, 1000];  // 连续内存

8. 与 Array 类和 LINQ 的交互

int[][] jagged = {
    new int[] {3, 1, 2},
    new int[] {6, 4, 5}
};

// 对单个子数组使用 Array 方法
Array.Sort(jagged[0]);  // 排序第0行:{1, 2, 3}
Array.Reverse(jagged[1]);  // 反转第1行:{5, 4, 6}

// 使用 LINQ 处理整个交错数组
using System.Linq;

// 获取所有元素
var all = jagged.SelectMany(row => row);

// 获取总行数
int totalRows = jagged.Length;

// 获取总元素数
int totalElements = jagged.Sum(row => row.Length);

// 获取最长行的长度
int maxLen = jagged.Max(row => row.Length);

// 筛选非空行
var nonEmpty = jagged.Where(row => row != null && row.Length > 0);

9. 混合使用:交错数组包含矩形数组

// 交错数组的元素可以是矩形数组
int[][,] mixed = new int[2][,];

mixed[0] = new int[,] {
    {1, 2, 3},
    {4, 5, 6}
};

mixed[1] = new int[,] {
    {7, 8},
    {9, 10},
    {11, 12}
};

// 访问方式
int a = mixed[0][1, 2];  // 第0个矩形数组的第1行第2列:6
int b = mixed[1][2, 0];  // 第1个矩形数组的第2行第0列:11

10. 常见错误

int[][] jagged = new int[3][];

// 错误1:未初始化子数组就访问
// int x = jagged[0][0];  // NullReferenceException

// 错误2:使用矩形数组的语法
// int y = jagged[0, 0];  // 编译错误

// 错误3:初始化器缺少 new int[]
// int[][] bad = {{1, 2}, {3, 4}};  // 编译错误

// 错误4:索引越界
jagged[0] = new int[] {1, 2};
// int z = jagged[0][5];  // IndexOutOfRangeException

// 错误5:假设所有子数组长度相同
for (int i = 0; i < jagged.Length; i++)
{
    // 危险:如果某行比其他行短,会越界
    for (int j = 0; j < jagged[0].Length; j++)  // 应该用 jagged[i].Length
    {
        Console.WriteLine(jagged[i][j]);
    }
}

总结

交错数组适合表示每行长度不固定的数据,如:

  • 三角形矩阵
  • 稀疏矩阵
  • 不同长度的字符串列表
  • 树形结构的层级数据

其特点是:

  • 使用 [][] 声明和访问,而非 [,]
  • 每个子数组长度可以不同
  • 子数组默认为 null,必须单独初始化
  • 内存不连续,访问效率略低于矩形数组
  • 灵活性高,适合不规则数据结构
  • 外层数组大小固定,但子数组可替换
0
博主关闭了当前页面的评论