【问题标题】:Algorithm to sort passengers into their nearest vehicle until vehicles are full算法将乘客分类到最近的车辆中,直到车辆满员
【发布时间】:2020-05-18 05:14:17
【问题描述】:

我有一个与bin packing problem 类似的问题,但问题不同:

  • 有 x 辆车,可以在 2D 平面中的任何位置
  • 每辆车的载客量为 y
  • 有 z 个乘客,可能在 2D 平面中的任何位置

我想编写一个算法来将乘客分类到车辆中,在以下限制范围内:

  1. 乘客必须转移到最近的车辆
  2. 如果车辆达到容量上限,则不再有乘客可以上车

我想要一个伪代码算法,因为我还不确定我是否会在 JavaScript、PHP 或 SQL 中实现它。我的第一次尝试是这样的:

  1. 为每位乘客分配一个从 0 开始并以 1 递增的数字 ID
  2. 为每辆车分配一个从 0 开始并以 1 递增的数字 ID
  3. 为每辆具有与其车辆 ID 相同名称/ID 的车辆创建一个数组
  4. 对于每位乘客,计算其与所有车辆之间的距离
  5. 将结果存储在乘客/车辆数组中:[passengerID, distance to vehicleID1, distance to vehicleID2, etc.]
  6. 对 z 乘客重复这些步骤:
    • 遍历数组并找到最小的距离值。这应该是离车辆最近的乘客
    • 将乘客 ID 添加到车辆数组中
    • 从乘客/车辆数组中删除该乘客项
    • 检查添加的车辆阵列的乘员。如果长度等于容量,则取消乘客/车辆数组中乘客距离值的每个实例

在执行结束时,如果乘客数量少于车辆总容量,我们应该有一个空的乘客/车辆数组,或者如果乘客数量超过所有车辆的容量,则该数组中会留下一些元素。我们还应该为每辆车辆提供与容量相同数量的元素的数组,或者如果乘客数量少于车辆容量,则可能更少。

我很确定上述方法会奏效,但我觉得可能有更有效的方法。我特别关心一开始就计算每个乘客和车辆之间的距离,因为这在计算上会很昂贵。

谁能指出一个更有效的解决方案?

非常感谢, 阿尔杰

【问题讨论】:

  • 解决这个问题的最佳方法是贪婪地使用分而治之的组合,IMO(看看最接近的配对问题)。您正在做最多的工作,计算每个乘客和每辆车之间的距离。所以让我们摆脱它。根据 x 坐标对乘客和车辆进行排序。将飞机分成n组(根据平均容量计算),并贪婪地将乘客放在组中最近的位置。像我们在 CP 中那样合并组。这应该给你O(nlogn)O(nm) 相对,我认为不可能做得更好nlgn
  • 我也怀疑这可以用简单的 DSL SQL 正确实现,所以我什至不知道为什么要列出它。但如果你真的关心性能,你应该使用 C(或 haskell 或 python),而不是简单添加 17 步算法的语言
  • 嗨 sinanspd。我认为你需要 x 和 y 坐标来解决这个问题。另外,我之所以说这些语言是因为我使用的是 LAMP 堆栈。它可以通过存储过程在 SQL 中实现。

标签: arrays algorithm performance sorting


【解决方案1】:

