我的Data Structures and Algorithms代码仓:https://github.com/617076674/Data-Structures-and-Algorithms
原题链接:https://pintia.cn/problem-sets/16/problems/681
题目描述:
知识点:最小生成树问题、prim算法、kruskal算法
思路一:prim算法
本题的本质是最小生成树问题,对除去第i个顶点(i = 1, 2, ... , N)后的N - 1个顶点求其最小生成树。对于Status == 1的路径,其边权为0,对Status == 0的路径,其边权就是Cost。
prim算法的具体实现:
prim算法需要实现两个关键的概念,即集合S的实现、顶点Vi(1 <= i <= N)与集合S的最短距离。
(1)集合S的实现方法和Dijkstra中相同,即使用一个bool型数组visited[]表示顶点是否已被访问。其中visited[i] == true表示顶点Vi已被访问,visited[i] == false表示顶点Vi未被访问。
(2)令int型数组d[]来存放顶点Vi(1 <= i <= N)与集合S的最短距离。初始时除了起点s的d[s]赋为0,其余顶点都赋为一个很大的数来表示INF,即不可达。
注意:如果去掉某个顶点后剩余的城市不连通,需要输出一个很大的数来表示INF,这样表示该城市一旦失陷,付出再大的代价也无法使得剩余图保持连通,测试点3就是这样一组测试用例。
时间复杂度是O(N ^ 2)。空间复杂度是O(N + M)。
C++代码:
#include<iostream>
#include<vector>
using namespace std;
struct node {
int v, cost;
node(int _v, int _cost) {
v = _v;
cost = _cost;
}
};
int N, M, City1, City2, Cost, Status, lost, INF = 1000000000;
vector<node> graph[501];
int costs[501], d[501];
bool visited[501];
int prim();
int main() {
scanf("%d %d", &N, &M);
fill(costs + 1, costs + N + 1, 0);
for(int i = 0; i < M; i++) {
scanf("%d %d %d %d", &City1, &City2, &Cost, &Status);
if(Status == 1) { //如果这条高速公路未损坏,则其费用为0
Cost = 0;
}
graph[City1].push_back(node(City2, Cost));
graph[City2].push_back(node(City1, Cost));
}
for(int i = 1; i <= N; i++) {
lost = i;
costs[i] = prim();
}
int maxIndex = 1; //求出最大花费的下标
for(int i = 2; i <= N; i++) {
if(costs[i] > costs[maxIndex]) {
maxIndex = i;
}
}
if(costs[maxIndex] == 0) { //如果最大花费为0,直接输出0并return 0
printf("0\n");
return 0;
}
vector<int> maxCosts; //存储最大花费的丢失的城市的索引
for(int i = 1; i <= N; i++) {
if(costs[i] == costs[maxIndex]) {
maxCosts.push_back(i);
}
}
for(int i = 0; i < maxCosts.size(); i++) { //输出结果
printf("%d", maxCosts[i]);
if(i != maxCosts.size() - 1) {
printf(" ");
} else {
printf("\n");
}
}
return 0;
}
int prim() {
fill(visited + 1, visited + 501, false); //所有点都未被访问过
fill(d + 1, d + 501, INF);
if(lost == 1) {
d[2] = 0;
} else {
d[1] = 0;
}
int ans = 0;
for(int i = 0; i < N - 1; i++) {
int u = -1, min = INF;
for(int j = 1; j <= N; j++) {
if(!visited[j] && d[j] < min && j != lost) {
u = j;
min = d[j];
}
}
if(u == -1) {
return INF; //如果找不到小于INF的d[u],则剩下的顶点和集合S不连通,此时的花费是最高的
}
visited[u] = true;
ans += d[u];
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v;
int len = graph[u][j].cost;
if(!visited[v] && v != lost && len < d[v]) {
d[v] = len;
}
}
}
return ans;
}
C++解题报告:
思路二:kruskal算法(测试点4和5会超时)
kruskal算法的基本思想为:在初始状态隐去图中的所有边,这样图中每个顶点都自成一个连通块。之后执行下面的步骤:
(1)对所有边按边权从小到大进行排序。
(2)按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在一个连通块中,则把这条测试边加入当前最小生成树中;否则,将边舍弃。
(3)执行步骤(2),直到最小生成树中的边数等于总顶点数减1或是测试完所有边时结束。而当结束时如果最小生成树的边数小于总顶点数减1,说明该图不连通,此时需要返回一个表示无穷大的数INF。
时间复杂度是O(MlogM)。空间复杂度是O(M)。
C++代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct edge {
int u, v, cost;
edge(int _u, int _v, int _cost) {
u = _u;
v = _v;
cost = _cost;
}
};
int N, M, City1, City2, Cost, Status, lost, INF = 1000000000;
vector<edge> edges;
int costs[501], father[501];
int kruskal();
int findFather(int x);
bool cmp(edge e1, edge e2);
int main() {
scanf("%d %d", &N, &M);
fill(costs + 1, costs + N + 1, 0);
for(int i = 0; i < M; i++) {
scanf("%d %d %d %d", &City1, &City2, &Cost, &Status);
if(Status == 1) { //如果这条高速公路未损坏,则其费用为0
Cost = 0;
}
edges.push_back(edge(City1, City2, Cost));
}
for(int i = 1; i <= N; i++) {
lost = i;
costs[i] = kruskal();
}
int maxIndex = 1; //求出最大花费的下标
for(int i = 2; i <= N; i++) {
if(costs[i] > costs[maxIndex]) {
maxIndex = i;
}
}
if(costs[maxIndex] == 0) { //如果最大花费为0,直接输出0并return 0
printf("0\n");
return 0;
}
vector<int> maxCosts; //存储最大花费的丢失的城市的索引
for(int i = 1; i <= N; i++) {
if(costs[i] == costs[maxIndex]) {
maxCosts.push_back(i);
}
}
for(int i = 0; i < maxCosts.size(); i++) {
printf("%d", maxCosts[i]);
if(i != maxCosts.size() - 1) {
printf(" ");
} else {
printf("\n");
}
}
return 0;
}
int kruskal() {
int ans = 0, num_edge = 0;
for(int i = 1; i <= N; i++) { //并查集的初始化
father[i] = i;
}
sort(edges.begin(), edges.end(), cmp);
for(int i = 0; i < edges.size(); i++){
int u = edges[i].u;
int v = edges[i].v;
if(u == lost || v == lost){
continue;
}
int faU = findFather(u);
int faV = findFather(v);
if(faU != faV){
father[faU] = faV;
ans += edges[i].cost;
num_edge++;
if(num_edge == N - 2){
break;
}
}
}
if(num_edge != N - 2){
return INF;
}
return ans;
}
int findFather(int x) {
int a = x;
while(x != father[x]) {
x = father[x];
}
while(a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
bool cmp(edge e1, edge e2){
return e1.cost < e2.cost;
}
C++解题报告: