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

基于分支定界算法的C++初始-终位最优匹配优化求助

分支定界算法优化:点集匹配最小总距离的实时性能问题

我正在开发一款游戏原型,需要将N个起始位置与N个终位关联,以最小化总距离之和。当前是2D项目,但问题同样适用于3D(或更高维度)。我用大学学过的分支定界算法能得到正确结果,但C++实现无法满足实时需求:当N=10时,算法执行耗时约8-10秒。测试发现do while循环是性能瓶颈,希望优化这段代码。

#include <iostream>
#include <utility>
#include <vector>
#include <algorithm>
#include <limits>
#include <queue>
#include <tuple>
#include <cmath>

const float Epsilon = 0.0005f;

class HashMap {
private:
    size_t* map;
    size_t size;

public:
    explicit HashMap(size_t size) {
        this->size = size;
        map = new size_t[size];
        for (auto i = 0; i < size; ++i) {
            map[i] = -1;
        }
    }

    ~HashMap() {
        delete map;
    }

    void clear() {
        for (auto i = 0; i < size; ++i) {
            map[i] = -1;
        }
    }

    void insert(size_t key, size_t value) {
        map[key] = value;
    }

    size_t get(size_t key) {
        return map[key];
    }

    void remove(size_t key) {
        map[key] = -1;
    }
};

struct Vector2 {
    float x, y; // Assuming a 2D vector
};

float distance(const Vector2& v1, const Vector2& v2) {
    float dx = v1.x - v2.x;
    float dy = v1.y - v2.y;
    return std::sqrt(dx * dx + dy * dy);
}

float getBestLineSolution(HashMap& usedValues, size_t size, const float* distances, size_t line) {
    auto best = std::numeric_limits<float>::max();
    for (auto i = 0; i < size; i++) {
        if (usedValues.get(i) == -1 && distances[line * size + i] < best) {
            best = distances[line * size + i];
        }
    }
    return best;
}

float getRemainingScore(HashMap& usedValues, size_t size, const float* distances, size_t line) {
    auto remainingScore = 0.0f;
    for (auto i = line; i < size; i++) {
        remainingScore += getBestLineSolution(usedValues, size, distances, i);
    }
    return remainingScore;
}

float getTotalHeuristic(std::tuple<float, float, std::vector<size_t>> a) {
    // 0 is the true weight up until now and 1 is the heuristics
    return std::get<0>(a) + std::get<1>(a);
}

struct CustomCompare {
    bool operator()(std::tuple<float, float, std::vector<size_t>> a, std::tuple<float, float, std::vector<size_t>> b) {
        // Sort by cost
        return getTotalHeuristic(std::move(a)) > getTotalHeuristic(std::move(b));
    }
};

std::vector<size_t> branchAndBoundIterative(const std::vector<Vector2>& currentPosition, std::vector<Vector2>& finalPositions) {
    auto size = currentPosition.size();
    auto usedPosition = HashMap(size);
    float distances[size * size];
    auto minHeap = std::priority_queue<std::tuple<float, float, std::vector<size_t>>,
                                        std::vector<std::tuple<float, float, std::vector<size_t>>>, CustomCompare>();

    // Initialize the matrix with the distances
    for (auto i = 0; i < size; i++) {
        for (auto j = 0; j < size; j++) {
            distances[i * size + j] = distance(currentPosition[i], finalPositions[j]);
        }
    }

    // Getting the threshold
    auto threshold = 0.0f;
    for (auto i = 0; i < size; i++) {
        threshold += distances[i * size + i];
    }

    // Getting all first node whose cost is under the threshold
    for (size_t i = 0; i < size; i++) {
        auto dist = distances[i];
        auto heuristic = getRemainingScore(usedPosition, size, distances, 1);

        if (dist + heuristic <= (threshold + Epsilon)) {
            auto v = std::vector<size_t>();
            v.push_back(i);
            minHeap.emplace(dist, heuristic, v);
        }
    }

    // Doing the algorithm
    // This is part is really slow
    auto bestTuple = minHeap.top();
    do {
        // Lower cost path (called current path in the rest of the algorithm)
        auto currentTuple = minHeap.top();
        minHeap.pop();
        usedPosition.clear();
        auto positions = std::get<2>(currentTuple);
        auto posSize = positions.size();

        if (posSize == size) {
            // If the current path (in the matrix) contains all elements
            auto totalDistance = std::get<0>(currentTuple);
            if (totalDistance <= (threshold + Epsilon)) {
                threshold = totalDistance;
                bestTuple = currentTuple;
            }
        } else {
            // If the current path (in the matrix) doesn't contain all elements
            for (auto i = 0; i < posSize; i++) {
                usedPosition.insert(positions[i], i);
            }

            auto currentDist = std::get<0>(currentTuple);

            // Getting the next best result for the next line in the current path
            for (auto i = 0; i < size; i++) {
                if (usedPosition.get(i) == -1) {
                    auto newDist = currentDist + distances[posSize * size + i];
                    auto heuristic = getRemainingScore(usedPosition, size, distances, posSize + 1);
                    if (newDist + heuristic <= (threshold + Epsilon)) {
                        auto positionCopy = std::vector<size_t>(positions);
                        positionCopy.push_back(i);
                        minHeap.emplace(newDist, heuristic, positionCopy);
                    }
                }
            }
        }
    } while (!minHeap.empty() && getTotalHeuristic(minHeap.top()) <= (threshold + Epsilon));

    return std::get<2>(bestTuple);
}

