Weighted graphs

Weighted graph representation

In order to extend the representation of graphs to weighted graphs, we extend adjacency lists to store not just the destination node of each arc, but also its weight (e.g., a distance or a cost), which we will be considering a real value:

using node = int;                           // nodes are represented by indices from 0
using weight = double;                      // weights are real numbers
using arc = pair<weight, node>;             // an arc is represented by its weight and its destination node
using WAdjacencyList = vector<arc>;         // the adjacency list of one node (set of arcs to successor nodes)
using WGraph = vector<WAdjacencyList>;      // the complete adjacency list for all nodes in the graph

The reason to use the weight as the first element of the arc pair is that it will determine the ordering of the pair, which will be useful in many subsequent algorithms.

As an exemple, the following weighted graph

Layout:





can be defined as

Graph G = {
    {{10, 1}, {8, 7}},      // 0
    {{2.5, 5}},             // 1
    {{1, 1}, {1, 3}},       // 2
    {{4, 3}},               // 3
    {{1, 5}},               // 4
    {{-2, 2}},              // 5
    {{1, 5}, {4, 1}},       // 6
    {{1, 6}},               // 7
};

Observe that we do not add contraints on the order of the nodes in the adjacency lists —even though this could make sense in some settings. However, we do not allow repetitions (this would mean multiple arcs) nor self-loops.

In the following, $n$ denotes the number of nodes of a graph, and $m$ denotes its number of arcs.

Furthermore, the infinity constant will be used to denote the infinity value for weights, which is also quite handy in many algorithms:

const weight infinity = numeric_limits<weight>::infinity();

The infinity value is legal for double variables and has all the nice properties of the ∞ symbol.

As we did the lesson on graphs, we will use the None constant to denote spectial integer values:

const int None = -1;

Shortest paths with Dijkstra’s algorithm

The following code implements Dijkstra’s algorithm in order to compute the shortest paths from one source node to all the nodes in a directed graph with non-negative weights on the arcs.

The input of dijkstra() is a weighted graph G with non-negative weights on the arcs, and a source node s from G. Its output are two vectors d and p. For each node u in G, d[u] is the minimum distance from s to u (or ∞ if u is not reachable from s) and p[u] is the parent node of u, that is the node from which u should be reached in order to get the minimum distance d[u]. We have that p[u] is None if u in unreachable from s or when u == s.

During the execution of the algorithm we maintain the subset S ⊆ V of vertices for which their minimum distance from s computed so far is the optimal one.

void dijkstra(const WGraph& G, node s, vector<weight>& d, vector<node>& p) {
    int n = G.size();                       // number of nodes in the graph
    d = vector<weight>(n, infinity);        // for each node, its minimum distance from s
    d[s] = 0;
    p = vector<node>(n, None);              // for each node, its parent node
    vector<bool> S(n, false);               // subset of nodes with optimal distance
    priority_queue<arc, vector<arc>, greater<arc>> pq;
    pq.push({0, s});
    while (not pq.empty()) {
        node u = pq.top().second;
        pq.pop();
        if (not S[u]) {
            S[u] = true;
            for (Arc a : G[u]) {
                weight c = a.first;
                node v = a.second;
                if (d[v] > d[u] + c) {
                    d[v] = d[u] + c;
                    p[v] = u;
                    pq.push({d[v], v});
}   }   }   }   }

The cost of Dijkstra’s algorithm is $\O(n+m\log m)$ in the worst case.

TBD: Example

TBD: Decidir si p i d són paràmetres o es retornen amb una pair.

Shortest paths with Bellman–Ford’s algorithm

The following code implements Bellman–Ford’s algorithm in order to compute the shortest paths from one source node to all the nodes in a directed graph with possible negative weights on the arcs but no negative cycles.

The input of bellman_ford() is a weighted graph G with no negative cycles, and a source node s from G. Its output are two vectors d and p. For each node u in G, d[u] is the minimum distance from s to u (or ∞ if u is not reachable from s) and p[u] is the parent node of u, that is the node from which u should be reached in order to get the minimum distance d[u]. We have that p[u] is -1 if u in unreachable from s or when u == s.

The cost of Bellman–Ford’s algorithm is $\O(nm)$ in the worst case.

void bellman_ford (const WGraph& G, node s, vector<weight>& d, vector<node>& p) {
    int n = G.size();                   // number of nodes in the graph
    d = vector<weight>(n, infinity);    // for each node, its minimum distance from s
    d[x] = 0;
    p = vector<node>(n, None);          // for each node, its parent node

    for (int i = 0; i < n - 1; ++i) {
        for (node u = 0; u < n; ++u) {
            for (arc a : G[u]) {
                weight c = a.first;
                node v = a.second;
                if (d[v] > d[u] + c) {
                    d[v] = d[u] + c;
                    p[v] = u;
}   }   }   }   }

TBD: Example

TBD: Decidir si p i d són paràmetres o es retornen amb una pair.

Minimum spanning tree with Prim’s algorithm

The following code implements Prims’s algorithm in order to compute a minimum spanning tree of a connected undirected graph with edge weights.

TBD: Decidir què es retorna: el pes de l’arbre?, les arestes de l’arbre?, les dues coses?. I, llavors, si són paràmetres o retornats.

The input of prim() is a connected undirected graph G with edge weights. Its output is a minimum spanning tree of G, encoded in a a vector p so that, for each node u in G, p[u] is the parent node of u in the tree (and the tree root has parent -1).

The cost of Prim’s algorithm is $\O(m\log m)$ in the worst case.

weight prim (const Graph& G) {
    int n = G.size();                       // number of nodes in the graph
    weight sum = 0;                         // sum of the weights of the tree edges
    vector<bool> S(n, false);               // for each node, tells if it has been included in the tree
    priority_queue<arc, vector<arc>, greater<arc>> pq;
    pq.push({0, 0});
    while (not pq.empty()) {
        weight c = pq.top().first;
        node u = pq.top().second;
        pq.pop();
        if (not S[u]) {
            sum += c;
            S[u] = true;
            for (arc a : G[u]) {
                node v = a.second;
                if (not S[v]) {
                    pq.push(a);
    }   }   }   }
    return sum;
}

TBD: Example

Minimum spanning tree with Kruskal’s algorithm

The following code implements Kruskal’s algorithm in order to compute the cost of a minimum spanning tree of a connected undirected graph with edge weights.

TBD: Decidir què es retorna: el pes de l’arbre?, les arestes de l’arbre?, les dues coses?. I, llavors, si són paràmetres o retornats.

The input of kruskal() is a connected undirected graph G with edge weights.

The implementation uses Disjoint sets.

The cost of Kruskal’s algorithm is $\O(m\log m)$ in the worst case.

weight kruskal (const Graph& G) {
    int n = G.size();

    // Sort all edges by their weight
    using edge = pair<weight, pair<node, node>>;
    vector<edge> edges;
    for (node u = 0; u < n; ++u) {
        for (arc a : G[u]) {
            edges.push_back({a.first, {u, a.second}});
    }   }
    sort(edges.begin(), edges.end());

    // Add each edge if it is not creating a cycle
    DisjointSets p(n);
    weight sum = 0;
    for (edge e : edges) {
        weight c = e.first;
        node u = e.second.first;
        node v = e.second.second;
        if (p[u] != p[v]) {
            p.merge(u, v);
            sum += c;
    }   }
    return sum;
}

TBD: Example


Fòrum







Lliçons.jutge.org
Jordi Petit
Universitat Politècnica de Catalunya, 2018

Prohibit copiar. Tots els drets reservats.
No copy allowed. All rights reserved.