在一个赋权连通图中,有一条路径将图中所有的结点连通并且权的和加起来最小, 在这条路径中所有边构成的就是最小生成树。
Prim算法的思想其实跟Dijkstra算法基本一样,甚至在执行贪心策略的时候判断条件更为简单,只要求获得到目标点的最小权值而无须加上从出发点自身带有的权值。
我们以codevs上1231为例:http://codevs.cn/problem/1231/
其中题目中的n达到了100000, 意味着在dijkstra算法中简陋的以线性查找为基础的优先队列肯定会TLE,我们必须使用二叉堆的优先队列找出队列中权值最小的点。
其中我先给出WA的代码:
#include<stdio.h>
#include<stdlib.h>
#define INT_MAX 1000000
#define MAX 100000
typedef struct computer{
int number;
int weight;
struct computer *next;
}computer;
typedef struct HNode{
int number;
int weight;
}HNode;
typedef struct {
HNode container[MAX + 1];
int size;
}Heap;
Heap temp;
int known[MAX + 1];
void Insert(computer *result, int first, int second, int cost){
computer *start = &result[first];
computer *newNode = (computer *)malloc(sizeof(computer));
newNode->number = second;
newNode->weight = cost;
newNode->next = NULL;
while(start->next && start->number != second){
start = start->next;
}
if(start->number == second){
if(start->weight > cost){
start->weight = cost;
}
}
else{
start->next = newNode;
}
}
void EnHeap(int number, int weight){
int i;
temp.size++;
for(i = temp.size;temp.container[i / 2].weight > weight;i /= 2){
temp.container[i].number = temp.container[i / 2].number;
temp.container[i].weight = temp.container[i / 2].weight;
}
temp.container[i].number = number;
temp.container[i].weight = weight;
}
int DelMin(){
int i, child;
int lastNum = temp.container[temp.size].number;
int lastWei = temp.container[temp.size].weight;
int ret = temp.container[1].number;
temp.size--;
for(i = 1;i * 2 <= temp.size;i = child){
child = 2 * i;
if(child != temp.size && temp.container[child + 1].weight < temp.container[child].weight){
child++;
}
if(temp.container[child].weight < lastWei){
temp.container[i].number = temp.container[child].number;
temp.container[i].weight = temp.container[child].weight;
}
else{
break;
}
}
temp.container[i].number = lastNum;
temp.container[i].weight = lastWei;
return ret;
}
int Prim(computer *result, int n){
temp.size = 0;
temp.container[0].weight = -1;
result[1].weight = 0;
int number;
computer *adajacent;
EnHeap(1, 0);
while(temp.size != 0){
number = DelMin();
known[number] = 1;
adajacent = result[number].next;
while(adajacent){
if(!known[adajacent->number]){
if(adajacent->weight < result[adajacent->number].weight){
result[adajacent->number].weight = adajacent->weight;
}
EnHeap(adajacent->number, result[adajacent->number].weight);//WA的原因
}
adajacent = adajacent->next;
}
}
}
int main(void){
int n, m;
scanf("%d%d", &n, &m);
int i;
computer *result = (computer *)malloc(sizeof(computer) * (n + 1));
for(i = 1;i <= n;i++){
result[i].number = i;
result[i].weight = INT_MAX;
result[i].next = NULL;
known[i] = 0;
}
int first, second, cost;
for(i = 1;i <= m;i++){
scanf("%d%d%d", &first, &second, &cost);
Insert(result, first, second, cost);
Insert(result, second, first, cost);
}
Prim(result, n);
long long sum = 0;
for(i = 1;i <= n;i++){
sum += result[i].weight;
}
printf("%lld", sum);
return 0;
}
以上的代码里二叉堆的插入和删除是没有错误的,那么为什么WA呢?
注意在图论二中我给出的代码里是用一个Path全局数组来记录最短路径的,也就是说当我使用贪心策略更新最短路径的值的时候,在队列中也会反映这个变化,但是我们使用二叉堆的时候是构造一个新的值代表结点,每当贪心策略找到同一个点更低权值的时候也会有一个全新的点入堆,这样子的做法就意味着在二叉堆中将会出现两个number值相同,而weight值不同的情况。
为了避免这种情况,我们在结点入堆的时候首先要检测number值是否在队列,如果在队列中,我们在替换更小的权值之后还要维护堆的性质。
下面给出AC的代码:
#include<stdio.h>
#include<stdlib.h>
#define INT_MAX 10000000
#define MAX 100000
typedef struct computer{
int number;
int weight;
struct computer *next;
}computer;
typedef struct HNode{
int number;
int weight;
}HNode;
typedef struct {
HNode container[MAX + 1];
int size;
}Heap;
Heap temp;
int known[MAX + 1];
void Insert(computer *result, int first, int second, int cost){
computer *start = &result[first];
computer *newNode = (computer *)malloc(sizeof(computer));
newNode->number = second;
newNode->weight = cost;
newNode->next = NULL;
while(start->next && start->number != second){
start = start->next;
}
if(start->number == second){
if(start->weight > cost){
start->weight = cost;
}
}
else{
start->next = newNode;
}
}
void EnHeap(int number, int weight){
int i, index;
for(i = 1;i <= temp.size;i ++){
if(temp.container[i].number == number){
if(temp.container[i].weight > weight){
temp.container[i].weight = weight;
for(index = i;temp.container[index / 2].weight > weight;index /= 2){
temp.container[index].number = temp.container[index / 2].number;
temp.container[index].weight = temp.container[index / 2].weight;
}
temp.container[index].number = number;
temp.container[index].weight = weight;
}
return ;
}
}
temp.size++;
for(i = temp.size;temp.container[i / 2].weight > weight;i /= 2){
temp.container[i].number = temp.container[i / 2].number;
temp.container[i].weight = temp.container[i / 2].weight;
}
temp.container[i].number = number;
temp.container[i].weight = weight;
}
int DelMin(){
int i, child;
int lastNum = temp.container[temp.size].number;
int lastWei = temp.container[temp.size].weight;
int ret = temp.container[1].number;
temp.size--;
for(i = 1;i * 2 <= temp.size;i = child){
child = 2 * i;
if(child != temp.size && temp.container[child + 1].weight < temp.container[child].weight){
child++;
}
if(temp.container[child].weight < lastWei){
temp.container[i].number = temp.container[child].number;
temp.container[i].weight = temp.container[child].weight;
}
else{
break;
}
}
temp.container[i].number = lastNum;
temp.container[i].weight = lastWei;
return ret;
}
int Prim(computer *result, int n){
temp.size = 0;
temp.container[0].weight = -1 * INT_MAX;
result[1].weight = 0;
int number;
computer *adajacent;
EnHeap(1, 0);
while(temp.size > 0){
number = DelMin();
known[number] = 1;
adajacent = result[number].next;
while(adajacent){
if(known[adajacent->number] == 0){
if(adajacent->weight < result[adajacent->number].weight){
result[adajacent->number].weight = adajacent->weight;
}
EnHeap(adajacent->number, result[adajacent->number].weight);
}
adajacent = adajacent->next;
}
}
}
int main(void){
int n, m;
scanf("%d%d", &n, &m);
computer *result = (computer *)malloc(sizeof(computer) * (n + 1));
int i;
for(i = 1;i <= n;i++){
result[i].number = i;
result[i].weight = INT_MAX;
result[i].next = NULL;
known[i] = 0;
}
int first, second, cost;
for(i = 1;i <= m;i++){
scanf("%d%d%d", &first, &second, &cost);
Insert(result, first, second, cost);
Insert(result, second, first, cost);
}
Prim(result, n);
long long sum = 0;
for(i = 1;i <= n;i++){
sum += result[i].weight;
}
printf("%lld", sum);
return 0;
}