算法与程序课程设计——观光铁路

观光铁路

一、任务

跳蚤国正在大力发展旅游业,每个城市都被打造成了旅游景点。

许多跳蚤想去其他城市旅游,但是由于跳得比较慢,它们的愿望难以实现。这时,小C听说有一种叫做火车的交通工具,在铁路上跑得很快,便抓住了商机,创立了一家铁路公司,向跳蚤国王请示在每两个城市之间都修建铁路。

然而,由于小C不会扳道岔,火车到一个城市以后只能保证不原路返回,而会随机等概率地驶向与这个城市有铁路连接的另外一个城市。

跳蚤国王向广大居民征求意见,结果跳蚤们不太满意,因为这样修建铁路以后有可能只游览了3个城市(含出发的城市)以后就回来了,它们希望能多游览几个城市。于是跳蚤国王要求小C提供一个方案,使得每只跳蚤坐上火车后能多游览几个城市才回来。

小C提供了一种方案给跳蚤国王。跳蚤国王想知道这个方案中每个城市的居民旅游的期望时间(设火车经过每段铁路的时间都为1),请你来帮跳蚤国王。

【输入格式】

输入的第一行包含两个正整数n、m,其中n表示城市的数量,m表示方案中的铁路条数。

接下来m行,每行包含两个正整数u、v,表示方案中城市u和城市v之间有一条铁路。

保证方案中无重边无自环,每两个城市之间都能经过铁路直接或间接到达,且火车由任意一条铁路到任意一个城市以后一定有路可走。

【输出格式】

输出n行,第i行包含一个实数ti,表示方案中城市i的居民旅游的期望时间。你应当输出足够多的小数位数,以保证输出的值和真实值之间的绝对或相对误差不超过1e-9。

二、要求

对于10%的测试点,n <= 10;
对于20%的测试点,n <= 12;
对于50%的测试点,n <= 16;
对于70%的测试点,n <= 19;
对于100%的测试点,4 <= k <= n <= 21,1 <= u, v <= n。数据有梯度。
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms

摘要:

本算法与程序课程设计通过图论的方法解决了一个实际问题------计算铁路网络中每个城市居民旅游的期望时间。首先,我们根据城市的铁路连接构建了一个有向图模型,并计算了每个城市的出度,这表示了从一个城市出发到其他城市的直接路径数量。通过分析火车运行的规则------即到达一个城市后随机等概率地向任一相连的城市移动且不返回原路。我们为每个城市建立了一个线性方程,表达了基于相邻城市的期望时间计算当前城市的期望时间的关系。这些方程共同形成了一个线性方程组,其解即为各城市居民旅游的期望时间。最终,我们以高精度格式输出了每个城市的期望旅游时间,确保结果的高准确性。此设计不仅展示了复杂系统建模的能力,也体现了算法在解决现实世界问题中的应用价值。

**关键字:**图论;系统建模;铁路网络;期望旅游时间

三、系统方案

1、任务要求及算法分析

任务要求:
  1. 图构建:根据提供的城市连接数据,构建一个有向图,以模拟铁路网络。每个节点代表一个城市,每个有向边代表两个城市间的铁路连接。
  2. 出度计算:对于图中的每个节点,计算其出度,即从该节点出发的边的数量。这表示从一个城市可以直达的其他城市的数量。
  3. 方程建立:根据火车运行规则,为网络中的每个城市建立一个方程,表达该城市的平均旅游时间与其邻居城市平均旅游时间的关系。确保考虑火车不会沿原路返回的条件。
  4. 求解方程组:使用适当的数学方法,求解由所有城市方程构成的线性方程组。确保计算结果满足精度要求。
  5. 结果输出:将每个城市的期望旅游时间以指定的格式输出。
解题思路:

对于出发点城市1来说,出发到每个子节点的概率之和(出度)与父节点到本节点的概率之和

(入度)相等,且为1。也就是说,乘客一定会回到起点。

对于概率p1 , p2 ... ... pn 与相对应的值x1 , x2 ... ... xn

它们的平均期望应该是

这个平均期望对应的概率是

对于某个节点K

到达K的平均期望就是**

到达K的概率就是p

算法分析:

时间复杂度: 主要的时间消耗主要体现在两个循环结构的执行上:第一个循环结构用于读取输入数据,并统计每个节点的度数。这个循环结构的时间复杂度为O(m),因为需要遍历所有的边。第二个循环结构用于计算每个节点的平均度数,并输出结果。这个循环结构的时间复杂度为O(n),因为需要遍历所有的节点。时间复杂度为O(m+n)。