感谢所有的贡献。我已经完成了研究并编写了自己的代码来解决这个问题。这很复杂:(:

function loadUnits(originalPassengersAndVehicles) {
    // this function takes care of everything required to load any selected passenger(s) into the destination vehicle(s)
    var vehicles = []; // an array of all transport vehicles
    var passengers = []; // an array of all passengers
    var passengerVehicleDistances = []; // an array of the distance between all passengers and all vehicles. this is a 2-dimensional array, with the first dimension being all passengers and the second dimension being the distance between all vehicles for those passengers
    var unitsAndDestinations = []; // an array of all valid passengers and their destination (which should be a vehicle's position)
    var loadingNumbers = []; // store the number of units loading into the vehicles
    var errorMessage = "";
    var distance, currentPassenger;

    // store the vehicle(s) location as the destination
    var destination = {
        lat: null,
        lng: null
    };

    function smallestValue() {
        // this function iterates over the passengerVehicleDistances array to find the passenger which is closest to a vehicle. it ignores any distances that are negative, since these would be passengers that are flagged as boarding/boarded
        var returnValue = {
            distance: 2000, // used as a comparison of passengers' shortest distances to vehicles in the passengerVehicleDistances array. we set the value to double passenger's maximum move distance
            passengerIndex: -1, // used to store the passenger index of the shortest distance between a passenger and a vehicle in the passengerVehicleDistances array
            vehicleIndex: -1 // used to store the vehicle index of the shortest distance between a passenger and a vehicle in the passengerVehicleDistances array
        };

        for (var i = 0; i < passengers.length; i++) {
            for (var j = 0; j < vehicles.length; j++) {     
                if (passengerVehicleDistances[i][j] < returnValue.distance &&
                    passengerVehicleDistances[i][j] > 0) {
                    returnValue.distance = passengerVehicleDistances[i][j];
                    returnValue.passengerIndex = i;
                    returnValue.vehicleIndex = j;
                }
            }
        }

        return returnValue;
    }

    // iterate through all selected passengers and assign valid transport vehicles to the vehicles array and valid passenger to the passengers array
    for (var key in originalPassengersAndVehicles) {
        if (originalPassengersAndVehicles.hasOwnProperty(key)) {
            if (originalPassengersAndVehicles[key].userID == gv.user.ID &&
                originalPassengersAndVehicles[key].selected) {
                if (originalPassengersAndVehicles[key].capacity > 0) {
                    // if the vehicle is moving, show an error message and don't assign it to the vehicles array
                    if (originalPassengersAndVehicles[key].moving) {
                        errorMessage = "Cannot load passenger into a moving " + originalPassengersAndVehicles[key].type + ".";
                        clickedError(originalPassengersAndVehicles[key].position, -30, 0, errorMessage);
                        continue;
                    }

                    // if the vehicle is currently full, show an error message and don't assign it to the vehicles array
                    if (originalPassengersAndVehicles[key].occupants >= originalPassengersAndVehicles[key].capacity) {
                        errorMessage = capitalizeFirstLetter(originalPassengersAndVehicles[key].type) + " is full.";
                        clickedError(originalPassengersAndVehicles[key].position, -20, 0, errorMessage);
                        continue;
                    }

                    vehicles.push(key); // add this vehicle to the array
                    loadingNumbers.push(0); // add another array element to loading numbers for this vehicle
                }

                else if (originalPassengersAndVehicles[key].type == "passenger") {
                    // if the passenger is already being loaded into another vehicle, show an error message and don't assign it to the passengers array
                    if (originalPassengersAndVehicles[key].loading.length > 0) {
                        errorMessage = "That passenger passenger is already boarding another vehicle.";
                        clickedError(originalPassengersAndVehicles[key].position, -30, 0, errorMessage);
                        continue;
                    }

                    // if the passenger is already onboard another vehicle, show an error message and don't assign it to the passengers array. note that this is the same procedure as above, but we want to show a different error message
                    if (originalPassengersAndVehicles[key].holding.length > 0) {
                        errorMessage = "That passenger passenger is already in another vehicle.";
                        clickedError(originalPassengersAndVehicles[key].position, -30, 0, errorMessage);
                        continue;
                    }

                    passengers.push(key); // add this passenger to the array
                    passengerVehicleDistances.push([]); // add an empty array element to the passengers and vehicles distance array for each vehicle to be processed below
                }
            }
        }
    }

    // if either passengers or vehicles are empty, show an error message, disable the load button to prevent the player clicking it again (i.e. force him to try a different selection), and quit this function 
    if (vehicles.length == 0) {
        errorMessage = "No valid vehicles selected to load passenger.";
        clickedError(gv.map.getCenter(), 0, 0, errorMessage);
        gv.o("transportIcon").style.display = "none"; // hide the load icon
        return;
    }

    if (passengers.length == 0) {
        errorMessage = "No valid passenger selected to board vehicles.";
        clickedError(gv.map.getCenter(), 0, 0, errorMessage);
        gv.o("transportIcon").style.display = "none"; // hide the load icon
        return;
    }

    // iterate over the passengers and vehicles arrays and calculate the distances between each passenger and each vehicle. this loop generates the passengerVehicleDistances array, which then needs to be processed for assigning passengers to vehicles
    for (var i = 0; i < passengers.length; i++) {
        for (var j = 0; j < vehicles.length; j++) {         
            // calculate the distance between the passenger's current position and the vehicle's current position
            distance = google.maps.geometry.spherical.computeDistanceBetween(originalPassengersAndVehicles[passengers[i]].position, originalPassengersAndVehicles[vehicles[j]].position); // in metres

            passengerVehicleDistances[i].push(distance); // add the distance to the array
        }
    }

    // calculate the best apportionment of passengers to vehicles. passengers should move to their closest vehicles until that vehicle is full
    for (var i = 0; i < passengers.length; i++) {
        currentPassenger = smallestValue(); // iterate over our passengerVehicleDistances array and find the passenger closest to a vehicle who hasn't yet been processed. note that we don't use i below, as the iteration over this loop isn't the same as the current closest passenger index

        // first we check for -1 in passenger and vehicle indices. if this is the case, then smallestValue() has not found any values in passengerVehicleDistances so we can stop the loop and process unitsAndDestinations
        if (currentPassenger.passengerIndex == -1 && currentPassenger.vehicleIndex == -1) {
            break; // end the loop since there are no longer any valid distances in passengerVehicleDistances
        }

        // check the vehicle this passenger has been assigned to. if it's now full, show an error message and invalidate all distances related to it in passengerVehicleDistances
        if (originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].occupants >= originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].capacity) {
            for (var j = 0; j < passengerVehicleDistances.length; j++) {
                passengerVehicleDistances[j][currentPassenger.vehicleIndex] = -1;
            }

            errorMessage = capitalizeFirstLetter(originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].type) + " is full.";
            clickedError(originalPassengersAndVehicles[passengers[currentPassenger.passengerIndex]].position, -20, 0, errorMessage);
            continue;
        }

        // check if this passenger will fit into this vehicle based on the vehicle's current occpants, units currently boarding and its capacity. first, we calculate the true loading figure as we can't just count the loading array as each unitID may be a passenger group      
        loadingNumbers[currentPassenger.vehicleIndex] += originalPassengersAndVehicles[passengers[currentPassenger.passengerIndex]].totalUnits;

        // then we add the vehicle's current occupants with the loading number and check if loading this passenger would put it over capacity. if the passenger can board, but the vehicle will be full when this passenger boards, invalidate all distances related to this vehicle in passengerVehicleDistances
        if (originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].occupants + loadingNumbers[currentPassenger.vehicleIndex] > originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].capacity) {
            // the passenger cannot board (i.e. its totalUnits is greater than available capacity). ideally, in this circumstance the passenger should try to board all other vehicles in ascending distance order. if it can't board any other vehicle, then we can remove the passenger from processing
            // insert code here

            for (var j = 0; j < passengerVehicleDistances.length; j++) {
                passengerVehicleDistances[j][currentPassenger.vehicleIndex] = -1;
            }

            errorMessage = capitalizeFirstLetter(originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].type) + " will be full.";
            clickedError(originalPassengersAndVehicles[passengers[currentPassenger.passengerIndex]].position, -20, 0, errorMessage);
            continue;
        }

        // if this distance is too far, then the distance to every other vehicle will also be too far
        if (currentPassenger.distance > originalPassengersAndVehicles[passengers[i]].range_move) {
            // show an error message
            errorMessage = "This passenger is too far to load.";
            clickedError(originalPassengersAndVehicles[passengers[currentPassenger.passengerIndex]].position, -10, 0, errorMessage);

            // remove the passenger from the distances array so that it's not processed again
            for (var j = 0; j < passengerVehicleDistances[currentPassenger.passengerIndex].length; j++) {
                passengerVehicleDistances[currentPassenger.passengerIndex][j] = -1;
            }

            continue;
        }

        // only add the passenger to the vehicle if it passes the above checks (within movement range, vehicle not currently full or will be full). move this passenger directly to its closest vehicle (i.e. directly to the same grid square occupied by the vehicle)
        destination.lat = roundToDecimal(originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].position.lat() - (gv.gridUnit / 2), 6);
        destination.lng = roundToDecimal(originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].position.lng() - (gv.gridUnit / 2), 6);

        // if we are ok to load, add the passenger to unitsAndDestinations
        unitsAndDestinations.push([passengers[currentPassenger.passengerIndex], destination]);

        // remove the passenger from the distances array so that it's not processed again
        for (var j = 0; j < passengerVehicleDistances[currentPassenger.passengerIndex].length; j++) {
            passengerVehicleDistances[currentPassenger.passengerIndex][j] = -1;
        }

        // flag the transport vehicle as loading the units
        originalPassengersAndVehicles[vehicles[currentPassenger.vehicleIndex]].loading.push(passengers[currentPassenger.passengerIndex]);

        // flag the passenger as boarding the vehicle
        originalPassengersAndVehicles[passengers[currentPassenger.passengerIndex]].loading.push(vehicles[currentPassenger.vehicleIndex]);
    }

    // only send something to the server and update the client if at least 1 passenger can be vaildly loaded
    if (unitsAndDestinations.length > 0) {
        // send the array of units and their destinations to the server for checking and database updating
        serverProcessing(unitsAndDestinations);
        gv.o("transportIcon").style.display = "none"; // hide the load icon
    }
}

