Алгоритм сортировки пассажиров до ближайшего транспортного средства, пока автомобили не заполнены - PullRequest
0 голосов
/ 02 февраля 2020

У меня есть проблема, которая похожа на проблему упаковки бункера , но проблема в другом:

  • Есть x транспортных средств, которые могут быть где угодно в 2D плоскости
  • Вместимость каждого транспортного средства y
  • Есть z пассажиров, которые могут находиться где угодно в плоскости 2D

Я хотел бы написать алгоритм сортировки пассажиры, находящиеся в транспортных средствах, с соблюдением следующих ограничений:

  1. Пассажиры должны переместиться к ближайшему транспортному средству
  2. Если транспортное средство достигает максимальной вместимости, больше пассажиров не может подойти к нему

Я хотел бы использовать алгоритм псевдокода, поскольку я еще не уверен, буду ли я реализовывать его в JavaScript, PHP или SQL. Моя первая попытка заключается в следующем:

  1. Назначьте каждому пассажиру цифру c ID, начиная с 0 и увеличивая на 1
  2. Назначьте каждому транспортному средству цифру c ID, начиная с 0 и с шагом 1
  3. Создать массив для каждого транспортного средства с тем же именем / идентификатором, что и его идентификатор транспортного средства
  4. Для каждого пассажира рассчитать расстояние между ним и всеми транспортными средствами
  5. Store результаты в массиве пассажиров / транспортных средств: [passengerID, distance to vehicleID1, distance to vehicleID2, etc.]
  6. Повторите эти шаги для пассажиров z:
    • Выполните итерацию по массиву и найдите наименьшее значение расстояния. Это должен быть пассажир, который находится ближе всего к транспортному средству
    • Добавьте идентификатор пассажира в массив автомобиля
    • Удалите этот пассажирский элемент из массива пассажир / автомобиль
    • Проверьте пассажиров добавлен массив транспортных средств. Если длина равна вместимости, обнуляйте каждый экземпляр значения расстояния пассажира в массиве пассажир / транспортное средство

В конце выполнения у нас должен быть пустой массив пассажир / транспортное средство если количество пассажиров меньше общей вместимости транспортного средства, или в этом массиве осталось несколько элементов, если пассажиров больше, чем уместилось бы во всех транспортных средствах. Мы также должны иметь массивы для каждого транспортного средства, которые имеют такое же количество элементов, что и их вместимость, или, возможно, меньше, если пассажиров было меньше, чем вместимость транспортного средства.

Я почти уверен, что вышеупомянутое сработает, но я чувствую, что мог бы быть более эффективный метод. Я особенно обеспокоен расчетом расстояния между каждым пассажиром и транспортным средством для начала, так как это будет вычислительно дорого.

Может кто-нибудь указать мне на более эффективное решение?

Большое спасибо, Arj

Ответы [ 3 ]

2 голосов
/ 02 февраля 2020

Это не проблема упаковки в мусорное ведро, что является хорошей новостью, потому что упаковка в мусорное ведро трудная для NP

Скорее, это проблема согласования двухстороннего минимального веса (также называемая «проблемой присвоения»), где веса - это расстояния. Один набор вершин имеет элемент для каждого клиента. У другого есть вершина для каждого места транспортного средства. Есть одно преимущество для каждой возможной пары автомобиль-клиент. Вес на ребре - это расстояние между ними.

Обратите внимание, что многие представления алгоритмов задачи назначения решают для согласования веса максимум . Не беспокойся. Чтобы решить проблему минимального веса, просто отмените вес. Классическим решением является Венгерский алгоритм , который будет O (n ^ 3) в количестве вершин. Однако ваша проблема - это специальная версия, называемая евклидовым двусторонним соответствием. Существуют асимптотически более быстрые алгоритмы для этого случая. Вот обзорный документ . Я понятия не имею, что это быстрее на практике. Венгерский алгоритм прост, с очень низкими издержками.

На практике, решение для нескольких тысяч вершин не является проблемой, но PHP, javascript или SQL не лучшие инструменты для очень интенсивные вычислительные алгоритмы, как этот. Если вы должны выбрать один из трех, используйте javascript и убедитесь, что он находится в среде с отличным JIT-компилятором.

Определение соответствия минимального веса будет соответствовать помещению людей в транспортные средства таким образом, чтобы минимизировать общее расстояние от клиентов до их транспортных средств.

0 голосов
/ 04 февраля 2020

Спасибо за все вклады. Я провел исследование и написал свой собственный код для решения этой проблемы. Это довольно сложно :(:

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, но я проверил ее в своем приложении, и она работает (насколько я вижу). Надеюсь, кто-то еще найдет код полезный.

Ура, Arj

0 голосов
/ 02 февраля 2020

Во-первых, любой алгоритм должен использовать оба измерения. Если вы спроецируете все точки на плоскость x или y, вы потеряете одно измерение информации. Таким образом, вы должны измерить расстояние между любой парой автомобиль / пассажир.

Как только вы это сделаете, вы можете думать об этом как о алгоритме роста региона:

  • Равномерно увеличивайте регион (круг) вокруг каждого транспортного средства.
  • Как только пассажир включается в регион для транспортного средства, этот пассажир назначается этому транспортному средству
  • Когда транспортное средство достигает своей вместимости, прекратите увеличивать регион

В коде запуск будет таким же, но вы можете немного ускорить его с помощью сортировки:

  • Создать массив для каждого транспортного средства, сохраняя расстояние до каждого пассажира
  • Возьмите уникальные значения во всех этих массивах и отсортируйте их в порядке возрастания
  • Затем выполните итерацию по этому уникальному списку расстояний. Для каждого расстояния назначьте пассажиров, которые имеют это расстояние в каждом массиве.
  • Когда транспортное средство достигнет своей вместимости, удалите этот массив.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...