34、航空乘客信息显示与邻接矩阵编码实践

航空乘客邻接矩阵编码实践

航空乘客信息显示与邻接矩阵编码实践

1. 乘客姓名显示与线程休眠

1.1 代码基础与可扩展性

最初的代码用于读取包含乘客姓名的 CSV 文件,并使用 StringBuilder 类将这些数据组合成一个字符串。这种方法类似于常规的字符串拼接,但具有更好的可扩展性。因为我们不确定一架飞机可能搭载(或预订)多少乘客,所以这种方法不仅性能更好,而且更易于扩展和维护。

1.2 显示乘客姓名

我们已经实现了读取所有乘客姓名并将其存储在一个字符串中的功能,接下来要将这个字符串显示出来。以下是 Main 方法的代码:

public static void Main()
{
    IDataStorage storage = new InternalFileSystem();
    IDisplay display = new WelcomeAboardATron3000();

    string text = storage.Read();
    display.Print(text);
}

我们只需实现 WelcomeAboardATron3000.Print 方法的逻辑。该方法接受一个字符串并将其打印出来。由于使用了策略模式, IDisplay 派生类无需了解数据的获取方式,只需接受数据并进行适当显示。

以下是 WelcomeAboardATron3000 类的初步实现:

public class WelcomeAboardATron3000 : IDisplay
{
    public void Print(string text)
    {
        Console.Write(text);
    }
}

运行这段代码时,所有乘客姓名会一次性输出到控制台。

1.3 优化显示效果

为了让乘客更方便地查看信息,我们可以模仿打字机效果,逐个字符输出姓名。同时,在首次启动应用程序时添加问候语。

为了实现这个效果,我们需要使用线程并在打印每个字符后暂停一段时间。但要注意,不能暂停主线程,否则应用程序会冻结。我们创建一个新线程来执行打印延迟操作:

public class WelcomeAboardATron3000 : IDisplay
{
    public void Print(string text)
    {
        new Thread(() => PrintDelay(text)).Start();
    }

    private void PrintDelay(string text)
    {
        foreach (char character in text)
        {
            Console.Write(character);
            Thread.Sleep(90);
        }
    }
}

为了添加问候语,我们使用一个静态字段 _firstMessage 来判断是否为首次显示信息:

public class WelcomeAboardATron3000 : IDisplay
{
    private bool _firstMessage = true;

    public void Print(string text)
    {
        if (_firstMessage)
        {
            PrintDelay("Welcome Aboard! ");
            _firstMessage = false;
        }

        new Thread(() => PrintDelay(text)).Start();
    }

    private void PrintDelay(string text)
    {
        foreach (char character in text)
        {
            Console.Write(character);
            Thread.Sleep(90);
        }
    }
}

1.4 总结

通过上述步骤,我们实现了逐个字符输出乘客姓名并添加问候语的功能。以下是整个过程的流程图:

graph TD;
    A[读取乘客姓名] --> B[创建显示对象];
    B --> C[调用打印方法];
    C --> D{是否首次显示};
    D -- 是 --> E[打印问候语];
    E --> F[逐个字符打印姓名];
    D -- 否 --> F;

2. 邻接矩阵编码实践

2.1 问题背景

航空公司的座位安排引发了问题,老板希望对离所有儿童最远的乘客收取特殊“放松”费用。我们的任务是编写代码计算乘客之间的距离,并以邻接图的形式展示结果。

2.2 图和矩阵的基本概念

2.2.1 图的类型
  • 有向图 :边只能沿一个方向遍历,类似于单行道系统。
  • 无向图 :边可以双向遍历,类似于普通道路。
  • 循环图 :可以从一个顶点出发,不重复经过同一条边回到该顶点。
  • 无环图 :不存在这样的循环。
2.2.2 邻接矩阵

邻接矩阵是一种数学模型,用于表示两个顶点之间的相邻程度。通过找到顶点之间的最短路径并统计经过的边数,可以确定邻接矩阵。

2.3 创建图的内存模型

C# 没有专门的“图”类型或类,我们需要自己实现。考虑到无向无环图的特点,我们选择使用双向链表来表示图。

以下是双向链表的实现:

public class Node
{
    public Node Next;
    public Node Previous;
    public string Name;
    public string Seat;

    public Node(Node previous, string name, string seat)
    {
        Previous = previous;
        Name = name;
        Seat = seat;
    }
}

public class DoublyLinkedList
{
    public List<Node> Nodes = new List<Node>();

    public IEnumerable<Node> AddNode(string name, string seat)
    {
        Node current;

        if (Nodes.Count == 0)
        {
            current = new Node(null, name, seat);
            Nodes.Add(current);
        }
        else
        {
            Node previous = Nodes[^1];
            current = new Node(previous, name, seat);
            Nodes.Add(current);
            previous.Next = current;
        }

        return Nodes;
    }
}

我们可以使用以下代码测试双向链表:

static void Main(string[] args)
{
    DoublyLinkedList list = new DoublyLinkedList();
    list.AddNode("Ed Bonestalk", "21C");
    list.AddNode("Hyacinth Notts", "2A");

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

2.4 扩展双向链表

为了准确计算乘客之间的距离,我们需要考虑前后排座位的关系。我们在 Node 类中添加 Twin 属性,并修改 DoublyLinkedList 类的 AddNode 方法:

public class Node
{
    public Node Next;
    public Node Previous;
    public Node Twin;
    public string Name;
    public string Seat;

    public Node(Node previous, Node twin, string name, string seat)
    {
        Previous = previous;
        Name = name;
        Seat = seat;
        Twin = twin;
    }
}

public class DoublyLinkedList
{
    public List<Node> Nodes = new List<Node>();
    private const int seatCapacity = 12;

    public IEnumerable<Node> AddNode(string name, string seat)
    {
        if (Nodes.Count == 0)
        {
            Nodes.Add(new Node(null, null, name, seat));
        }
        else
        {
            Node previous = Nodes[^1];
            Node current;

            if (TwinMode())
            {
                Node twin = Nodes[^(seatCapacity / 2)];
                current = new Node(previous, twin, name, seat);
                twin.Twin = current;
                Nodes.Add(current);
            }
            else
            {
                current = new Node(previous, null, name, seat);
                Nodes.Add(current);
            }

            previous.Next = current;
        }

        return Nodes;
    }

    private bool TwinMode() => Nodes.Count > seatCapacity / 2 - 1;
}

2.5 测试扩展后的双向链表

static void Main(string[] args)
{
    DoublyLinkedList list = new DoublyLinkedList();
    list.AddNode("Scrooge", "1A");
    list.AddNode("Colin", "1B");
    list.AddNode("Harry", "1C");
    list.AddNode("Olivia", "1D");
    list.AddNode("Ziya", "1E");
    list.AddNode("Blair", "1F");
    list.AddNode("Bubba", "2A");
    // 可继续添加更多乘客信息
}

2.6 总结

通过使用双向链表,我们可以模拟飞机座位的布局,并为后续计算邻接矩阵打下基础。以下是创建双向链表的步骤总结:
1. 定义 Node 类,包含 Next Previous Twin Name Seat 属性。
2. 定义 DoublyLinkedList 类,包含 Nodes 列表和 AddNode 方法。
3. 在 AddNode 方法中,根据座位数量和当前节点位置确定是否设置 Twin 属性。

以下是创建双向链表的流程图:

graph TD;
    A[创建双向链表对象] --> B[添加第一个节点];
    B --> C{是否还有节点};
    C -- 是 --> D{是否进入第二排};
    D -- 是 --> E[设置 Twin 属性并添加节点];
    D -- 否 --> F[不设置 Twin 属性添加节点];
    E --> C;
    F --> C;
    C -- 否 --> G[结束];

3. 计算邻接矩阵

3.1 邻接矩阵的意义

邻接矩阵可以帮助我们清晰地了解乘客之间的距离关系。通过它,我们能够确定离儿童最远的乘客,从而实现老板收取“放松”费用的需求。以一个简单的无向无环图为例,通过计算顶点之间的最短路径和经过的边数,就能得到对应的邻接矩阵。

3.2 基于双向链表计算邻接矩阵

我们已经构建了双向链表来表示座位布局,接下来就可以基于这个链表计算邻接矩阵。以下是实现思路和示例代码:

public class AdjacencyMatrixCalculator
{
    public static int[,] CalculateAdjacencyMatrix(DoublyLinkedList list)
    {
        int nodeCount = list.Nodes.Count;
        int[,] matrix = new int[nodeCount, nodeCount];

        for (int i = 0; i < nodeCount; i++)
        {
            for (int j = 0; j < nodeCount; j++)
            {
                if (i == j)
                {
                    matrix[i, j] = 0; // 自身到自身的距离为 0
                }
                else
                {
                    matrix[i, j] = CalculateDistance(list.Nodes[i], list.Nodes[j]);
                }
            }
        }

        return matrix;
    }

    private static int CalculateDistance(Node start, Node end)
    {
        // 简单示例,实际需要更复杂的路径搜索算法
        // 这里仅考虑直接相邻和 Twin 关系
        if (start.Next == end || start.Previous == end || start.Twin == end)
        {
            return 1;
        }
        return int.MaxValue; // 表示不可达
    }
}

3.3 测试邻接矩阵计算

static void Main(string[] args)
{
    DoublyLinkedList list = new DoublyLinkedList();
    list.AddNode("Scrooge", "1A");
    list.AddNode("Colin", "1B");
    list.AddNode("Harry", "1C");
    list.AddNode("Olivia", "1D");
    list.AddNode("Ziya", "1E");
    list.AddNode("Blair", "1F");
    list.AddNode("Bubba", "2A");

    int[,] matrix = AdjacencyMatrixCalculator.CalculateAdjacencyMatrix(list);

    // 输出邻接矩阵
    for (int i = 0; i < list.Nodes.Count; i++)
    {
        for (int j = 0; j < list.Nodes.Count; j++)
        {
            Console.Write(matrix[i, j] + " ");
        }
        Console.WriteLine();
    }
}

3.4 总结

通过上述代码,我们实现了基于双向链表计算邻接矩阵的功能。以下是计算邻接矩阵的步骤总结:
1. 初始化一个二维数组作为邻接矩阵,其大小为节点数量的平方。
2. 遍历所有节点对,对于自身到自身的节点对,距离设为 0。
3. 对于其他节点对,调用 CalculateDistance 方法计算距离。
4. 输出邻接矩阵。

以下是计算邻接矩阵的流程图:

graph TD;
    A[创建双向链表并添加节点] --> B[调用计算邻接矩阵方法];
    B --> C[初始化邻接矩阵];
    C --> D[遍历节点对];
    D --> E{是否为自身到自身};
    E -- 是 --> F[距离设为 0];
    E -- 否 --> G[计算距离];
    F --> H[继续遍历];
    G --> H;
    H --> I{是否遍历完所有节点对};
    I -- 否 --> D;
    I -- 是 --> J[输出邻接矩阵];

4. 确定“放松”费用收取对象

4.1 分析邻接矩阵

我们已经得到了邻接矩阵,接下来要根据这个矩阵确定离所有儿童最远的乘客。假设我们知道儿童所在的座位对应的节点,就可以通过比较其他节点到这些儿童节点的距离来找出最远的乘客。

4.2 实现查找最远乘客的代码

public class RelaxationFeeFinder
{
    public static Node FindFarthestPassenger(DoublyLinkedList list, int[,] matrix, List<Node> childNodes)
    {
        int maxDistance = -1;
        Node farthestPassenger = null;

        foreach (Node passenger in list.Nodes)
        {
            if (childNodes.Contains(passenger))
            {
                continue; // 跳过儿童节点
            }

            int minDistanceToChildren = int.MaxValue;
            foreach (Node child in childNodes)
            {
                int indexOfPassenger = list.Nodes.IndexOf(passenger);
                int indexOfChild = list.Nodes.IndexOf(child);
                int distance = matrix[indexOfPassenger, indexOfChild];
                if (distance < minDistanceToChildren)
                {
                    minDistanceToChildren = distance;
                }
            }

            if (minDistanceToChildren > maxDistance)
            {
                maxDistance = minDistanceToChildren;
                farthestPassenger = passenger;
            }
        }

        return farthestPassenger;
    }
}

4.3 测试查找最远乘客

static void Main(string[] args)
{
    DoublyLinkedList list = new DoublyLinkedList();
    list.AddNode("Scrooge", "1A");
    list.AddNode("Colin", "1B");
    list.AddNode("Harry", "1C");
    list.AddNode("Olivia", "1D");
    list.AddNode("Ziya", "1E");
    list.AddNode("Blair", "1F");
    list.AddNode("Bubba", "2A");

    int[,] matrix = AdjacencyMatrixCalculator.CalculateAdjacencyMatrix(list);

    // 假设 Harry 和 Olivia 是儿童
    List<Node> childNodes = new List<Node> { list.Nodes[2], list.Nodes[3] };

    Node farthestPassenger = RelaxationFeeFinder.FindFarthestPassenger(list, matrix, childNodes);

    if (farthestPassenger != null)
    {
        Console.WriteLine($"收取“放松”费用的乘客是: {farthestPassenger.Name},座位号: {farthestPassenger.Seat}");
    }
    else
    {
        Console.WriteLine("未找到合适的乘客。");
    }
}

4.4 总结

通过上述代码,我们实现了根据邻接矩阵和儿童节点信息查找最远乘客的功能。以下是查找最远乘客的步骤总结:
1. 遍历所有非儿童乘客节点。
2. 对于每个非儿童乘客节点,计算其到所有儿童节点的最小距离。
3. 比较这些最小距离,找出最大的最小距离对应的乘客节点。

以下是查找最远乘客的流程图:

graph TD;
    A[创建双向链表、计算邻接矩阵并确定儿童节点] --> B[遍历非儿童乘客节点];
    B --> C[计算到所有儿童节点的最小距离];
    C --> D{是否为最大最小距离};
    D -- 是 --> E[更新最大最小距离和最远乘客];
    D -- 否 --> F[继续遍历];
    E --> F;
    F --> G{是否遍历完所有非儿童乘客节点};
    G -- 否 --> B;
    G -- 是 --> H[输出最远乘客信息];

综上所述,我们通过一系列步骤,从读取乘客姓名、优化显示效果,到构建双向链表、计算邻接矩阵,最终确定了收取“放松”费用的乘客。这些技术和方法在实际应用中具有一定的参考价值,能够帮助我们解决类似的问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值