4、核心算法实现:基于库存的最优匹配算法、成本最优拆分策略、时效优先拆分策略
好,咱们直接进入正题。前面几章我们把订单拆分的业务场景、数据模型都聊透了。这一章,是真正动手写代码的时候了。
我个人习惯,在讲具体算法之前,先画一张图,把三种核心策略的脉络理清楚。你想想看,没有地图就进森林,很容易迷路。
这张图就是我们的作战地图。三种策略,对应三种不同的业务诉求。下面我一个一个拆开来讲。
4.1 基于库存的最优匹配算法
这个算法,说白了就是「哪个仓库有货,就优先从哪个仓库发」。听起来简单?嗯,这里要注意,实际场景远比你想象的复杂。
我在项目中遇到过这样一个坑:用户下单买了5件商品,系统发现A仓有3件,B仓有2件。如果简单按库存量匹配,A仓发3件,B仓发2件,看起来没问题。但问题是,A仓和B仓不在同一个城市,用户要付两笔运费,体验很差。
所以,真正的库存最优匹配,不是「有货就发」,而是「在满足发货条件的前提下,尽量用最少的仓库覆盖最多的商品」。
核心逻辑是这样的:
// 伪代码:库存最优匹配算法
function matchByInventory(orderItems, warehouseInventory) {
// 1. 按仓库库存量降序排序
let sortedWarehouses = sortByStockDesc(warehouseInventory);
// 2. 贪心匹配:优先从库存最多的仓库开始
let result = [];
let remainingItems = [...orderItems];
for (let warehouse of sortedWarehouses) {
if (remainingItems.length === 0) break;
let matchedItems = [];
for (let item of remainingItems) {
if (warehouse.stock[item.sku] >= item.quantity) {
matchedItems.push(item);
warehouse.stock[item.sku] -= item.quantity;
}
}
if (matchedItems.length > 0) {
result.push({
warehouseId: warehouse.id,
items: matchedItems
});
// 从待匹配列表中移除已匹配的商品
remainingItems = remainingItems.filter(
item => !matchedItems.includes(item)
);
}
}
return result;
}
4.2 成本最优拆分策略
成本最优,目标很明确:让履约成本最低。这里的成本包括:仓储费、配送费、包装费,甚至还有退货率折算的成本。
你想想看,如果用户买了一件99元的T恤和一台5000元的电脑,从同一个仓库发,配送费可能只要10元。但如果分开发,T恤从本地仓发(运费5元),电脑从总仓发(运费15元),总运费20元。哪个更划算?显然是合在一起发。
但问题来了:合在一起发,电脑的包装要求高,需要额外的泡沫箱和防震材料,包装成本可能增加8元。这时候,分开发反而更便宜。
所以,成本最优拆分,本质上是一个「组合优化问题」。我常用的方法是动态规划:
// 伪代码:成本最优拆分(动态规划)
function costOptimizedSplit(orderItems, warehouses) {
// dp[i][j] 表示前i个商品,使用j个仓库的最小成本
let n = orderItems.length;
let m = warehouses.length;
let dp = Array(n+1).fill(null).map(() => Array(m+1).fill(Infinity));
dp[0][0] = 0;
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
// 尝试将第i个商品分配给第j个仓库
let cost = calculateCost(orderItems[i-1], warehouses[j-1]);
dp[i][j] = Math.min(dp[i][j], dp[i-1][j-1] + cost);
// 尝试将第i个商品与前面的商品合并到同一个仓库
for (let k = i-1; k >= 1; k--) {
let combinedCost = calculateCombinedCost(
orderItems.slice(k-1, i),
warehouses[j-1]
);
dp[i][j] = Math.min(dp[i][j], dp[k-1][j-1] + combinedCost);
}
}
}
return reconstructSolution(dp, orderItems, warehouses);
}
4.3 时效优先拆分策略
这个策略,是用户体验的底线。用户下单时选了「次日达」,你就必须保证每个子订单都能在24小时内送达。
时效优先的核心逻辑,不是「哪个仓库快就用哪个」,而是「所有仓库都必须满足时效要求」。如果一个仓库无法在承诺时间内送达,哪怕它库存再多、成本再低,也不能用。
我举个例子:用户在北京,买了一件羽绒服和一本笔记本。羽绒服在天津仓有货(次日达),笔记本在武汉仓有货(需要3天)。如果按时效优先,笔记本就不能从武汉仓发,必须从北京本地仓调货,哪怕成本高一些。
具体实现时,我习惯用「时效约束过滤 + 就近匹配」两步走:
// 伪代码:时效优先拆分
function timePrioritySplit(orderItems, warehouses, deliveryPromise) {
// 第一步:时效约束过滤
let eligibleWarehouses = warehouses.filter(warehouse => {
return warehouse.deliveryTime[deliveryPromise] !== undefined;
});
// 第二步:按距离/时效排序,就近匹配
let sortedWarehouses = eligibleWarehouses.sort((a, b) => {
return a.deliveryTime[deliveryPromise] - b.deliveryTime[deliveryPromise];
});
let result = [];
let remainingItems = [...orderItems];
for (let warehouse of sortedWarehouses) {
if (remainingItems.length === 0) break;
let matchedItems = [];
for (let item of remainingItems) {
// 检查该仓库是否能满足时效
if (warehouse.canDeliver(item, deliveryPromise)) {
matchedItems.push(item);
}
}
if (matchedItems.length > 0) {
result.push({
warehouseId: warehouse.id,
items: matchedItems,
estimatedDelivery: warehouse.deliveryTime[deliveryPromise]
});
remainingItems = remainingItems.filter(
item => !matchedItems.includes(item)
);
}
}
// 如果还有剩余商品无法满足时效,需要走「降级策略」
if (remainingItems.length > 0) {
result = fallbackStrategy(remainingItems, result);
}
return result;
}
三种策略的对比与选择
讲到这里,你可能要问了:这三种策略,到底用哪个?
我的建议是:不要单选,要组合。实际系统中,往往是「库存匹配打底,成本优化调优,时效约束兜底」。
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 库存最优匹配 | 大促期间、库存紧张 | 避免缺货,履约率高 | 可能增加配送成本 |
| 成本最优拆分 | 日常订单、利润敏感 | 降低履约成本 | 计算复杂,响应慢 |
| 时效优先拆分 | 会员订单、高价值用户 | 用户体验好 | 成本可能偏高 |
我曾经在一个项目中,把三种策略做成了「可配置的权重模型」。运营同学可以在后台调整权重:比如大促期间,库存权重设为0.6,成本权重0.2,时效权重0.2。这样系统就能根据业务目标,自动选择最优的拆分方案。
嗯,这个权重模型的具体实现,涉及一些数学计算,篇幅有限就不展开了。但核心思路就是:把多目标优化问题,转化为单目标优化问题,然后用贪心或DP求解。
最后说一句:算法再漂亮,也要经过线上验证。我建议先用小流量灰度测试,对比三种策略的履约率、成本和用户满意度,再决定最终方案。毕竟,理论归理论,实战才是检验真理的唯一标准。