简体   繁体   中英

Placing electric stations NP Hard problem

We have a problem (stated to be NP Hard) as follows:

Given a connected undirected unweighted graph G = (V, E) and a set of routes (a sequence of vertices) from homes to offices (there can be multiple routes starting from homes, and multiple routes ending at offices), we want to place minimum electric charging stations such that all routes are covered at least once. We cannot place stations at homes or offices.

The below image shows a possible input (please ignore the weights): 车站输入

Where the house icons are homes and brick wall icons are offices.

The optimal above is placing stations at vertices R and D .

Finding the optimal solution is said to be an NP Hard problem.

I have an approach, which goes something like this:

Let S be empty set of charging stations (vertices)
Let R be set of all given routes, r_i = <v_0, v_1 ... v_j> where v_0 is of type "home" and v_j is of type "office"
Let Vdeg be array of integers of size |V| intially set to 0
For r in R:
    Increment Vdeg of every vertex v in r
while R is not empty:
    Sort vertices by decreasing Vdeg
    Let maxDeg be maximum degree vertex in V
    Add maxDeg to S
    for all routes r in R which contains maxDeg
        R = R\{r}
        Decrement Vdeg for maxDeg

The above algorithm runs in (I believe) polynomial time, and also (I believe) gives the optimal solution.

The full implementation is given below:

#include <iostream>
#include <vector>

// A Graph Vertice
struct node {
    int id;
    std::string type;
    std::string name;
};

std::ostream& operator <<(std::ostream& out, const node& n) {
    return out << "{ " << n.type << ", " << n.name << " }";
}

// A Graph Edge
struct edge {
    node * from;
    node * to;
    int weight;
};

std::ostream& operator <<(std::ostream& out, const edge& e) {
    return out << "{ " << *e.from << ", " << *e.to << ", " << e.weight << " }";
}

std::ostream& operator <<(std::ostream& out, const std::vector<edge>& graph) {
    out << "{ ";
    for(int i = 0; i < graph.size(); ++i) {
        out << graph[i] << ", ";
    }
    return out << "\b\b }";
}

std::ostream& operator <<(std::ostream& out, const std::vector<node*>& vertices) {
    out << "{ ";
    for(int i = 0; i < vertices.size(); ++i) {
        out << "\n  " << *vertices[i] << ", ";
    }
    return out << "\b\b \n}";
}

std::ostream& operator <<(std::ostream& out, const std::vector<edge*>& graph) {
    out << "{ ";
    for(int i = 0; i < graph.size(); ++i) {
        out << "\n  " << *graph[i] << ", ";
    }
    return out << "\b\b \n}";
}

std::ostream& operator <<(std::ostream& out, const std::vector<std::vector<edge*>>& graph) {
    out << "{";
    for(int i = 0; i < graph.size(); ++i) {
        out << "\n  " << graph[i] << ", ";
    }
    return out << "\b\b \n}";
}

// A Vdeg which also stores id and routes intersecting on it
struct vertice_degrees {
    int id;
    int degree;
    std::vector<int> intersecting;
};

// Greater than operator overloaded for sorting Vdegs by degree
bool operator >(const vertice_degrees& vd1, const vertice_degrees& vd2) {
    return vd1.degree > vd2.degree;
}

std::ostream& operator <<(std::ostream& out, const std::vector<vertice_degrees>& vd) {
    out << "{ ";
    for(int i = 0; i < vd.size(); ++i) {
        out << "{ " << vd[i].id << ", " << vd[i].degree << ", { ";
        for(int j = 0; j < vd[i].intersecting.size(); ++j) {
            out << vd[i].intersecting[j] << ", ";
        }
        out << "\b\b }, ";
    }
    return out << "\b\b }";
}

void print_routes(const std::string label, const std::vector<int> routes_to_cover, const std::vector<std::vector<edge*>> routes, const std::string label_after) {
    std::cout << label << "{ ";
    for(int i = 0; i < routes_to_cover.size(); ++i) {
        std::cout << routes[routes_to_cover[i]] << ", ";
    }
    std::cout << "\b\b }" << label_after;
}

