35、航班座位邻接矩阵计算与优化

航班座位邻接矩阵计算与优化

1. 问题背景与数据准备

在航班座位分配的场景中,我们需要生成一个邻接矩阵,以便确定哪个乘客需要支付额外费用。为了实现这个目标,我们首先构建了一个双向链表来表示座位信息,同时记录了座位的“双胞胎”关系(即前后排对应的座位)。

以下是添加座位节点的代码示例:

list.AddNode("Erica", "2B");
list.AddNode("Daisy", "2C");
list.AddNode("Mike", "2D");
list.AddNode("Keith", "2E");
list.AddNode("Mark", "2F");

foreach (Node node in list.Nodes) 
{ 
    Console.WriteLine($"{node.Name} | Seat: {node.Seat}  | Previous: {node.Previous?.Name} | Next: {node.Next?.Name}  | Twin: {node.Twin?.Name}"); 
}

运行这段代码,我们可以看到座位信息的正确输出,这表明双向链表的构建和节点添加操作正常。

2. 提取邻接矩阵

为了生成邻接矩阵,我们需要对双向链表中的每个元素进行两次迭代。具体步骤如下:
1. 初始化一个锯齿数组来存储邻接矩阵。
2. 遍历链表中的每个元素。
3. 对于每个元素,再次遍历链表中的所有元素。
4. 计算从当前元素到目标元素的步数。
5. 将步数存储在邻接矩阵中。

以下是实现该功能的代码:

private static int[][] CalculateAdjacencyMatrix(DoublyLinkedList list) 
{ 
    int[][] matrix = new int[list.Nodes.Count][]; 
    for (int i = 0; i < list.Nodes.Count; i++) 
    { 
        Node current = list.Nodes[i]; 
        matrix[i] = new int[list.Nodes.Count]; 
        for (int y = 0; y < list.Nodes.Count; y++) 
        { 
            Node target = list.Nodes[y]; 
            if (current == target) continue; 
            int count = 0; 
            Node temp = current; 
            while (temp != target) 
            { 
                count++; 
                temp = y > i ? temp.Next : temp.Previous; 
            } 
            matrix[i][y] = count; 
        } 
    } 
    return matrix; 
}

这个算法虽然简单,但效率较低,属于“暴力”解法。不过,在技术面试中,先得到一个可行的解决方案往往比追求最优解更重要。

3. 考虑“双胞胎”跳跃

上述代码没有考虑座位的“双胞胎”关系,而在某些情况下,通过“双胞胎”座位跳跃可以减少邻接值。因此,我们对代码进行了改进,添加了“双胞胎”跳跃的逻辑。

private static int[][] CalculateAdjacencyMatrix(DoublyLinkedList list) 
{ 
    int[][] matrix = new int[list.Nodes.Count][]; 
    for (int i = 0; i < list.Nodes.Count; i++) 
    { 
        Node current = list.Nodes[i]; 
        matrix[i] = new int[list.Nodes.Count]; 
        for (int y = 0; y < list.Nodes.Count; y++) 
        { 
            Node target = list.Nodes[y]; 
            if (current == target) continue; 
            int count = 0; 
            Node temp = current; 
            while (temp != target) 
            { 
                count++; 
                temp = y > i ? temp.Next : temp.Previous; 
            } 
            int countTwin = 1; 
            temp = current.Twin; 
            int index = y;  
            while (temp != target) 
            { 
                countTwin++; 
                if (temp.Next == null) 
                { 
                    index = 0; 
                } 
                if (temp.Previous == null) 
                { 
                    index = list.Nodes.Count; 
                } 
                temp = index > i ? temp.Next : temp.Previous; 
            } 
            matrix[i][y] = count < countTwin ? count : countTwin; 
        } 
    } 
    return matrix; 
}

改进后的代码虽然仍然不够优雅,可能存在隐藏的 bug,但对于我们的场景来说,近似的正确值已经足够。如果假设“Mark”是儿童,那么邻接矩阵会将“Scrooge”选为支付额外费用的对象,这与我们的预期相符。

4. 并行执行循环

为了提高性能,我们可以考虑并行执行循环。当处理可以轻松“分块”的数据时,并行操作可以带来性能提升。在计算邻接矩阵时,我们可以使用 .NET 的任务并行库(TPL)来并行执行循环。

private static int[][] CalculateAdjacencyMatrix(DoublyLinkedList list) 
{ 
    int[][] matrix = new int[list.Nodes.Count][]; 
    Parallel.For(0, list.Nodes.Count, i =>  
    { 
        Node current = list.Nodes[i]; 
        matrix[i] = new int[list.Nodes.Count]; 
        for (int y = 0; y < list.Nodes.Count; y++) 
        { 
            Node target = list.Nodes[y]; 
            if (current == target) continue; 
            int count = 0; 
            Node temp = current; 
            while (temp != target) 
            { 
                count++; 
                temp = y > i ? temp.Next : temp.Previous; 
            } 
            int countTwin = 1; 
            temp = current.Twin;  
            int index = y;  
            while (temp != target) 
            { 
                countTwin++; 
                if (temp.Next == null)  
                {  
                    index = 0;  
                }  
                if (temp.Previous == null)  
                {  
                    index = list.Nodes.Count;  
                }  
                temp = index > i ? temp.Next : temp.Previous; 
            } 
            matrix[i][y] = count < countTwin ? count : countTwin; 
        } 
    }); 
    return matrix; 
}

