1. 程式人生 > >最小生成樹模版與次小生成樹

最小生成樹模版與次小生成樹

prim演算法:

類似於dij演算法,將每個點距離已經加入的點的集合的最短距離算出,每次取出這些距離裡最短的點加入集合,並在每次加入新的點後更新這些距離即可。

演算法模版:

#define INF 32767;
void Prim(MGraph g,int v)
{
    int lowcost[MAXV];
    int min;
    //就是對應的元素下標的頂點的連線的上一個頂點
    int closest[MAXV];
    int i,k;
    for(i=0;i<g.n;i++)
    {
        lowcost[i] = g.edges[v][i];
        closest[i] = v;
    }
    for(i=1;i<g.n;i++)
    {
        min = INF;
        //先遍歷出lowcost裡面最小的拿出來
        for(j=0;j<g.n;j++)
        {
            if(lowcost[j]!=0 && lowcost[j]<min)
            {
                min = lowcost[j];
                k = j;
            }
        }
        printf("邊(%d,%d)權為:%d\n",closest[k],k,min);
        lowcost[k] = 0;
        //接下來做的就是把集合裡的倆個頂點跟集合外的頂點組成的邊的權值做比較
        for(j=0;j<g.n;j++)
        {
            if(g.edges[k][j]!=0 && lowcost[j]>g.edges[k][j])
            {
                lowcost[j] = g.edges[k][j];
                closest[j] = k;
            }
        }
    }
}

krustral演算法

先將所有的邊按照邊權值排序,之後利用並查集,檢查邊的兩個點是否被連通,若被連通則忽略,否則加入樹中。

#include"iostream"
#include"cstdio"
#include"cstring"

using namespace std;

typedef struct node
{
	int x,y;
	int weight;
}e;

e edge[100];
int fa[100];
int deep[100];
int n,m;
int sum=0;

void init()
{
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
		deep[i]=1;
	}
}

int find(int x)
{
	if(x==fa[x]) return x;
	else return fa[x]=find(fa[x]);
}

void unit(int x,int y)
{
	x=find(x);
	y=find(y);
	if(x==y) return ;
	else
	{
		if(deep[x]>deep[y]) fa[y]=x;
		else
		{
			fa[x]=y;
			if(deep[x]==deep[y]) deep[y]++;
		}
	}
}

bool same(int x,int y)
{
	return find(x)==find(y);
}

void quicksort(int left,int right)
{
	if(left>right) return ;
	else
	{
		int i=left;
		int j=right;
		e t;
		e temp=edge[left];
		while(i!=j)
		{
			while(i<j&&edge[j].weight>=temp.weight) j--;
			while(i<j&&edge[i].weight<=temp.weight) i++;
			if(i<j)
			{
				t=edge[i];
				edge[i]=edge[j];
				edge[j]=t;
			}
		}
		edge[left]=edge[i];
		edge[i]=temp;
		quicksort(left,i-1);
		quicksort(i+1,right);
		return ;
	}
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>edge[i].x>>edge[i].y>>edge[i].weight;
	init();
	quicksort(1,m);
	for(int i=1;i<=n-1;)
	{
		if(!same(edge[i].x,edge[i].y))
		{
			unit(edge[i].x,edge[i].y);
			sum+=edge[i].weight;
			i++;
		}
	}
	cout<<sum<<endl;
	return 0;
}

次小生成樹:

下面介紹一下利用prim求次小生成樹的主要步驟。

1.先求出來最小生成樹。並將最小生成樹任意兩點之間路徑當中的權值最大的那一條找出來,為什麼要找最大的呢,因為生成樹加入一條邊之後一定構成了迴路,那麼肯定要去掉這個迴路當中一條邊才是生成樹,那麼,怎麼去邊才是次小的,那就去掉除了剛剛新增的一條邊之外迴路當中權值最大的一個,所以留下的就是最小的。

2.列舉最小生成樹外的每一條邊。找出最小的就是次小生成樹。

POJ1679 The Unique MST

題意:給定圖,讓求它的最小生成樹是否唯一。如果唯一的話輸出最小生成樹的權值和,否則輸出Not Unique!

思路:直接求次小生成樹就行。


#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int maxn = 111;
const int inf = 0x3f3f3f3f;
int Map[maxn][maxn];//鄰接矩陣存圖
int Max[maxn][maxn];//表示最小生成樹中i到j的最大邊權
bool used[maxn][maxn];//判斷該邊是否加入最小生成樹
int pre[maxn];
int dis[maxn];
void init(int n)
{
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i == j) Map[i][j] = 0;
            else Map[i][j] = inf;
}
void read(int m)
{
    int u, v, w;
    for (int i = 0; i < m; i++)
    {
        scanf("%d %d %d", &u, &v, &w);
        Map[u][v] = Map[v][u] = w;
    }
}
int prim(int n)
{
    int ans = 0;
    bool vis[maxn];
    memset(vis, false, sizeof(vis));
    memset(used, false, sizeof(used));
    memset(Max, 0, sizeof(Max));
    for (int i = 2; i <= n; i++) 
    {
        dis[i] = Map[1][i];
        pre[i] = 1;
    }
    pre[1] = 0;
    dis[1] = 0;
    vis[1] = true;
    for (int i = 2; i <= n; i++)
    {
        int min_dis = inf, k;
        for (int j = 1; j <= n; j++)
        {
            if (!vis[j] && min_dis > dis[j])
            {
                min_dis = dis[j];
                k = j;
            }
        }
        if (min_dis == inf) return -1;//如果不存在最小生成樹
        ans += min_dis;
        vis[k] = true;
        used[k][pre[k]] = used[pre[k]][k] = true;
        for (int j = 1; j <= n; j++)
        {
            if (vis[j]) Max[j][k] = Max[k][j] = max(Max[j][pre[k]], dis[k]);
            if (!vis[j] && dis[j] > Map[k][j])
            {
                dis[j] = Map[k][j];
                pre[j] = k;
            }
        }
    }
    return ans;//最小生成樹的權值之和
}
int smst(int n, int min_ans)//min_ans 是最小生成樹的權值和
{
    int ans = inf;
    for (int i = 1; i <= n; i++)//列舉最小生成樹之外的邊
        for (int j = i + 1; j <= n; j++)
            if (Map[i][j] != inf && !used[i][j])
                ans = min(ans, min_ans + Map[i][j] - Max[i][j]);
    if (ans == inf) return -1;
    return ans;
}
void solve(int n)
{
    int ans = prim(n);
    if (ans == -1)
    {
        puts("Not Unique!");
        return;
    }
    if (smst(n, ans) == ans)
    printf("Not Unique!\n");
    else
    printf("%d\n", ans);
}
    
int main()
{
    int T, n, m;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d %d", &n, &m);
        init(n);
        read(m);
        solve(n);
    }
    return 0;
}