[数据结构]图:邻接矩阵
目录前言数学建模实验设计实验数据算法设计思想C语言实现及关键注释结果输出算法时间复杂度分析1. 状态总数2. 每个状态的转移次数3. 总操作数4. 随规模增长的趋势前言还记得22年的华为杯数模研赛吗多维邻接矩阵无法解开。在此做一个总结吧。2022年“华为杯”中国研究生数学建模竞赛C题核心是设计一个**涂装-总装缓存调序区PBS的调度优化方案**汽车制造厂主要由焊装车间、涂装车间、总装车间构成。每个车间由于采用了混流生产方式有不同的生产偏好对不同类型的车身有一定的配置比例要求。由于车间偏好不同前一车间的出车序列可能对下一车间的生产效率等方面产生不利影响。特别是涂装车间与总装车间车身序列差异较大为此需要在这两个车间之间建立一个具有调序功能的缓存区通过将涂装车间的序列调整为合适的总装车间序列提高整条生产线的生产效率。此缓存区即汽车制造涂装-总装缓存调序区Painted Body Store PBS。PBS 区域主要由涂装-PBS 出车口、 PBS-总装接车口、进车道 1 至进车道 6、返回道、接车横移机及送车横移机组成。给定涂装-PBS 出车口处的车身序列要求在给定的约束条件下优化调度方案使得经 PBS 缓冲区重排序后的出车序列在给定的优化目标评价尺度下能够有较好的表现。相关概念具体描述如下车身本题目给出三项车身属性——车型、动力、驱动类型但结合问题约束及优化目标仅需考虑动力类型及驱动类型两项。进、出车序列进车序列即涂装-PBS 出车口出的车辆序列为已知条件车身与进车顺序一一对应因此可以将车身在进车序列中的排序作为其编号出车序列即经进车序列重排序后的序列应尽可能满足总装车间的需求。约束条件主要包括原文件给定的 12 条 PBS 约束其中包含接车横移机及送车横移机的接、送逻辑以及车身在进车道及行车道的行进逻辑等。优化目标主要包括四个方面出车序列动力类型及驱动类型尽量满足要求、返回道使用次数尽量少 PBS 调度总时长尽可能短权重依次降低。时间设定由原文件给出包括给出的车身在同一车道不同车位间的转移时间接车、送车横移机在不同车道处接送所用时间等。由以上时间数据结合文中给出的距离数据我们可以构造位置矩阵以描述各移动单位的位置关系便于得到所需的不同车身位置与时间的关系矩阵。原文链接【数模研赛】“华为杯”第十九届中国研究生数学建模竞赛C题分享——二问题重述_2022华为杯c题问题重述-CSDN博客数学建模数学建模过程略本文不关注文档写作。.......实验设计我们自行补全了一个具体的问题在一个有向路网中一辆小车从仓库出发需要遍历所有客户点和充电站共5个中间节点各一次最后返回仓库。小车有电池容量限制每段路消耗与距离相等的电量到达充电站可立即充满电。目标是找到满足电量约束的最短行驶路线即带充电约束的TSP问题。我们用C语言实现状态压缩动态规划求解并生成SVG图像显示路线。实验数据typedef struct VertexVertex0Axis((0,0)) Discription:”仓库起点/终点”Vertex1Axis((10,0))Discription:”客户A”Vertex2Axis((5,10))Discription:”客户B”Vertex3Axis((15,5))Discription:”客户C”Vertex4Axis((3,3)) Discription:”充电站S1”Vertex5Axis((12,8))Discription:”充电站S2”//如需增加节点数量15状态爆炸可改用启发式算法如遗传算法Adjacency Matrix(DN,unit: km)电池参数电池容量100 (kWh)每公里耗电1 kWh/km充电站节点4、节点5到达后电池立刻充满至100初始状态从仓库(0)出发电量100必须遍历中间节点 {1,2,3,4,5} 各一次最后返回仓库算法设计思想核心算法状态压缩DP 电量维度定义 dp[mask][last][batt] 表示已访问节点集合为 mask当前位于节点 last剩余电量为 batt 时的最短行驶距离。其中 mask 是一个5位二进制数节点1~5last 取值范围1~5batt 0~100。状态转移从 (mask, last, batt) 出发尝试前往未访问节点 next。消耗 cost dist[last][next]需满足 batt cost。到达 next 后新电量为 new_batt (is_charger(next) ? 100 : batt cost)。更新 dp[mask|bit(next)][next][new_batt] min(自身, dp[mask][last][batt] cost)。初始状态从仓库0直接到第一个节点 first消耗 cost dist[0][first]要求初始电量100≥cost新电量根据 first 是否为充电站决定。最终答案遍历所有 last 和 batt若 dp[全1][last][batt] 可达且 batt dist[last][0]则总距离为 dp dist[last][0]取最小值。路径回溯记录前驱 prev_mask[last][batt], prev_last[last][batt], prev_batt[last][batt] 以还原完整序列。程序输出控制台打印最优路径、每段路程、电量变化、总距离。生成 path.svg 矢量图包含节点、标号、路线箭头可用浏览器打开。C语言实现及关键注释include stdio.h include stdlib.h include string.h include limits.h include math.h define N_NODES 6 // 0仓库,1~5客户/充电站 define MID_START 1 // 第一个中间节点编号 define MID_END 5 // 最后一个中间节点编号 define MID_COUNT 5 // 中间节点个数 (1..5) define BATTERY_MAX 100 define INF 1000000 // 节点坐标 (用于绘图) const int coord_x[N_NODES] {0, 10, 5, 15, 3, 12}; const int coord_y[N_NODES] {0, 0, 10, 5, 3, 8}; // 有向距离矩阵 int dist[N_NODES][N_NODES] { {0, 10, 12, 16, 5, 14}, {10, 0, 8, 11, 7, 13}, {12, 7, 0, 9, 8, 6}, {15, 10, 9, 0, 13, 6}, {5, 6, 8, 12, 0, 4}, {13, 12, 7, 5, 4, 0} }; // 判断节点是否为充电站 int is_charger(int node) { return (node 4 || node 5); } // 将中间节点编号(1..5)转换为位掩码 (bit 0对应节点1) inline int bit(int node) { return 1 (node MID_START); } // 全局DP数组: dp[mask][last][batt] // 由于状态数: 2^5 5 101 325101 16160用short存距离最大距离1000 short dp[1MID_COUNT][MID_COUNT1][BATTERY_MAX1]; // 前驱记录 short prev_mask[1MID_COUNT][MID_COUNT1][BATTERY_MAX1]; char prev_last[1MID_COUNT][MID_COUNT1][BATTERY_MAX1]; char prev_batt[1MID_COUNT][MID_COUNT1][BATTERY_MAX1]; // 初始化dp为INF void init_dp() { int m, l, b; for (m 0; m (1MID_COUNT); m) for (l MID_START; l MID_END; l) for (b 0; b BATTERY_MAX; b) dp[m][l][b] INF; } int main() { init_dp(); // 初始状态: 从仓库出发到第一个节点 for (int first MID_START; first MID_END; first) { int cost dist[0][first]; if (cost BATTERY_MAX) { int batt is_charger(first) ? BATTERY_MAX : (BATTERY_MAX cost); int mask bit(first); if (cost dp[mask][first][batt]) { dp[mask][first][batt] cost; // 前驱: mask0, last1, battBATTERY_MAX (仓库) prev_mask[mask][first][batt] 0; prev_last[mask][first][batt] 1; // 表示来自仓库 prev_batt[mask][first][batt] BATTERY_MAX; } } } // DP 状态转移 int full_mask (1MID_COUNT) 1; for (int mask 1; mask full_mask; mask) { for (int last MID_START; last MID_END; last) { if (!(mask bit(last))) continue; // last必须在mask中 for (int batt 0; batt BATTERY_MAX; batt) { if (dp[mask][last][batt] INF) continue; // 尝试去下一个未访问节点 for (int nxt MID_START; nxt MID_END; nxt) { if (mask bit(nxt)) continue; int cost dist[last][nxt]; if (batt cost) continue; // 电量不足 int new_batt is_charger(nxt) ? BATTERY_MAX : (batt cost); int new_mask mask | bit(nxt); int new_dist dp[mask][last][batt] cost; if (new_dist dp[new_mask][nxt][new_batt]) { dp[new_mask][nxt][new_batt] new_dist; prev_mask[new_mask][nxt][new_batt] mask; prev_last[new_mask][nxt][new_batt] last; prev_batt[new_mask][nxt][new_batt] batt; } } } } } //剩余此处省略评论区获取结果输出算法时间复杂度分析下面分析所给C代码带充电约束的TSP状态压缩DP的时间复杂度代码主要耗时在DP状态转移部分。1. 状态总数中间节点数量MID_COUNT n 5访问状态掩码 mask\(2^n 32\) 种当前所在节点 last\(n 5\) 种仅中间节点节点1~5当前剩余电量 batt\(B_{\max} 1 101\) 种0~100因此总状态数\[ S 2^n \times n \times (B_{\max}1) 32 \times 5 \times 101 16160 \]2. 每个状态的转移次数对于每个状态 (mask, last, batt)需要枚举所有未被访问的下一个节点 nxt。最坏情况下未访问节点数为 \(n\)实际上随着 mask 增大而减少。平均约 \(n/2\)但上界取 \(n5\)。每次转移的操作计算新电量、新距离、比较更新均为 \(O(1)\)。3. 总操作数\[T S \times n 16160 \times 5 80800\]加上初始化的 \(S\) 次赋值init_dp 中循环 \(32\times5\times101\) 次\[T_{\text{init}} 16160\]总时间复杂度\[O(2^n \times n^2 \times B_{\max})\]代入 \(n5,\ B_{\max}100\) 得约 \(8\times 10^4\) 次操作常数极小可视为瞬时完成。4. 随规模增长的趋势若 \(n10\)\(2^{10}1024\)状态数 \(1024\times10\times101\approx 1.03\times10^6\)转移次数约 \(5.15\times10^6\)仍很快。若 \(n15\)\(2^{15}32768\)状态数 \(32768\times15\times101\approx 4.96\times10^7\)转移约 \(2.48\times10^8\)在C语言中可能需几秒。若 \(n20\)状态数将超过 \(2^{20}\times20\times101\approx 2.12\times10^9\)内存和时间均不可接受。因此该算法仅适用于节点数 ≤ 15的小规模问题。