int main() {
    std::vector<Vector2> currentPosition = { {0, 0},
                                             {1, 0},
                                             {2, 0},
                                             {3, 0},
                                             {4, 0},
                                             {5, 0},
                                             {6, 0},
                                             {7, 0},
                                             {8, 0},
                                             {9, 0}
    };

    std::vector<Vector2> finalPosition = { {-4, 5},
                                           {-3, 5},
                                           {-2, 5},
                                           {-1, 5},
                                           {0, 5},
                                           {1, 5},
                                           {2, 5},
                                           {3, 5},
                                           {4, 5},
                                           {5, 5}
    };
    std::vector<size_t> bestFinalPosition = branchAndBoundIterative(currentPosition, finalPosition);

    // Output the result
    std::cout << "Optimal final positions:" << std::endl;
    for (auto i = 0; i < currentPosition.size(); i++) {
        auto pos = finalPosition[bestFinalPosition[i]];
        std::cout << "(" << pos.x << ", " << pos.y << ") ";
    }

    return 0;
}
优化方案

1. 替换自定义HashMap为位集或布尔数组

自定义HashMap的实现冗余,完全可以用std::vector<bool>或std::bitset替代,这类结构访问和修改速度更快,内存占用极小:

// 替换HashMap为std::vector<bool>
std::vector<bool> usedPosition(size, false);
// 检查是否使用:if (!usedPosition[i])
// 标记使用:usedPosition[i] = true;
// 清空:std::fill(usedPosition.begin(), usedPosition.end(), false);

避免了不必要的内存操作和类型转换,大幅提升get和insert的效率。

2. 预计算启发式数据,减少重复计算

当前每次调用getRemainingScore都会遍历剩余行查找最小未使用距离,属于重复计算重灾区。可以:

  • 预计算每行的最小距离数组row_mins,初始时row_mins[i]是第i行的最小距离。
  • 用行最小和列最小的和作为下界,这个启发式更紧,能更早剪枝:
std::vector<float> row_mins(size, std::numeric_limits<float>::max());
std::vector<float> col_mins(size, std::numeric_limits<float>::max());
for (int i = 0; i < size; ++i) {
    for (int j = 0; j < size; ++j) {
        row_mins[i] = std::min(row_mins[i], distances[i*size + j]);
        col_mins[j] = std::min(col_mins[j], distances[i*size + j]);
    }
}
float initial_lower_bound = std::accumulate(row_mins.begin(), row_mins.end(), 0.0f);

计算剩余启发式时,基于已使用的行和列快速得到剩余行的最小可用距离之和,无需每次遍历所有列。

3. 减少优先级队列的数据拷贝

当前每次插入队列都会拷贝整个std::vector<size_t>,开销极大。可以用位掩码替代vector存储已选列:

// 把tuple中的vector替换为uint16_t(N<=16时足够)
using Node = std::tuple<float, float, uint16_t, int>; // 当前距离,启发式,位掩码,已选数量
// 检查第j列是否被使用:(mask & (1 << j)) == 0
// 标记第j列使用:mask | (1 << j)

每个节点内存占用从O(N)降到O(1),拷贝速度大幅提升。

4. 优化初始阈值

当前初始阈值用对角线距离之和,远大于实际最优值,导致队列堆积大量无用节点。用贪心算法快速生成接近最优的初始阈值:

float greedy_threshold = 0.0f;
std::vector<bool> used(size, false);
for (int i = 0; i < size; ++i) {
    float min_dist = std::numeric_limits<float>::max();
    int best_j = -1;
    for (int j = 0; j < size; ++j) {
        if (!used[j] && distances[i*size + j] < min_dist) {
            min_dist = distances[i*size + j];
            best_j = j;
        }
    }
    greedy_threshold += min_dist;
    used[best_j] = true;
}
threshold = greedy_threshold;

初始阈值更接近最优值,能大幅减少队列中的节点数量。

5. 去掉不必要的浮点运算

最小化总距离和与最小化总距离平方和的结果一致(平方根是单调递增函数),可以去掉std::sqrt,用平方距离计算:

float distance_sq(const Vector2& v1, const Vector2& v2) {
    float dx = v1.x - v2.x;
    float dy = v1.y - v2.y;
    return dx*dx + dy*dy;
}

所有距离计算用平方值,仅在最终输出时按需计算平方根。

6. 调整优先级队列比较逻辑

当前CustomCompare中的std::move是多余的,直接传引用即可:

struct CustomCompare {
    bool operator()(const Node& a, const Node& b) {
        return (std::get<0>(a) + std::get<1>(a)) > (std::get<0>(b) + std::get<1>(b));
    }
};

避免不必要的移动操作。

7. 内存安全优化

栈数组float distances[size * size];在N较大时可能溢出,换成std::vector<float>更安全,性能几乎无差别:

std::vector<float> distances(size * size);

替代方案:匈牙利算法

如果N超过20,分支定界可能仍不够快,此时可以用匈牙利算法,它专门解决这类分配问题,时间复杂度为O(N³),效率远高于分支定界。

正确输出示例

Optimal final positions:
(-4, 5) (-3, 5) (-2, 5) (-1, 5) (0, 5) (1, 5) (2, 5) (3, 5) (4, 5) (5, 5)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 03:47:03