空间复杂度: 空间复杂度是O(n),其中n表示节点数。因为代码中使用了一个长度为MAX的数组num来存储每个节点的度数,而节点数最多为n。

数值稳定性: 在计算过程中需注意数值的稳定性和精度问题。

2、 方案比较与选择

  1. 方案一:使用 SPFA 算法
    • 优点:时间复杂度较低,适用于大规模图的处理。
    • 缺点:不能保证得到全局最优解,可能产生冗余操作。
  2. 方案二:使用 Dijkstra 算法
    • 优点:能够保证得到全局最优解,适用于无负权边的图。
    • 缺点:时间复杂度较高,不适用于大规模图的处理。
  3. 方案三:通过输入输出案例找规律
    • 优点:代码简短,易于理解。
    • 缺点:不清楚通过规律算时间期望的原因。

Dijkstra算法与SPFA****算法比较

1、负权边处理

  1. Dijkstra 算法 :不能处理带负权的边。
  2. SPFA 算法 :可以处理带负权的边。
    2、 算法原理
  3. Dijkstra 算法 :使用优先队列(最小堆)来选择下一个要处理的节点,这样可以保证每次选择的都是当前已知最短路径的节点,从而逐步扩展已确定最短路径的节点集合。
  4. SPFA 算法 :使用队列这一数据结构,但其核心在于边的遍历和更新。当一个节点的距离估计发生变化时,该节点会重新入队,以便于更新所有相邻节点的距离估计。
    3、 时间复杂度
  5. Dijkstra 算法 :通常情况下时间复杂度为 O(| V|log|V | + |E|) ,其中 |V| 是节点数量, |E| 是边的数量。每处理一个节点,都要在优先队列中进行插入和删除操作。
  6. SPFA 算法 :在最坏情况下时间复杂度为 O(|V||E|) ,但实际表现通常好于这个界限,特别是在稀疏图或者边权为整数的情况下。
代码实现:
cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <iomanip>
using namespace std;

const int INF = 1e9;

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> edges(m, vector<int>(2));
    vector<vector<int>> dist(n, vector<int>(n, INF));
    for (int i = 0; i < m; ++i) {
        cin >> edges[i][0] >> edges[i][1];
        dist[edges[i][0] - 1][edges[i][1] - 1] = 1;
        dist[edges[i][1] - 1][edges[i][0] - 1] = 1;
    }
    vector<int> in_degree(n, 0);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (dist[i][j] != INF) {
                in_degree[j]++;
            }
        }
    }
    queue<int> q;
    for (int i = 0; i < n; ++i) {
        if (in_degree[i] == 0) {
            q.push(i);
        }
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int v = 0; v < n; ++v) {
            if (dist[u][v] != INF) {
                dist[u][v] = min(dist[u][v], dist[u][u] + dist[u][v]);
                in_degree[v]--;
                if (in_degree[v] == 0) {
                    q.push(v);
                }
            }
        }
    }
    for (int i = 0; i < n; ++i) {
        double sum = 0;
        for (int j = 0; j < n; ++j) {
            if (dist[i][j] != INF) {
                sum += dist[i][j];
            }
        }
        cout << fixed << setprecision(10) << sum / (n - 1) << endl;
    }
    return 0;
}
发现规律:

四、算法实现

1、 算法流程设计

  1. 初始化一个长度为MAX的整型数组num,用于存储每个节点的度数。
  2. 从标准输入读取两个整数n和m,分别表示节点的数量和边的数量。
  3. 使用一个for循环,循环m次,每次循环中: a. 从标准输入读取两个整数u和v,表示一条边连接的两个节点。 b. 将num[u]和num[v]的值加1,表示这两个节点的度数都增加了1。
  4. 使用另一个for循环,循环n次,每次循环中: a. 计算当前节点的度数概率p,公式为:p = m * 2.0 / num[i]。 b. 使用printf函数,以保留12位小数的格式输出p。
  5. 返回0,表示程序正常结束。

2、 算法模型构建

根据提供的城市连接数据,构建一个有向图,以模拟铁路网络。每个节点代表一个城市,每个有向边代表两个城市间的铁路连接。使用图论中的最短路径算法来计算从任一城市出发到达其他城市的最短路径长度。

完整代码:

cpp 复制代码
#include<iostream>
using namespace std;
const int MAX = 100;
int num[MAX];

