基于Dijkstra算法和KM算法的网约车订单分配问题

      网约车在人们的日常出行生活中扮演了十分重要的角色,它为出租车和乘客搭建了一个方便的业务平台,既减少了乘客的候车时间,又很大程度上缓解了出租车的空载现象,从而大大提高了人们的出行效率。网约车的订单分配模式一般包括抢单模式和系统派单模式,这里实现了系统派单模式下的订单分配策略,即保证所有乘客等待的时间全局最小。使用Dijkstra算法来计算每辆出租车到每个乘客的最短路径,并通过KM算法求得出租车和乘客之间的最优匹配,来保证所有出租车到乘客之间所需的行驶距离最短,从而保证乘客所需的候车时间也全局最短。

       左边图是对实际的网约车订单分配问题的一个简化模型,右边是对模型的抽象。在加权关系图中,我们将每一个交叉路口视为在图中的一个结点,结点之间的权重表示两个路口之间的距离,而出租车和乘客都位于某一个路口,并可以被视为图中的一个结点。

       将关系图转换为加权邻接矩阵作为程序的输入,并在程序的开始初始化出租车和乘客在图中的位置。在使用Dijkstra算法求出租车到乘客的最短路径时,因为我们求的是有限点的最短路径,所以我们将遍历了所有要求结点(在这个问题中是乘客结点)作为结束Dijkstra算法的条件以减少运算量。因为我们需要求的是订单的全局最优匹配,即最小化所有出租车到对应乘客所花费的时间之和最小。而在原始的KM算法中是以最大化最优匹配作为目标的,所以在处理时需要将对应的权重取负来保证得到正确的结果。

代码实现

# -*- coding:utf-8 -*-
# @Time : 2021/11/13 15:26
# @Author: zhcode
# @File : taxi_dispatcher.py

import sys
import copy
import json

"""
1. 使用dijkstra发现出租车到乘客的最短距离
2. 使用二部图最优匹配,将出租车和乘客进行匹配
"""

path = './graph.json'


def json_traverse(data: dict):
    """
    遍历字典,并返回ndarray格式的元组
    :param data: 要遍历的地图数据
    :return: 图的邻接矩阵
    """
    graph_items = [(item[0], item[1]) for item in data[0].items()]
    point_num = len(graph_items)
    adjacency_matrix = [[float('inf') for j in range(point_num)] for i in range(point_num)]
    for k, v in data[0].items():
        row_index = ord(k) - ord('A')
        adjacency_matrix[row_index][row_index] = 0
        for edge, dis in v.items():
            column_index = ord(edge) - ord('A')
            adjacency_matrix[row_index][column_index] = dis
            adjacency_matrix[column_index][row_index] = dis
        # print(k, v)
    return adjacency_matrix


def dijkstra(graph_matrix, src):
    """
    实现迪杰斯特拉算法
    :param graph_matrix: 图的邻接矩阵
    :param src: 源点
    :param des: 终点,当算法访问到终点时,停止算法
    :return: 返回最短的距离和路径
    """
    graph = None
    if graph_matrix == None:
        return None
    else:
        graph = graph_matrix

    # 定点集合
    nodes = [i for i in range(len(graph))]  # 获取顶点列表,用邻接矩阵存储图
    # 顶点是否被访问
    visited = []
    # 将源点加入已访问节点
    # visited.append(src)
    # 初始化dis
    dis = {src: 0}  # 源点到自身的距离为0
    # 初始化距离
    for i in nodes:
        dis[i] = graph[src][i]
    path = {src: {src: []}}  # 记录源节点到每个节点的路径
    k = pre = src
    while nodes:
        temp_k = k
        mid_distance = float('inf')  # 设置中间距离无穷大
        for v in visited:
            for d in nodes:
                # A 到 Z, Z 再到 Q
                if graph[src][v] != float('inf') and graph[v][d] != float('inf'):  # 有边
                    new_distance = graph[src][v] + graph[v][d]
                    if new_distance <= mid_distance:
                        mid_distance = new_distance
                        graph[src][d] = new_distance  # 进行距离更新
                        k = d
                        pre = v

        # 当已经遍历了所有的节点, 就终止算法
        if k != src and not nodes:
            break

        dis[k] = mid_distance  # 最短路径
        # 保存当前最短路径
        path[src][k] = [i for i in path[src][pre]]
        path[src][k].append(k)

        if k not in visited:
            visited.append(k)
            nodes.remove(k)

    return dis, path