int main() {
        
    // List of vertices in graph
    node vertices[] = {
        node{ 0, "House", "A" },
        node{ 1, "House", "B" },
        node{ 2, "House", "F" },
        node{ 3, "House", "N" },
        node{ 4, "House", "S" },
        node{ 5, "Office", "H" },
        node{ 6, "Office", "K" },
        node{ 7, "Office", "L" },
        node{ 8, "Office", "P" },
        node{ 9, "Office", "T" },
        node{ 10, "None", "C" },
        node{ 11, "None", "D" },
        node{ 12, "None", "E" },
        node{ 13, "None", "G" },
        node{ 14, "None", "I" },
        node{ 15, "None", "J" },
        node{ 16, "None", "M" },
        node{ 17, "None", "O" },
        node{ 18, "None", "Q" },
        node{ 19, "None", "R" },
        node{ 20, "None", "U" }
    };
    // Length of vertices array
    const int vertice_count = sizeof(vertices)/sizeof(node);

    // List of edges in graph
    std::vector<edge> graph = {
        edge{ &vertices[0], &vertices[12], 3 },
        edge{ &vertices[12], &vertices[15], 3 },
        edge{ &vertices[15], &vertices[19], 4 },
        edge{ &vertices[19], &vertices[20], 5 },
        edge{ &vertices[20], &vertices[9], 5 },
        edge{ &vertices[1], &vertices[10], 4 },
        edge{ &vertices[10], &vertices[11], 2 },
        edge{ &vertices[11], &vertices[5], 2 },
        edge{ &vertices[2], &vertices[12], 4 },
        edge{ &vertices[12], &vertices[14], 4 },
        edge{ &vertices[14], &vertices[11], 3 },
        edge{ &vertices[11], &vertices[13], 7 },
        edge{ &vertices[13], &vertices[6], 2 },
        edge{ &vertices[3], &vertices[19], 3 },
        edge{ &vertices[19], &vertices[16], 5 },
        edge{ &vertices[16], &vertices[17], 2 },
        edge{ &vertices[17], &vertices[7], 2 },
        edge{ &vertices[4], &vertices[19], 4 },
        edge{ &vertices[19], &vertices[17], 6 },
        edge{ &vertices[17], &vertices[18], 4 },
        edge{ &vertices[18], &vertices[8], 3 }
        // Remaining edges are not on routes, and hence are inconsequential and can be omitted
    };
    
    // List of routes
    std::vector<std::vector<edge*>> routes_arr = {
        std::vector<edge*>{ &graph[0], &graph[1], &graph[2], &graph[3], &graph[4] },
        std::vector<edge*>{ &graph[5], &graph[6], &graph[7] },
        std::vector<edge*>{ &graph[8], &graph[9], &graph[10], &graph[11], &graph[12] },
        std::vector<edge*>{ &graph[13], &graph[14], &graph[15], &graph[16] },
        std::vector<edge*>{ &graph[17], &graph[18], &graph[19], &graph[20] }
    };

    std::cout << routes_arr;

    // Vdeg list of size V, and initialised
    std::vector<vertice_degrees> vertice_degrees_arr;
    for(int i = 0; i < vertice_count; ++i) {
        vertice_degrees_arr.push_back(vertice_degrees{ i, 0 });
    }

    // Add all the routes the vertice is part of for each vertice in Vdeg
    for(int i = 0; i < routes_arr.size(); ++i) {
        for(int j = 0, k; j < routes_arr[i].size(); ++j) {
            vertice_degrees& vd = vertice_degrees_arr[routes_arr[i][j]->from->id];
            vd.degree += 1;
            for(k = 0; k < vd.intersecting.size(); ++k) {
                if(vd.intersecting[k] == i) {
                    continue;
                }
            }
            vd.intersecting.push_back(i);
        }
    }

    // routes_to_cover is remaining routes left to cover, list of route indexes
    std::cout << "\nFilled: \n" << vertice_degrees_arr;
    std::vector<int> routes_to_cover;
    for(int i = 0; i < routes_arr.size(); ++i) {
        routes_to_cover.push_back(i);
    }
    // Final resulting S
    std::vector<node*> stations_placed_at;

    // While there are routes still to cover
    while(routes_to_cover.size() > 0) {

        // Insertion Sort the Vdeg by degree
        for(int i = 1, j; i < vertice_degrees_arr.size(); ++i) {
            const vertice_degrees vd = vertice_degrees_arr[i];
            for(j = i; j > 0 && vertice_degrees_arr[j-1] > vd; --j) {
                vertice_degrees_arr[j] = vertice_degrees_arr[j-1];
            }
            vertice_degrees_arr[j] = vd;
        }

        // Get all the routes intersecting at v, add v to S
        std::vector<int> covered_routes = vertice_degrees_arr[vertice_degrees_arr.size()-1].intersecting;
        stations_placed_at.push_back(&vertices[ vertice_degrees_arr[vertice_degrees_arr.size()-1].id ]);
        std::cout << "\nSorted: \n" << vertice_degrees_arr;

        // Remove routes intersecting at v from all Vdeg, decrease degree for next iteration
        for(int i = 0; i < vertice_degrees_arr.size(); ++i) {
            for(int j = 0; j < vertice_degrees_arr[i].intersecting.size(); ++j) {
                for(int k = 0; k < covered_routes.size(); ++k)
                if(vertice_degrees_arr[i].intersecting[j] == covered_routes[k]) {
                    vertice_degrees_arr[i].intersecting.erase(vertice_degrees_arr[i].intersecting.begin() + j--);
                    vertice_degrees_arr[i].degree -= 1;
                    break;
                }
            }
        }

        std::cout << "\nCleaned:\n" << vertice_degrees_arr;

        // Remove covered routes this iteration from routes_to_cover
        for(int i = 0; i < routes_to_cover.size(); ++i) {
            for(int j = 0; j < covered_routes.size(); ++j) {
                if(routes_to_cover[i] == covered_routes[j]) {
                    routes_to_cover.erase(routes_to_cover.begin() + i--);
                    break;
                }
            }
        }

        print_routes("\nRemaining routes: ", routes_to_cover, routes_arr, "\n");
    }

    std::cout << "\nFinal Nodes: " << stations_placed_at;

    return 0;
}