需要注意的是,在并行循环中不能使用 continue 关键字。此外,对于小规模数据,并行操作可能会因为额外的开销而导致性能下降,但在大规模数据场景下,并行操作可能会带来显著的性能提升。

5. 打印邻接矩阵

最后,我们需要将计算得到的邻接矩阵打印到控制台,以便查看结果。以下是打印矩阵的代码:

private static void PrintAdjacencyMatrix(int[][] matrix) 
{ 
    foreach (int[] row in matrix) 
    { 
        foreach (int column in row) 
        { 
            Console.Write($"{column}\t"); 
        } 
        Console.WriteLine(); 
    } 
}

通过调用这个方法,我们可以将邻接矩阵以表格形式输出,方便查看和分析。

6. 练习题答案

以下是一些相关练习题的答案及解释:
| 章节 | 练习题编号 | 答案 | 解释 |
| ---- | ---- | ---- | ---- |
| 2 | 2.1 | B, C | - |
| 2 | 2.2 | False | 可以在元组中混合不同类型,如 (bool a, int b) |
| 2 | 2.3 | B | - |
| 2 | 2.4 | False | SqlConnection 实现了 IDisposable 接口,需要清理资源 |
| 2 | 2.5 | D | AmigaOS 不被 .NET Core 支持 |
| 2 | 2.6 | C | - |
| 2 | 2.7 | C | Visual Basic 是 CLI 兼容语言,可与 C# 互用 |
| 2 | 2.8 | D | - |
| 2 | 2.9 | A | - |
| 2 | 2.10 | A | CoreCLR 是 CLR 的分支 |
| 2 | 2.11 | B, A | - |
| 2 | 2.12 | E | - |
| 2 | 2.13 | C | - |
| 2 | 2.14 | A | - |
| 3 | 3.1 | C | - |
| 3 | 3.2 | False | 一直将数据库信息保存在内存中会影响性能 |
| 3 | 3.3 | True | - |
| 4 | 4.1 | False | 属性可以应用于方法、类、类型、属性和字段 |
| 4 | 4.2 | D | - |
| 4 | 4.3 | False | 枚举使用 enum 关键字创建 |
| 4 | 4.4 | A, D | 只有粗心的人会把连接字符串写在便利贴上 |
| 4 | 4.5 | B | - |
| 4 | 4.6 | A | - |
| 4 | 4.7 | C | - |
| 4 | 4.8 | True | - |
| 4 | 4.9 | False | - |
| 5 | 5.1 | B | - |
| 5 | 5.2 | A | - |
| 5 | 5.3 | A, D | Tomcat 类似 WebHost,JVM 类似 CLR |
| 5 | 5.4 | False | 实现 IDisposable 接口可安全处理对象 |
| 5 | 5.5 | True | - |
| 5 | 5.6 | 见下方表格 | 有多种方式实现,返回类型为整数,返回两个整数输入参数的乘积 |
| 5 | 5.7 | 每个数据库实体一个 | - |
| 6 | 6.1 | C | 答案 B 描述的是 DRY 原则 |
| 6 | 6.2 | True | - |
| 6 | 6.3 | False | 测试类必须是公共的才能被测试运行器使用 |
| 6 | 6.4 | C | - |
| 6 | 6.5 | False | LINQ 允许使用类似 SQL 的语句和方法查询集合 |
| 6 | 6.6 | A | - |
| 6 | 6.7 | B | - |
| 6 | 6.8 | C | - |
| 6 | 6.9 | True | - |
| 6 | 6.10 | A | - |
| 6 | 6.11 | True | - |
| 7 | 7.1 | 编写使用异常发现方法属性的单元测试 | - |
| 7 | 7.2 | B | - |
| 7 | 7.3 | Exception | - |
| 7 | 7.4 | C | A 和 B 在某些情况下可以是集合基础类型的默认值 |
| 7 | 7.5 | C | 比较值类型时,相等运算符比较值 |
| 7 | 7.6 | A | 比较引用类型时,相等运算符比较内存地址 |
| 7 | 7.7 | True | - |
| 7 | 7.8 | A | 重载运算符时,需要同时重载其反向运算符 |
| 7 | 7.9 | False | 计算中不存在完美随机性 |
| 7 | 7.10 | False | 计算中不存在完美随机性 |
| 7 | 7.11 | True | - |
| 8 | 8.1 | C | - |
| 8 | 8.2 | False | 两个类高度依赖表示紧密耦合 |
| 8 | 8.3 | A | - |
| 8 | 8.4 | False | 基本类型以小写字母开头,包装类以大写字母开头 |
| 8 | 8.5 | C | - |
| 8 | 8.6 | False | 字符串是不可变的,每次更改都会分配新内存 |
| 8 | 8.7 | False | 需要重写基类的方法 |
| 8 | 8.8 | A | DRY 原则代表“不要重复自己” |

