通过自定义边缘重量惩罚来提升A *访客?

时间:2015-09-03 09:30:51

标签: c++ boost a-star boost-graph boost-property-map

我正在使用boost A *算法,从以下示例开始:http://www.boost.org/doc/libs/1_37_0/libs/graph/example/astar-cities.cpp

我看到你可以覆盖它的启发式和它的访问者进行某种自定义调整,只是我还没有像以下那样得到这样的概念,作为一个学习的例子,我想如果旅行时间(边缘重量)大于X,则算法不选择边缘城市 - 城市,例如100分钟。 (只有在可能的情况下,如果没有找到其他路径,则应该选择该城市,而不是找不到路径)

我尝试了一个自定义启发式课程,它返回比现实更长的时间,“欺骗”它不选择那个城市,但问题是,通过这个技巧,惩罚的城市被丢弃,即使是进一步的互动。 (下面的例子解释了它:B-> D被丢弃,因为找到了更好的路径,但是城市D没有被丢弃(你看到它在接下来的迭代中被选中)

所以我进一步简化了问题:

enum nodes {
    A, B, C, D, E, N
  };
  const char *name[] = {
    "A", "B", "C", "D", "E", "F"
  };
edge edge_array[] = {
    edge(A,B), edge(B,C),
    edge(C,D), edge(D,E),
    edge(B,D) // B to D is the problem here, it should be excluded
  };
cost weights[] = { // estimated travel time (mins)
    // 107, 174, 283, 71, 436
    35, 58, 94, 23, 145
  };

通过这个例子(将原始代码作为基础),我得到了一条路线:

  

开始顶点:A

     

目标顶点:E

     

从A到E的最短路径:A - > B - > D - > ë

     

总旅行时间:204.5

问题是B - > D路径,这是一个如此长的距离(例如,假设阈值为100,这将是优选的路径,如:A - > B - > C - > D - > E,这样,距离两个城市之间的差距不超过100(当然只有在可能的情况下,如果没有其他路径,则必须选择)

我以次优的方式解决了这个问题:自定义函数一旦添加边缘,即(或手动设置权重)return travelTime > 100 ? travelTime * 2 : travelTime,可以通过以下方式实现测试:

cost weights[] = { // estimated travel time (mins)
    // 107, 174, 283, 71, 436
    (35 > 100) ? 35*2 : 35, (58 > 100) ? 58*2 : 58, (94>100) ? 94*2 : 94, 23, (145 > 100) ? 145*2 : 145
  }; // The comparisons are not needed, just there to better explain what the custom add_edge function will do.

使用这种方法,我得到了所需的A -> B -> C -> D -> E,但这样,只是一个黑客/解决问题并在内部修改输入数据,我认为这不是最好的解决方案。

有没有更好的方法来实现这一点,而无需手动更改距离/旅行时间?

2 个答案:

答案 0 :(得分:3)

您尝试的与启发式无关。 A *搜索算法是广度优先搜索的好处。启发式是将下限添加到最终成本中。对于做街道方向的地图,直线距离是完美的启发式。启发式的关键是确保您在最差的候选人之前扩展您最有可能的候选人。同样,在地图意义上,广度优先搜索基本上会向外旋转,直到找到目的地为止 - 而启发式搜索使得您可以直接渗透到目的地并减少值得考虑的路径。从不同的角度来看 - 启发式是一种函数,它获取路径和目标点中的当前最后一个点并返回成本。您不能使用它来排除边缘,因为它不知道路径。它只知道两点。

回到你的问题。你想要:

  

如果旅行时间(边缘重量)大于X,我希望算法不选择边缘城市 - 城市,例如100分钟。 (只有在可能的情况下,如果没有找到其他路径,则应该选择该城市,而不是找不到路径)

启发式方法 nothing 与特定的图形节点或边缘有关。它只是对最终成本的估计,可能不应该依赖于图表本身。你所要求的与权重有关。 A *就是找到最小权重路径。如果你想要一个边缘不被采取......只需增加它的重量!

您链接的示例包含以下权重:

cost weights[] = { // estimated travel time (mins)
  96, 134, 143, 65, 115, 133, 117, 116, 74, 56,
  84, 73, 69, 70, 116, 147, 173, 183, 74, 71, 124
};

你想要新的重量,基本上:

auto new_weights = at_most(weights, 100); // I don't want to use any edges
                                          // that take 100 minutes

我们可以这样写:

template <size_t N>
std::array<cost, N> at_most(cost (&old)[N], cost max_cost) {
    cost total = std::accumulate(old, old+N, 0.0f) * N;
    std::array<cost, N> new_weights;
    for (size_t i = 0; i < N; ++i) {
        new_weights[i] = old[i] < max_cost ? old[i] : old[i] + total;
    }
    return new_weights;
}

基本上,我们只是将所有权重相加并用新的权重替换所有边缘,这些边缘的成本大于您规定的最大值,新权重大于获取所有边缘。最终结果是,如果存在不使用任何&gt; 100权重边的路径,则它肯定具有比该新路径更低的总成本。我们使用的具体新重量并不重要,它只需要足够大以保证前一个陈述的真实性。

我们不需要改变启发式。只是重量。

答案 1 :(得分:1)

我认为你只想在这里找到最短的路径(dijkstra就好了)。

关键是要意识到您可以使用客户edge_weight属性地图。这可以是例如boost::property_map::transform_value_property_map<>喜欢这样:

auto my_custom_weight_map = 
    boost::make_transform_value_property_map(
            [](float w) { return w>100? 10*w : w; },
            boost::get(boost::edge_weight, g));

您会发现任何高于100的边缘重量都会增加十倍。

然后,你基本上已经完成了:

    astar_search_tree(g, start, 
            distance_heuristic<mygraph_t, cost>(goal),
            boost::weight_map(my_custom_weight_map) // THIS LINE ADDED
                .predecessor_map(make_iterator_property_map(p.begin(), get(boost::vertex_index, g)))
                .distance_map(make_iterator_property_map(d.begin(), get(boost::vertex_index, g)))
                .visitor(astar_goal_visitor<vertex>(goal))
        );

结果是:

Start vertex: A
Goal vertex: E
Shortest path from A to E: A -> B -> C -> D -> E
Total travel time: 210

<强> Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <iostream>
#include <list>

// auxiliary types
struct location {
    float y, x; // lat, long
};

typedef float cost;

// euclidean distance heuristic
template <class Graph, class CostType>
class distance_heuristic : public boost::astar_heuristic<Graph, CostType> {
  public:
    typedef typename boost::graph_traits<Graph>::vertex_descriptor Vertex;
    distance_heuristic(Vertex goal) : m_goal(goal) {}
    CostType operator()(Vertex /*u*/) {
        return 0; // Not really needed here
    }

  private:
    Vertex m_goal;
};

struct found_goal {}; // exception for termination

// visitor that terminates when we find the goal
template <class Vertex> class astar_goal_visitor : public boost::default_astar_visitor {
  public:
    astar_goal_visitor(Vertex goal) : m_goal(goal) {}
    template <class Graph> void examine_vertex(Vertex u, Graph &/*g*/) {
        if (u == m_goal)
            throw found_goal();
    }

  private:
    Vertex m_goal;
};

int main() {
    // specify some types
    typedef boost::adjacency_list<boost::listS, boost::vecS,
            boost::undirectedS, boost::no_property,
            boost::property<boost::edge_weight_t, cost> 
        > mygraph_t;

    typedef boost::property_map<mygraph_t, boost::edge_weight_t>::type WeightMap;
    typedef mygraph_t::vertex_descriptor vertex;
    typedef mygraph_t::edge_descriptor edge_descriptor;
    typedef std::pair<int, int> edge;

    enum nodes { A, B, C, D, E, N };
    const char *name[] = { "A", "B", "C", "D", "E", "F" };
    edge edge_array[] = {
        edge(A, B), edge(B, C), edge(C, D), edge(D, E), edge(B, D) // B to D is the problem here, it should be excluded
    };
    cost weights[] = { // estimated travel time (mins)
                       // 107, 174, 283, 71, 436
                       35, 58, 94, 23, 145
    };

    unsigned int num_edges = sizeof(edge_array) / sizeof(edge);

    // create graph
    mygraph_t g(N);
    WeightMap weightmap = get(boost::edge_weight, g);
    for (std::size_t j = 0; j < num_edges; ++j) {
        edge_descriptor e;
        bool inserted;
        boost::tie(e, inserted) = add_edge(edge_array[j].first, edge_array[j].second, g);
        weightmap[e] = weights[j];
    }

    // pick start/goal
    vertex start = A;
    vertex goal = E;

    std::cout << "Start vertex: " << name[start] << std::endl;
    std::cout << "Goal vertex: "  << name[goal]  << std::endl;

    std::vector<mygraph_t::vertex_descriptor> p(num_vertices(g));

    using boost::get;

    // do a real edit
    std::vector<cost> d(num_vertices(g));

    auto my_custom_weight_map = 
        boost::make_transform_value_property_map(
                [](float w) { return w>100? 10*w : w; },
                boost::get(boost::edge_weight, g));

    try {
        // call astar named parameter interface
        astar_search_tree(g, start, 
                distance_heuristic<mygraph_t, cost>(goal),
                boost::weight_map(my_custom_weight_map)
                    .predecessor_map(make_iterator_property_map(p.begin(), get(boost::vertex_index, g)))
                    .distance_map(make_iterator_property_map(d.begin(), get(boost::vertex_index, g)))
                    .visitor(astar_goal_visitor<vertex>(goal))
            );

    } catch (found_goal fg) { // found a path to the goal
        std::list<vertex> shortest_path;
        for (vertex v = goal;; v = p[v]) {
            shortest_path.push_front(v);
            if (p[v] == v)
                break;
        }

        std::cout << "Shortest path from " << name[start] << " to " << name[goal] << ": ";
        std::list<vertex>::iterator spi = shortest_path.begin();
        std::cout << name[start];

        for (++spi; spi != shortest_path.end(); ++spi)
            std::cout << " -> " << name[*spi];

        std::cout << std::endl << "Total travel time: " << d[goal] << std::endl;
        return 0;
    }

    std::cout << "Didn't find a path from " << name[start] << "to" << name[goal] << "!" << std::endl;
    return 0;
}