Prim演算法及其優化
阿新 • • 發佈:2018-12-31
來源自我的部落格
#include <stdio.h>
int main(){
int n, m;
scanf("%d%d", &n, &m);
int e[10][10]; // 任意兩點間直接距離
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
if (i == j) e[i][j] = 0;
else e[i][j] = inf;
}
}
int t1, t2, t3;
for (int i = 1; i <= m; i++){
scanf("%d%d%d", &t1, &t2, &t3);
e[t1][t2] = t3;
e[t2][t1] = t3;
}
int dis[10]; // 生成樹到各個頂點的距離,初始只表示1號點到各個頂點的距離
for (int i = 1; i <= n; i++){
dis[i] = e[1][i];
}
int book[10] = {0}; // 為1表示頂點已加入生成樹
int count = 0; // 生成樹中頂點數量
int sum = 0; // 生成樹的權值和
// 將1號頂點加入生成樹
book[1] = 1;
count++;
// 核心部分,等待所有結點加入生成樹
while (count < n){
// 在未加入生成樹的結點中尋找離生成樹最近的結點
int min = INT_MAX;
for (int i = 1; i <= n; i++){
if (book[i] == 0 && dis[i] < min){
min = dis[i];
j = i;
}
}
// 將其加入生成樹
book[j] = 1;
count++;
sum += dis[j];
// 掃描當前頂點j所有的邊,以j為中間點,更新生成樹到每一個非樹頂點的距離
for (int k = 1; k <= n; k++){
if (book[k] == 0 && dis[k] > e[j][k]){
dis[k] = e[j][k];
}
}
}
printf("%d\n", sum);
return 0;
}
// 鄰接表儲存圖,用堆來選新邊
// 複雜度從O(N^2)變成O(MlogN)
#include <stdio.h>
#include <limits.h>
int heap[20], pos[20], size; // heap用來儲存堆,pos用來儲存每個頂點在堆中的位置,size表示堆大小
// 交換堆中兩個結點
void swap(int x, int y){
// 直接交換值
int t = heap[x];
heap[x] = heap[y];
heap[y] = t;
// 更新pos
t = pos[heap[x]];
pos[heap[x]] = pos[heap[y]];
pos[heap[y]] = t;
}
// 堆的向下調整函式
void siftDown(int i){
// i表示向下調整的堆結點編號
int flag = 1; // 為1表示需要繼續向下調整
while (i * 2 <= size && flag == 1){
int t = i;
// 如果兒子更小,則指向兒子
if (dis[heap[t]] > dis[heap[i * 2]]){
t = i * 2;
}
if (i * 2 + 1 <= size && dis[heap[t]] > dis[heap[i * 2 + 1]]){
t = i * 2 + 1;
}
// 判斷最小結點是當前結點還是在子結點上
if (t != i){
// 如果是在子結點上
swap(t, i); // 交換兩個結點
i = t; // 當前結點下沉
}
else{
// 如果當前就是最小結點,就不用繼續調整了
flag = 0;
}
}
}
// 堆向上調整函式
void siftUp(int i){
int flag = 1;
if (i == 1) return; // 堆頂不需要調整
while (i != 1 && flag == 1){
// 判斷是不是比父結點小
if (dis[heap[i]] < dis[heap[i / 2]]){
swap(i, i / 2);
}
else{
flag = 1;
}
i /= 2; // 上升到父結點
}
}
// 從堆頂取元素
int pop(){
int t = heap[1]; // 記錄堆頂元素
heap[1] = heap[size]; // 將最後一個結點提到堆頂
pos[heap[1]] = 1; // 更新pos中的位置資訊
size--;
siftDown(1);
return t;
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
int u[20], v[20], w[20];
for (int i = 1; i <= m; i++){
scanf("%d%d%d", &u[i], &v[i], &w[i]);
}
// 無向圖,所有邊反向儲存一次
for (int i = m + 1; i <= 2 * m; i++){
u[i] = v[i - m];
v[i] = u[i - m];
w[i] = w[i - m];
}
int first[20]; // 表示頂點對應的第一條鄰接邊, -1表示到頭了
for (int i = 1; i <= n; i++){
first[i] = -1;
}
// 把邊都接上
for (int i = 1; i <= 2 * m; i++){
next[i] = first[u[i]]; // 第i條邊頭插上去
first[u[i]] = i; // 使第i條邊為u[i]結點的第一條邊
}
int book[20] = {0}; // 為1表示已經加入生成樹
int count = 0;
int sum = 0;
// 加入1號頂點到生成樹
book[1] = 1;
count++;
int dis[20] = {0}; // 生成樹到各個頂點的初始距離,開始為1號頂點到各個頂點的初始距離
dis[1] = 0;
for (int i = 2; i <= n; i++) dis[i] = INT_MAX;
// 開始只要將1號結點的鄰接點距離更新到dis中即可
int k = first[1]; // 1號結點的第一條鄰接邊
while (k != -1){
dis[v[k]] = w[k];
k = next[k];
}
// 初始化堆
size = n;
for (int i = 1; i <= size; i++) {
h[i] = i; // 初始化堆每個結點儲存初始編號
pos[i] = i; // 每個圖結點在堆中初始位置
}
// 調整堆
for (int i = size / 2; i >= 1; i--){
siftDown(i); // 從第一個非葉結點向下調整
}
pop(); // 將堆頂的1號結點取出
// 核心部分,等待所有結點加入到生成樹
while (count < n){
int j = pop(); // 取出堆頂元素,因為其必然離生成樹最近,所以可以加入生成樹
book[j] = 1;
count++;
sum += dis[j];
// 掃描剛加入的結點j相鄰的邊,更新其到生成樹的距離
int k = first[j]; // j的第一條相鄰邊
while (k != -1){
if (book[v[k]] == 0 && dis[v[k]] > w[k]){
dis[v[k]] = w[k];
// 更新某點到生成樹距離後要在堆中向上調整
siftUp(pos[v[k]]);
}
k = next[k];
}
}
printf("%d\n", sum);
return 0;
}