通过以上步骤,我们完成了航班座位邻接矩阵的计算、优化和结果展示,同时对相关知识点进行了巩固和练习。在实际应用中,我们可以根据具体需求对代码进行进一步的优化和扩展。

航班座位邻接矩阵计算与优化(续)

7. 练习题 5.6 详细解答

对于练习题 5.6,要求实现一个方法,返回类型为整数,返回两个整数输入参数的乘积。以下是几种可能的实现方式:

// 普通方法实现
public int Product(int a, int b)
{
    return a * b;
}

// 箭头函数实现
public int ProductArrow(int a, int b) => a * b;

这两种实现方式本质上是一样的,只是语法上有所不同。箭头函数是 C# 中一种简洁的语法糖,适用于简单的方法实现。

8. 练习题 6.7 异步单元测试解答

练习题 6.7 要求将失败的单元测试转换为异步运行并使其通过。以下是具体的异步单元测试代码:

[TestMethod]
public async Task CreateCustomer_SuccessAsync()
{
    CustomerRepository repository = new CustomerRepository();
    Assert.IsNotNull(repository);

    bool result = await repository.CreateCustomer("Donald Knuth");
    Assert.IsTrue(result);
}

[TestMethod]
public async Task CreateCustomer_Failure_NameIsNull()
{
    CustomerRepository repository = new CustomerRepository();
    Assert.IsNotNull(repository);

    bool result = await repository.CreateCustomer(null);
    Assert.IsFalse(result);
}

[TestMethod]
public async Task CreateCustomer_Failure_NameIsEmptyString()
{
    CustomerRepository repository = new CustomerRepository();
    Assert.IsNotNull(repository);

    bool result = await repository.CreateCustomer(string.Empty);
    Assert.IsFalse(result);
}

[TestMethod]
[DataRow('#')]
[DataRow('$')]
[DataRow('%')]
[DataRow('&')]
[DataRow('*')]
public async Task CreateCustomer_Failure_NameContainsInvalidCharacters(char invalidCharacter)
{
    CustomerRepository repository = new CustomerRepository();
    Assert.IsNotNull(repository);

    bool result = await repository.CreateCustomer("Donald Knuth" + invalidCharacter);
    Assert.IsFalse(result);
}

在这些测试方法中,使用了 async await 关键字来实现异步操作。 await 关键字会暂停当前方法的执行,直到异步操作完成,然后继续执行后续代码。

9. 性能分析与总结

在计算邻接矩阵的过程中,我们使用了不同的方法,包括基本的遍历算法、考虑“双胞胎”跳跃的算法以及并行执行循环的算法。下面是对这些算法的性能分析:

  • 基本遍历算法 :这是最基础的算法,通过两次遍历链表来计算邻接值。时间复杂度为 $O(n^2)$,其中 $n$ 是链表中节点的数量。该算法简单易懂,但效率较低,尤其是在节点数量较多的情况下。
  • 考虑“双胞胎”跳跃的算法 :在基本算法的基础上,增加了“双胞胎”跳跃的逻辑,通过比较普通遍历和“双胞胎”跳跃的步数,选择较小的值作为邻接值。虽然该算法仍然是 $O(n^2)$ 的时间复杂度,但在某些情况下可以减少步数,提高效率。
  • 并行执行循环的算法 :使用 .NET 的任务并行库(TPL)来并行执行循环,理论上可以提高性能。但对于小规模数据,并行操作可能会因为额外的开销而导致性能下降。在大规模数据场景下,并行操作可以充分利用多核 CPU 的优势,显著提高性能。

以下是一个简单的 mermaid 流程图,展示了计算邻接矩阵的整体流程:

graph TD;
    A[初始化链表] --> B[添加节点];
    B --> C[计算邻接矩阵];
    C --> D{是否考虑双胞胎跳跃};
    D -- 是 --> E[计算普通遍历步数];
    D -- 否 --> F[仅计算普通遍历步数];
    E --> G[计算双胞胎跳跃步数];
    F --> H[选择较小步数作为邻接值];
    G --> H;
    H --> I[并行执行循环(可选)];
    I --> J[打印邻接矩阵];
10. 实际应用建议

在实际应用中,我们可以根据具体的需求和数据规模选择合适的算法。如果数据规模较小,基本的遍历算法已经足够;如果数据规模较大,并且有多核 CPU 可用,可以考虑使用并行执行循环的算法。同时,在编写代码时,要注意代码的可读性和可维护性,避免出现隐藏的 bug。

此外,对于练习题的解答,我们可以将其作为知识的巩固和拓展。通过练习,我们可以更好地理解 C# 和 .NET 的相关知识,提高编程能力。

总之,通过本次航班座位邻接矩阵的计算和优化,我们不仅掌握了相关的算法和编程技巧,还学会了如何根据实际情况选择合适的解决方案。希望这些内容对大家有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值