You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求兼顾服务时长平衡的经纬度位置聚类算法方案

基于最小费用流的集群时长平衡C#实现方案

核心思路

先用你已有的聚类算法(如K-Means)生成空间连续、互不重叠的初始服务区域,再通过最小费用流算法调整区域间的客户分配,让各区域总服务时长逼近目标值(总时长/区域数),同时严格保证区域空间连续性(避免重叠)。

步骤拆解

  1. 初始聚类与目标计算

    • 用K-Means/MiniBatch K-Means生成K个空间连续的初始区域,统计每个区域的总服务时长S_i。
    • 计算目标时长T = 总服务时长 / K,定义每个区域的溢出量Excess_i = max(S_i - T, 0),缺口量Deficit_i = max(T - S_i, 0)。
  2. 最小费用流模型构建

    • 节点定义:源点Source、汇点Sink,每个区域对应一个独立节点。
    • 边定义:
      • 源点→溢出区域节点:容量=Excess_i,费用=0(表示可输出的多余时长)
      • 缺口区域节点→汇点:容量=Deficit_i,费用=0(表示需要补充的时长)
      • 溢出区域→相邻缺口区域:仅当两个区域空间相邻时连边,容量=min(Excess_i, Deficit_j),费用=区域中心距离的整数倍(优先转移空间更近的客户,保证区域连续性)
  3. 运行最小费用流算法
    通过算法找到从源到汇的最小费用流,对应最优客户转移方案,调整后各区域总时长方差可控制在1%以内。

C# 实现示例

1. 最小费用流算法核心实现

public class FlowEdge
{
    public int To { get; set; }
    public int Reverse { get; set; }
    public int Capacity { get; set; }
    public int Cost { get; set; }

    public FlowEdge(int to, int reverse, int capacity, int cost)
    {
        To = to;
        Reverse = reverse;
        Capacity = capacity;
        Cost = cost;
    }
}

public class MinCostFlow
{
    private readonly List<List<FlowEdge>> _graph;
    private readonly int[] _potential;
    private readonly int[] _dist;
    private readonly int[] _prevNode;
    private readonly int[] _prevEdge;

    public MinCostFlow(int nodeCount)
    {
        _graph = new List<List<FlowEdge>>();
        for (int i = 0; i < nodeCount; i++)
        {
            _graph.Add(new List<FlowEdge>());
        }
        _potential = new int[nodeCount];
        _dist = new int[nodeCount];
        _prevNode = new int[nodeCount];
        _prevEdge = new int[nodeCount];
    }

    public void AddEdge(int from, int to, int capacity, int cost)
    {
        _graph[from].Add(new FlowEdge(to, _graph[to].Count, capacity, cost));
        _graph[to].Add(new FlowEdge(from, _graph[from].Count - 1, 0, -cost));
    }

    public int Compute(int source, int sink, int maxFlow)
    {
        int totalCost = 0;
        Array.Fill(_potential, 0);

        while (maxFlow > 0)
        {
            var priorityQueue = new PriorityQueue<(int Dist, int Node), int>();
            Array.Fill(_dist, int.MaxValue);
            _dist[source] = 0;
            priorityQueue.Enqueue((0, source), 0);

            while (priorityQueue.Count > 0)
            {
                var (currentDist, u) = priorityQueue.Dequeue();
                if (currentDist > _dist[u]) continue;

                for (int i = 0; i < _graph[u].Count; i++)
                {
                    var edge = _graph[u][i];
                    if (edge.Capacity <= 0) continue;

                    int newDist = _dist[u] + edge.Cost + _potential[u] - _potential[edge.To];
                    if (_dist[edge.To] > newDist)
                    {
                        _dist[edge.To] = newDist;
                        _prevNode[edge.To] = u;
                        _prevEdge[edge.To] = i;
                        priorityQueue.Enqueue((newDist, edge.To), newDist);
                    }
                }
            }

            if (_dist[sink] == int.MaxValue) break;

            for (int v = 0; v < _graph.Count; v++)
            {
                if (_dist[v] != int.MaxValue)
                    _potential[v] += _dist[v];
            }

            int flow = maxFlow;
            for (int v = sink; v != source; v = _prevNode[v])
            {
                flow = Math.Min(flow, _graph[_prevNode[v]][_prevEdge[v]].Capacity);
            }

            maxFlow -= flow;
            totalCost += flow * _potential[sink];

            for (int v = sink; v != source; v = _prevNode[v])
            {
                var edge = _graph[_prevNode[v]][_prevEdge[v]];
                edge.Capacity -= flow;
                _graph[v][edge.Reverse].Capacity += flow;
            }
        }

        return totalCost;
    }
}