def search_batch(u: int) -> bool:
    """
    给x[u]找匹配,这个过程和匈牙利匹配是一样的
    :param u:
    :return:
    """
    global weight, n, Cx, Cy, sx, sy, match
    sx[u] = True
    for v in range(n):
        if not sy[v] and Cx[u] + Cy[v] == weight[u][v]:
            sy[v] = True
            if match[v] == -1 or search_batch(match[v]):
                match[v] = u
                return True
    return False


def kuhn_munkras(max_weight: bool) -> list:
    """
    实现km算法
    :param max_weight:
    :return:
    """
    global weight, n, Cx, Cy, sx, sy, match
    negate_weight = lambda x: -x
    if not max_weight:
        # 对权重进行取反操作,因为是按照出租车到乘客的最短距离进行匹配
        weight = [[negate_weight(weight[i][j]) for j in range(n)] for i in range(n)]

    for u in range(n):
        # 不断修改顶标,直到完美匹配
        while True:
            sx = [0 for i in range(n)]
            sy = [0 for i in range(n)]
            if search_batch(u):
                break
            inc = sys.maxsize
            for j in range(n):
                if sx[j]:
                    for k in range(n):
                        if not sy[k] and (Cx[j] + Cy[k] - weight[j][k]) < inc:
                            inc = Cx[j] + Cy[k] - weight[j][k]

            for j in range(n):
                if sx[j]:
                    Cx[j] -= inc
                if sy[j]:
                    Cy[j] += inc

    return [match[i] for i in range(n) if match[i] >= 0]


if __name__ == '__main__':
    # 出租车的位置
    taxi_pos = [0, 10, 18]
    # 乘客的位置
    passenger_pos = [5, 7, 24]
    # 保存各个出租车到各个乘客的路径信息
    path_infos = [[{} for j in range(len(passenger_pos))] for i in range(len(taxi_pos))]
    # 输入的有向图,有边存储的就是边的权值,无边就是float('inf'),顶点到自身就是0
    graph = None
    with open(path, 'r', encoding='utf-8') as f:
        graph = json.load(f)
    graph = json_traverse(graph)
    for i, taxi in enumerate(taxi_pos):
        graph_copy = copy.deepcopy(graph)
        dis, path = dijkstra(graph_copy, taxi)  # 查找从源点0开始带其他节点的最短路径
        for from_point, to_data in path.items():
            for k, vals in to_data.items():
                if k in passenger_pos:
                    drive_path = [chr(ord('A') + val) for val in vals]
                    path_infos[i][passenger_pos.index(k)]['d'] = dis[k]
                    path_infos[i][passenger_pos.index(k)]['p'] = drive_path
                    # 输出路径信息
                    # print("{}--->{}:".format(chr(ord('A') + from_point), chr(ord('A') + k)), end=" ")
                    # print(drive_path, end=" ")

    # for item in path_infos:
    #     print(item)

    weight = [[val['d'] for val in item] for item in path_infos]
    n = len(weight)
    # x 的定点标号,为与之相连的边的最大权值
    Cx = [max(row) for row in weight]
    # y 的定点标号初始化为0
    Cy = [0 for i in range(n)]
    # 记录寻找增广路时,点集x,y里的点是否搜索过
    sx, sy = [], []
    # 初始化match数组为未匹配
    match = [-1 for i in range(n)]
    # for match_i in kuhn_munkras(0):
    total_dis = 0
    for i, match_passenger in enumerate(kuhn_munkras(0)):
        print("出租车{}分配乘客{}的订单, 距离为{}".format(chr(ord('A') + taxi_pos[i]), chr(ord('A') + passenger_pos[match_passenger]), path_infos[i][match_passenger]['d'], end=" "))
        print("规划路径为:", end=" ")
        for point in path_infos[i][match_passenger]['p']:
            print(point, end=" ")
        print("\n", "-"*25)

 作为输入的文件 graph.json 

程序运行结果

 


 

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值