int main()
{
	int n, m,i;
	cin >> n >> m;
	for (i = 1; i <= m; i++)
	{
		int u, v;
		cin >> u >> v;
		num[u]++;
		num[v]++;
	}
	for (i = 1; i <= n; i++)
	{
		double p = m * 2.0 / num[i];
		printf("%.12f\n", p);
	}
	return 0;
}

时间复杂度: 主要的时间消耗主要体现在两个循环结构的执行上:第一个循环结构用于读取输入数据,并统计每个节点的度数。这个循环结构的时间复杂度为O(m),因为需要遍历所有的边。第二个循环结构用于计算每个节点的平均度数,并输出结果。这个循环结构的时间复杂度为O(n),因为需要遍历所有的节点。

时间复杂度:****O(m+n)

空间复杂度O(n),其中n表示节点数。

五、 工程管理及经济学分析

1、 算法实现所涉及的工程管理

项目管理
  • 项目规划:制定算法实现铁路建设计划,包括城市间铁路的数量、长度和连接方式。
团队协作
  • 沟通协调:建立有效的沟通机制,确保团队成员之间的信息流通。
  • 任务分配:根据团队成员的能力和特长,合理分配任务,提高工作效率。
  • 冲突解决:及时解决团队内部的矛盾和冲突,维护良好的团队氛围。
进度管理
  • 进度计划:制定详细的进度计划,确保项目按时完成。
  • 进度跟踪:实时跟踪工程进度,及时发现并解决影响进度的问题。
  • 进度调整:根据实际情况,适时调整工程进度计划,确保项目顺利进行。

注意事项:

  • 数据准确性:输入数据的准确性至关重要,任何错误都可能导致算法结果的偏差。
  • 算法效率:考虑到大规模的城市数量,算法的效率必须足够高。
  • 结果精确度:输出的结果应保证足够的小数位数,以满足误差不超过1e-9的要求。

2、算法实现中体现的经济学分析

  1. 成本效益分析:在规划铁路网络时,需要对比建设和维护铁路的成本与通过提升旅游效率带来的收益。包括直接收益(如票价收入)和间接收益(如增加的就业机会、促进当地经济发展等)。
  2. 需求预测:对旅游需求进行预测,了解不同城市间的旅客流量,以及旅游高峰期和淡季的变化。有助于设计出能够满足不同时间段需求的铁路服务。
  3. 外部性:考虑铁路建设和运营可能带来的正面或负面外部效应,例如减少交通拥堵、环境污染,或者提高地区间的经济联系。

六、总结

1、过程难点及反思

  1. 理解问题需求
    难点:在课程设计的初期,难以完全理解问题背景和需求。
    反思:应该采用多种方式(如流程图)确保对需求有清晰的认识。

  2. 选择合适的数据结构和算法
    难点:面对复杂问题时,确定最合适的数据结构和算法是一大挑战,尤其是在性能和实现复杂度之间需要权衡。
    反思:应通过学习,加强对不同数据结构和算法性能的了解。

  3. 编码实现
    难点:将设计好的算法转化为实际代码时,会遇到语法错误、逻辑错误等问题。
    反思:编写单元测试来验证每个函数的正确性,逐步集成各个模块。

2、心得与体会

**1.**理解问题的复杂性
深度分析 :在课程设计之初,我意识到了深入分析问题的重要性。仅仅表面上的理解会导致解决方案的偏差。
层层递进 :通过逐步分解问题,我更加清晰地看到了问题的本质,这有助于构建更加精确的算法模型。

**2.**数据结构与算法的选择
权衡比较 :选择合适的数据结构和算法是关键步骤,我学会了如何根据问题特点进行权衡比较。
持续学习 :这一过程也让我明白了学习新算法和数据结构是一个不断的、终身的过程。

**3.**编码实现的挑战
细节关注 :编码过程中,我更加注意到了细节对程序性能和稳定性的影响。
重构优化 :不断重构和优化代码,我体会到了简洁代码带来的高效维护和易于理解的好处。

相关推荐
唐诺3 小时前
几种广泛使用的 C++ 编译器
c++·编译器
XH华3 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生4 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_4 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子4 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
冷眼看人间恩怨4 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
菜鸡中的奋斗鸡→挣扎鸡4 小时前
滑动窗口 + 算法复习
数据结构·算法
红龙创客4 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码5 小时前
Cmd命令大全(万字详细版)
python·算法·小程序