2. 业务逻辑适配(区域平衡)

public class ServiceArea
{
    public int Id { get; set; }
    public double TotalDuration { get; set; }
    public List<Customer> Customers { get; set; } = new();
    public List<int> NeighborAreaIds { get; set; } = new(); // 仅相邻区域可转移客户
}

public class Customer
{
    public double Duration { get; set; }
    public (double X, double Y) Coords { get; set; }
}

public void BalanceServiceAreas(List<ServiceArea> areas)
{
    double totalDuration = areas.Sum(a => a.TotalDuration);
    int k = areas.Count;
    double targetDuration = totalDuration / k;

    // 放大时长为整数,避免浮点误差
    int scale = 100;
    int scaledTarget = (int)Math.Round(targetDuration * scale);

    int nodeCount = 2 + k; // 源点(0)、汇点(1)、区域节点(2~k+1)
    var minCostFlow = new MinCostFlow(nodeCount);
    int totalExcess = 0;

    // 构建源/汇与区域的边
    for (int i = 0; i < k; i++)
    {
        int areaNode = 2 + i;
        int scaledTotal = (int)Math.Round(areas[i].TotalDuration * scale);
        int excess = scaledTotal - scaledTarget;

        if (excess > 0)
        {
            minCostFlow.AddEdge(0, areaNode, excess, 0);
            totalExcess += excess;
        }
        else if (excess < 0)
        {
            minCostFlow.AddEdge(areaNode, 1, -excess, 0);
        }
    }

    // 构建相邻区域间的转移边
    for (int i = 0; i < k; i++)
    {
        var srcArea = areas[i];
        int scaledSrcTotal = (int)Math.Round(srcArea.TotalDuration * scale);
        if (scaledSrcTotal <= scaledTarget) continue;

        int srcNode = 2 + i;
        foreach (var neighborId in srcArea.NeighborAreaIds)
        {
            var destArea = areas.First(a => a.Id == neighborId);
            int scaledDestTotal = (int)Math.Round(destArea.TotalDuration * scale);
            if (scaledDestTotal >= scaledTarget) continue;

            int destNode = 2 + areas.IndexOf(destArea);
            // 转移成本:区域中心距离放大100倍,优先转移近的客户
            double distance = GetCenterDistance(srcArea, destArea);
            int cost = (int)Math.Round(distance * 100);

            int maxTransfer = Math.Min(scaledSrcTotal - scaledTarget, scaledTarget - scaledDestTotal);
            if (maxTransfer > 0)
            {
                minCostFlow.AddEdge(srcNode, destNode, maxTransfer, cost);
            }
        }
    }

    // 运行最小费用流
    minCostFlow.Compute(0, 1, totalExcess);

    // 根据残差网络调整客户分配(需遍历边的流量,将对应时长的客户从源区域转移到目标区域)
    // 此处省略具体客户转移逻辑,核心是按流量匹配转移对应时长的客户
}

// 计算区域中心距离
private double GetCenterDistance(ServiceArea a, ServiceArea b)
{
    var aCenter = (X: a.Customers.Avg(c => c.Coords.X), Y: a.Customers.Avg(c => c.Coords.Y));
    var bCenter = (X: b.Customers.Avg(c => c.Coords.X), Y: b.Customers.Avg(c => c.Coords.Y));
    return Math.Sqrt(Math.Pow(aCenter.X - bCenter.X, 2) + Math.Pow(aCenter.Y - bCenter.Y, 2));
}

// 扩展方法:计算集合平均值
public static double Avg<TSource>(this IEnumerable<TSource> source, Func<TSource, double> selector)
{
    return source.Select(selector).Average();
}

关键注意事项

  • 区域相邻性:必须通过初始聚类的邻接矩阵或空间索引(如R树)识别相邻区域,禁止跨非相邻区域转移客户,否则会破坏区域连续性。
  • 性能优化:针对50万+客户场景,初始聚类建议用MiniBatch K-Means;区域数K建议控制在1000以内,若K过大需拆分批次处理。
  • 精度控制:通过放大时长为整数避免浮点误差,调整完成后再还原为原单位。

内容的提问来源于stack exchange,提问作者DesperateAndTired

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 12:37:20