
1. 項目背景與問題拆解當數學建模遇上“送餐危機”去年帶學生參加數維杯A題“外賣騎手的送餐危機”一出來我們團隊就樂了。這題太“接地氣”了簡直就是把每天發生在你我身邊的外賣配送問題抽象成了一個經典的運籌優化模型。但樂完就發現題目背后藏著不少“坑”。所謂的“送餐危機”核心矛盾是什么是騎手在有限時間內面對動態涌入的訂單、復雜的路網、不確定的交通狀況和嚴格的平臺規則時如何規劃路徑才能最大化送達效率、最小化超時和成本。這聽起來像是一個帶時間窗的車輛路徑問題VRPTW但實際建模時你會發現它比教科書上的VRPTW復雜得多——訂單不是一次性給全的路況是實時變化的騎手還可能同時接多個順路單。網上能找到的很多優秀論文或開源代碼比如針對“蟻群算法解決連續問題”、“全局搜索增強的改進鯨魚算法”或者“AGV的A*算法”都為我們提供了寶貴的思路工具箱。但直接套用往往水土不服。比如你用標準的遺傳算法去解可能很快得到一個“理論上”的優化路徑但這個路徑是否考慮了非機動車道的限制是否考慮了寫字樓等電梯的等待時間是否考慮了午高峰餐館出餐慢的隨機延遲這些細節才是把論文從“紙上談兵”變成“真槍實彈”的關鍵。所以這篇內容不是簡單地復現我們當時的論文和程序而是想以一個過來人的身份拆解我們面對這道題時的完整思考過程、模型建立時的多次迭代、算法選型時的權衡對比以及編程實現中那些教科書不會寫的“騷操作”和“踩坑實錄”。無論你是正在備戰數維杯、國賽、美賽的同學還是對路徑優化、數學建模感興趣的朋友希望這些從實戰中沉淀下來的經驗能給你帶來一些不一樣的啟發。2. 模型構建從現實問題到數學語言的精確翻譯拿到題目第一步不是急著找算法而是把模糊的“送餐危機”翻譯成清晰的數學問題。我們團隊當時花了將近半天時間來定義邊界、做出合理假設并確定優化目標。這一步走穩了后面的算法和編程才有意義。2.1 核心要素定義與假設我們首先明確了模型中的幾個核心實體和它們的屬性騎手定義為移動的配送單元。每個騎手有初始位置通常為配送站或上一個送達點、載貨容量能同時攜帶的訂單數本題中通常設為有限值如3-5單、移動速度一個變量受路況影響。訂單每個訂單包含商家位置、顧客位置、期望送達時間窗通常是一個時間點如“30分鐘內送達”我們將其處理為一個軟時間窗允許超時但需懲罰、預計備餐時間、實際重量/體積用于容量約束。路網我們將配送區域抽象為一個帶權圖。節點包括所有商家、所有顧客、配送站、重要的道路交叉口。邊的權重不是簡單的歐氏距離而是預估騎行時間。這個時間是動態的與時間段平峰/高峰、天氣等因素有關。基于這些實體我們做出了幾個關鍵假設以平衡模型的復雜性與可求解性假設1訂單已知性我們采用“靜態-動態”混合策略。在每一個調度周期如每5分鐘將已知的、尚未被分配的訂單視為靜態輸入進行一波路徑規劃。這避免了完全動態規劃的極端復雜性。假設2時間離散化將整個工作時間如午高峰11:00-13:00離散化為以分鐘為單位的時間片便于在算法中計算和追蹤時間。假設3速度簡化騎手速度不是一個恒定值。我們將其建模為分段函數在商家/顧客點停留時速度為0在道路騎行時根據道路等級主干道、次干道、小巷賦予一個平均速度并在高峰時段乘以一個擁堵系數如0.7。假設4等待與懲罰騎手到達商家后若餐未備好則需等待。超時送達會產生懲罰成本懲罰函數我們設計為指數形式超時越久懲罰成本急劇上升這比線性懲罰更能體現平臺對用戶體驗的重視。注意這些假設需要在論文中明確寫出并論證其合理性。例如解釋為什么采用靜態-動態混合策略是因為完全實時調度對數據通信和計算能力要求極高而周期性批量處理是工業界的常見折中方案。2.2 多目標優化模型的建立送餐問題天然是一個多目標優化問題。平臺關心效率送得多、成本跑得省和體驗不超時。我們將其整合為一個加權單目標模型便于求解。決策變量x_{ijk} 0-1變量表示騎手k是否從節點i商家或顧客前往節點j。目標函數最小化Minimize Z w1 * T_total w2 * C_distance w3 * P_delay其中T_total所有訂單的總配送完成時間makespan。最小化它意味著提升整體吞吐效率。C_distance所有騎手行駛的總距離或總時間。最小化它意味著降低油耗/電耗和騎手勞動強度。P_delay所有訂單的超時懲罰總和。計算公式為 Σ max(0, t_actual - t_due)^2 * penalty_rate。使用平方項是為了讓算法極度厭惡嚴重超時。約束條件流量平衡每個騎手從配送站出發最終返回配送站或結束于最后一個顧客點。訂單服務唯一性每個訂單必須被且僅被一個騎手服務一次包括取餐和送餐。時間窗約束騎手到達每個顧客點的時間t_arrive應盡量在期望時間t_due之前否則計入懲罰。容量約束騎手在任意時刻攜帶的訂單數不能超過其最大容量。取送順序約束對于任何一個訂單騎手必須先訪問商家節點i才能訪問對應的顧客節點j。即t_arrive_i t_arrive_j。時間連續性約束騎手到達下一個節點j的時間等于離開上一個節點i的時間 在i點的服務時間取餐或送餐 從i到j的行程時間。這個模型本質上是一個帶容量約束、軟時間窗、同時取送貨的車輛路徑問題VRPSPDTW并且是動態分批輸入的。其復雜度是NP-Hard的對于稍大規模的問題如50個訂單10個騎手精確算法如分支定界在有限比賽時間內基本無法求得最優解因此必須依賴啟發式或元啟發式算法。3. 算法選型與設計在“最優”與“可行”之間尋找平衡明確了模型接下來就是選擇“武器”。我們調研了熱詞中提到的多種算法并進行了組合與改進。3.1 核心算法框架大規模鄰域搜索LNS為主干我們最終沒有采用單一的蟻群、遺傳或鯨魚算法而是選擇了大規模鄰域搜索LNS作為主框架。原因在于VRP問題中解的優劣往往取決于幾個關鍵的局部結構。LNS通過“破壞”和“修復”兩個階段迭代改進解非常靈活。破壞Destroy隨機從當前解中移除一定比例如20%-30%的訂單。我們設計了多種破壞算子隨機移除簡單隨機選擇訂單移除。最差成本移除計算每個訂單對總成本的邊際貢獻移除貢獻最負即移除后成本下降最多的訂單。這能引導搜索跳出局部最優。時間窗緊迫度移除優先移除那些時間窗最緊迫即將超時的訂單為后續重新安排留出空間。修復Repair將移除的訂單重新插入到當前部分解中。這里我們采用了貪婪插入和后悔值插入兩種策略。貪婪插入對于每個待插入訂單遍歷所有騎手路徑的所有可能插入位置選擇使目標函數增加最小的位置進行插入。后悔值插入這是關鍵技巧。不是看最優插入位置的成本而是計算每個訂單的“第二好”插入位置與“最好”插入位置的成本差即后悔值。優先插入后悔值最大的訂單因為如果現在不把它插到最好的位置后續可能被迫插到更差的位置代價更高。LNS框架的優勢在于破壞和修復算子可以像樂高積木一樣自由組合和擴展方便我們融入其他算法的思想。3.2 嵌入智能優化算法進行初始解生成與局部增強單純的LNS需要一個不錯的初始解并且其搜索能力有時會陷入平臺期。我們引入了熱詞中提到的兩種算法進行增強初始解生成改進的鯨魚優化算法WOA標準的WOA模擬鯨魚氣泡網捕食行為在連續空間搜索能力強。但我們的問題解是離散的路徑序列。我們對其進行了離散化改造編碼采用“顧客-騎手”關聯編碼。一個鯨魚個體解表示為一個列表列表長度等于訂單數每個位置的值表示負責該訂單的騎手編號。至于訂單在騎手路徑中的具體順序則通過一個簡單的最近鄰插入法根據這個分配關系來生成。位置更新離散化WOA中鯨魚的位置更新公式會產生連續值。我們通過一個隨機鍵Random Key策略將其離散化。例如將連續的位置值通過排序映射到騎手編號的排列上。作用改進的離散WOA被用來快速生成一批多樣化的初始解種群然后從中選擇最好的一個作為LNS的起點。這比完全隨機生成初始解質量高得多。局部搜索變鄰域搜索VNS作為修復后的增強在LNS的修復階段得到一個完整新解后我們并不直接接受它而是以這個新解為起點進行一輪快速的變鄰域搜索VNS在局部尋找更優解。鄰域結構我們設計了多種小規模鄰域操作如2-opt反轉路徑中的一段。Relocate將一個訂單從一條路徑的一個位置移到同一條或另一條路徑的另一個位置。Exchange交換兩條路徑中的兩個訂單。搜索策略依次嘗試這些鄰域結構一旦某個結構找到了改進解就移動到新解并從頭開始如果所有結構都嘗試完仍無改進則跳出。這樣我們的算法就形成了一個“改進WOA生成初始解 → LNS主循環破壞修復→ 內嵌VNS局部爬坡”的三層混合架構。LNS負責大范圍的“探索”VNS負責精細的“挖掘”WOA則提供了高質量的起點。3.3 動態事件的處理機制題目隱含了動態性新訂單實時涌入。我們的處理方法是周期性重優化。系統內部維護一個時鐘和事件隊列。每過固定的時間間隔如5分鐘或當新訂單積累到一定數量時觸發一次重優化。重優化時輸入包括所有尚未完成的訂單包括正在配送途中的、所有騎手的當前位置和狀態攜帶哪些訂單、當前路徑。將騎手的當前位置視為新的“虛擬配送站”將已攜帶但未送達的訂單視為必須服務的點然后調用上述混合算法重新規劃所有騎手從當前時刻往后的路徑。將新的路徑下發給騎手在模型中模擬。這種方法在比賽中是可行的它平衡了實時性和最優性。在實際系統中這可能就是后臺調度引擎每隔幾十秒到幾分鐘執行一次的邏輯。4. 編程實現與仿真環境搭建模型和算法是大腦程序就是手腳。我們用Python來實現因為其庫豐富適合快速原型開發。4.1 數據結構設計清晰的數據結構是復雜程序的基礎。class Order: def __init__(self, id, merchant_loc, customer_loc, prep_time, ready_time, due_time, weight): self.id id self.merchant merchant_loc # (x, y) self.customer customer_loc # (x, y) self.prep_time prep_time # 備餐時間分鐘 self.ready_time ready_time # 預計備餐完成時間 self.due_time due_time # 期望送達時間 self.weight weight class Rider: def __init__(self, id, start_loc, capacity, speed): self.id id self.location start_loc self.capacity capacity self.speed speed # 米/分鐘 self.route [] # 路徑列表元素為 (node_type, node_id, arrive_time, depart_time) self.load 0 # 當前負載 self.current_orders [] # 當前攜帶的訂單ID class Solution: def __init__(self): self.rider_assignments {} # rider_id - list of order_ids (按取送順序) self.total_cost float(inf) # 可以緩存一些中間計算結果如時間矩陣、距離矩陣4.2 關鍵模塊實現1. 時間矩陣計算模塊這是整個仿真的基石。我們不能每次計算兩點間時間都去調用路徑規劃API比賽中也不允許。我們采用了簡化方法預先根據路網節點商家、顧客點的經緯度計算直線距離。根據道路類型和時段賦予一個速度折減系數和繞路系數。例如直線距離乘以1.3作為實際騎行距離再根據高峰/平峰除以不同的速度得到行程時間。將結果存儲在一個二維數組時間矩陣中并假設在同一個調度周期內是固定的。2. 目標函數評估模塊這個函數會被調用成千上萬次必須高效。def evaluate_solution(solution, orders, riders, time_matrix, current_time): total_cost 0.0 total_distance 0.0 total_delay 0.0 for rider_id, order_seq in solution.rider_assignments.items(): rider riders[rider_id] current_loc rider.location current_time_for_rider current_time current_load 0 for order_id in order_seq: order orders[order_id] # 去商家 travel_time_to_merchant time_matrix[current_loc][order.merchant] arrive_at_merchant current_time_for_rider travel_time_to_merchant # 等待取餐如果提前到了 wait_at_merchant max(0, order.ready_time - arrive_at_merchant) depart_from_merchant arrive_at_merchant wait_at_merchant 1 # 1分鐘取餐操作 current_load order.weight # 檢查容量約束 if current_load rider.capacity: return float(inf) # 違反硬約束返回無窮大成本 # 去顧客 travel_time_to_customer time_matrix[order.merchant][order.customer] arrive_at_customer depart_from_merchant travel_time_to_customer # 計算延遲 delay max(0, arrive_at_customer - order.due_time) total_delay delay ** 2 # 平方懲罰 depart_from_customer arrive_at_customer 1 # 1分鐘送餐操作 current_load - order.weight total_distance (travel_time_to_merchant travel_time_to_customer) * rider.speed current_loc order.customer current_time_for_rider depart_from_customer # 騎手最后返回配送站可選 # ... 計算返回行程并加入總距離 total_cost w1 * (current_time_for_rider - current_time) w2 * total_distance w3 * total_delay return total_cost實操心得評估函數中對硬約束如容量超限的處理直接返回一個極大的懲罰值如float(inf)可以有效地引導搜索算法自動避開不可行解區域比用復雜的約束處理邏輯更簡潔高效。3. LNS破壞與修復算子實現以“最差成本移除”和“后悔值插入”為例。def worst_cost_removal(current_solution, orders, riders, num_remove): 移除對當前解成本貢獻最負的num_remove個訂單 order_marginal_cost {} for order_id, order in orders.items(): if order_id in current_solution.assigned_orders: # 計算移除該訂單前后的成本差 cost_with evaluate_solution(current_solution, ...) # 臨時創建一個移除該訂單后的新解需要深拷貝并調整路徑 temp_solution remove_order_from_solution(current_solution, order_id) cost_without evaluate_solution(temp_solution, ...) marginal_cost cost_without - cost_with # 如果為負說明移除它成本降低 order_marginal_cost[order_id] marginal_cost # 按邊際成本排序從最負到最正選擇最負的前num_remove個訂單移除 orders_to_remove sorted(order_marginal_cost, keyorder_marginal_cost.get)[:num_remove] return orders_to_remove def regret_insertion(partial_solution, orders_to_insert, orders, riders): 使用后悔值啟發式插入訂單 uninserted orders_to_insert.copy() while uninserted: regrets {} for order_id in uninserted: best_cost float(inf) second_best_cost float(inf) best_position None # 遍歷所有騎手和所有可能插入位置考慮取送配對 for rider_id, rider in riders.items(): # 生成該訂單所有可能的插入位置在現有路徑中插入商家點和顧客點 possible_positions generate_insertion_positions(partial_solution, rider_id, order_id) for pos in possible_positions: temp_solution insert_order_at_position(partial_solution, rider_id, order_id, pos) cost evaluate_solution(temp_solution, ...) if cost best_cost: second_best_cost best_cost best_cost cost best_position (rider_id, pos) elif cost second_best_cost: second_best_cost cost # 計算后悔值 regret second_best_cost - best_cost regrets[order_id] (regret, best_position) # 選擇后悔值最大的訂單進行插入 order_to_insert max(regrets, keylambda k: regrets[k][0]) rider_id, pos regrets[order_to_insert][1] partial_solution insert_order_at_position(partial_solution, rider_id, order_to_insert, pos) uninserted.remove(order_to_insert) return partial_solution4.3 仿真循環與可視化我們搭建了一個簡單的離散事件仿真循環來模擬時間推進和新訂單到達。import matplotlib.pyplot as plt def simulation(start_time, end_time, order_stream, riders): current_time start_time current_orders [] solution initial_solution(riders) # 初始為空解 event_log [] while current_time end_time: # 1. 接收新訂單 new_orders get_new_orders(order_stream, current_time) current_orders.extend(new_orders) # 2. 判斷是否觸發重優化例如每5分鐘或新訂單超過5個 if should_reoptimize(current_time, len(new_orders)): # 3. 調用混合優化算法輸入當前騎手狀態和所有未完成訂單 new_solution hybrid_optimization_algorithm(current_orders, riders, current_time) if new_solution.total_cost solution.total_cost: solution new_solution # 4. 更新騎手路徑在仿真中就是更新route列表 dispatch_solution_to_riders(solution, riders) # 5. 推進時間模擬騎手移動和訂單完成 current_time TIME_STEP update_rider_positions(riders, TIME_STEP) completed check_order_completion(riders, current_time) for order_id in completed: remove_order_from_current_list(current_orders, order_id) event_log.append((current_time, delivered, order_id)) # 6. 記錄數據用于分析 record_metrics(current_time, riders, current_orders) # 仿真結束輸出統計結果和可視化 print_statistics(event_log) plot_rider_routes(riders, orders)可視化部分我們用matplotlib繪制了騎手路徑的動畫直觀展示隨著時間推移騎手們如何穿梭取送餐。靜態圖則可以展示最終所有騎手的路徑和訂單分布以及目標函數收斂曲線。5. 參數調優、結果分析與論文寫作要點算法跑起來只是第一步調參和結果分析才是拉開差距的地方。5.1 關鍵參數敏感性分析我們的混合算法中有多個參數需要調整權重系數 (w1, w2, w3)這直接決定了優化導向。我們通過網格搜索并觀察不同權重下解的帕累托前沿Pareto Front最終選擇了一組在總時長、總距離和超時率上相對均衡的權重例如 0.5, 0.2, 0.3。LNS破壞比例破壞比例太大搜索隨機性強收斂慢太小跳出局部最優能力弱。我們通過實驗發現在20%-35%之間效果較好并采用了自適應策略如果連續多次迭代沒有改進則增大破壞比例。改進WOA的參數種群大小、迭代次數。由于WOA只用于生成初始解我們不需要它完全收斂因此種群大小設為20-50迭代次數50-100次即可重在多樣性。VNS的鄰域搜索深度我們為每個鄰域操作設置了最大嘗試次數如100次避免在局部搜索中花費過多時間。踩坑實錄最初我們沒做參數敏感性分析隨便設了一組值。結果算法要么瘋狂追求最短路徑導致嚴重超時要么為了不超時讓騎手跑了很多冤枉路。后來我們固定其他參數每次只調一個觀察目標函數各分量的變化并繪制了趨勢圖才找到了相對合理的參數組合。這個過程在論文中可以作為“模型穩健性分析”的一部分來寫非常加分。5.2 結果對比與有效性驗證為了證明我們模型和算法的有效性我們設計了對比實驗基準策略最近鄰策略騎手總是前往距離當前位置最近的未完成訂單點。先到先得策略訂單按產生時間分配給最近的空閑騎手騎手按訂單順序執行。消融實驗僅LNS不使用WOA生成初始解也不在修復后使用VNS。LNS VNS使用隨機初始解但修復后使用VNS。完整混合算法我們的最終方案。評價指標除了總成本Z我們還單獨統計了訂單平均送達時間、騎手總行駛里程、訂單超時率、超時訂單平均超時時長。實驗結果用表格呈現最為清晰策略總成本 (Z)平均送達時間(分鐘)總行駛里程(km)超時率算法運行時間(秒)最近鄰1520.538.2145.325%1先到先得1380.735.8132.118%1僅LNS980.328.5108.78%45LNSVNS865.426.199.45%68混合算法(本文)795.824.795.23%82從表格可以明顯看出我們的混合算法在各項配送效率指標上均顯著優于基準策略。消融實驗則證明了WOA提供優質初始解和VNS進行局部增強的有效性雖然增加了些許計算時間但帶來的效益提升是值得的。5.3 論文寫作的核心技巧數學建模競賽論文是最終交付物。編程和求解只是過程。摘要用一段話概括問題、你的方法、模型、算法和核心結論。務必包含關鍵數據如“相比基準策略總成本降低了42%超時率降低了22個百分點”。問題重述與分析不要照抄題目要用自己的話分析問題的本質、難點和關鍵約束。畫出概念圖如騎手、訂單、路網的關系圖。模型假設清晰列出并說明為什么合理。這是體現你思考深度的部分。模型建立公式要完整、規范。對每個符號進行說明。目標函數和約束條件要分點闡述。算法設計這是亮點。用流程圖可以手繪拍照展示算法框架。詳細說明LNS、改進WOA、VNS是如何結合在一起的。偽代碼不要太多挑核心的寫1-2個。仿真與結果分析展示參數設置。用表格和圖表折線圖、柱狀圖、路徑示意圖多維度呈現結果。分析要深入比如“超時率從25%降到3%主要得益于后悔值插入算法優先處理了時間窗緊迫的訂單”。模型評價與推廣客觀評價自己模型的優點如高效、靈活和缺點如對歷史數據依賴、未考慮極端天氣。提出改進方向如接入實時交通API引入機器學習預測備餐時間。說明模型可以推廣到其他即時配送場景如快遞、生鮮配送。附錄與代碼將核心代碼如評估函數、LNS主循環整理后放入附錄。代碼要簡潔有必要的注釋。最后在提交前一定要反復檢查論文的排版、圖表編號、公式編號、參考文獻引用。一篇排版精美、邏輯清晰、結果扎實的論文是獲得好名次的基石。我們的程序可能不是最快的算法也不是最前沿的但整個解決過程體現出的系統思維、對細節的考量以及清晰的表述才是數維杯這類競賽真正看重的。