这是一个纯 JavaScript 实现,但我已经在我的应用程序中对其进行了测试,并且它确实有效(据我所知)。希望其他人会发现代码有用。

干杯, 阿尔杰

【讨论】:

    【解决方案2】:

    这不是装箱问题,这是个好消息,因为装箱是 NP 难题。

    这是一个最小权重二分匹配问题(也称为“分配问题”),其中权重是距离。一个顶点集为每个客户提供一个元素。另一个对每个车辆座椅都有一个顶点。每个可行的车辆-客户配对都有一个优势。边缘的权重是两者之间的距离。

    注意许多关于分配问题算法解决最大权重匹配的介绍。不用担心。要解决最小权重问题,只需对权重取反即可。经典解决方案是Hungarian Algorithm,其顶点数为 O(n^3)。但是,您的问题是一个称为欧几里得二分匹配的特殊版本。对于这种情况,存在渐近更快的算法。 Here's a survey paper。我不知道这些在实践中更快。匈牙利算法很简单,开销很低。

    实际上,解决几千个顶点不是问题,但 PHP、javascript 或 SQL 并不是此类计算密集型算法的最佳工具。如果您必须选择三者中的一个,请使用 javascript 并确保它处于具有出色 JIT 编译器的环境中。

    找到最小重量匹配将对应于将人员放入车辆中,以最大限度地减少从客户到车辆的总距离。

    【讨论】:

    • 嗨 Gene,感谢您的贡献。但是,我真的在寻找某人的算法。匈牙利算法看起来很接近,但不是我想要的。
    • @Arj 好吧,可能是这样。您的算法本质上是贪婪的。匈牙利算法更简单,并产生最佳答案。但我很高兴你开心。
    【解决方案3】:

    首先,任何算法都必须利用这两个维度。如果您将所有点投影到 x 平面或 y 平面上,那么您将丢失一维信息。因此,您必须测量任何车辆/乘客对之间的距离。

    完成此操作后,您可以将其视为区域增长算法:

    • 在每辆车周围均匀地生长一个区域(圆圈)。
    • 只要将乘客包含在车辆的区域中,就会将该乘客分配给该车辆
    • 当车辆达到其容量时,停止种植该区域

    在代码中,开始是相同的,但您可以通过排序稍微加快它:

    • 为每辆车创建一个数组,存储到每个乘客的距离
    • 获取所有这些数组中的唯一值并按升序对它们进行排序
    • 然后遍历这个唯一的距离列表。对于每个距离,分配在每个阵列中具有该距离的乘客。
    • 当车辆达到其容量时,移除该阵列。

    【讨论】:

    • 嗨 mattyx17。我同意您需要这两个维度,但是我不认为逐渐扩大半径是一种有效的解决方案,因为如果乘客和其他车辆都在该半径内,您会遇到冲突。
    猜你喜欢
    • 2012-11-26
    • 2020-11-11
    • 1970-01-01
    • 1970-01-01
    • 2018-10-13
    • 1970-01-01
    • 2017-07-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多