So what's wrong with this solution?

Is it:

  1. Not polynomial? or
  2. Not optimal?

If the former, can you explain how?

If the latter, can you provide a counter?

Your algorithm is not optimal, as can be seen by reduction from the exact 3-cover problem .

To see that, let's start with a number of homes that is a multiple of 3. Add a single office. And for each 3-set in our cover we add one vertex with routes from the 3 homes in the 3-set to this vertex, then to the office. It is easy to verify that if an exact 3-cover exists, the set of vertices associated with that exact 3-cover is an optimal arrangement of gas stations. So your problem requires being able to find exact 3-covers.

But your algorithm is greedy and its very first choice is a random guess with no way to decide that any vertex is better than any other. If it first guesses a 3-set that is not in any exact 3-cover, then you won't find the optimal solution.

Okay, after reading up on Exact 3 set cover problem and the helpful answer by @btilly , I have finally been able to come up with a counter to my algorithm.

The intuition is that the algorithm works for inputs which don't have the same degree. Hence, try making the same degree.

The counter is as follows.

Given below graph:

输入图

With the following routes:

R1: A, C, F, J, L
R2: B, C, D
R3: E, F, H, K
R4: I, J, H, G

The optimal is selecting the vertices C and H like so:

最优解

But if our algorithm picks F first (as degrees are same) like so:

贪心解 Pt1

Then we are forced to pick C and one of H or J as follows:

贪心解 Pt2

Which is obviously not the optimal solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM