邊的權值之和最小的生成樹Minimum-Spanning-Tree
假設G=(V, E)是一個帶權連通無向圖,U是頂點集V的一個非空子集。若(u, v)是一條具有最小權值的邊,其中u∈U,v∈V-U,則必存在一棵包含邊(u, v)的最小生成樹。
任意一個生成樹T,添加一條邊e則會產(chǎn)生回路,刪除該回路中的任一條邊,則又恢復為生成樹,若e小于刪除的那條邊,則得到一個更小的生成樹。
在建立的時候,所添加的邊都為避免生成回路的可添加的邊中最小的,則最后生成的生成樹是不可改進的。任意邊換進去得到的回路中,換進去的那條邊一定是最大的。
采用貪心算法
GENERIC_MST(G)
{
T=NULL;
while T未形成一棵生成樹;
do 找到一條最小代價邊(u, v)并且加入T后不會產(chǎn)生回路;
T=TU(u, v);
}
Prim
初始化:向空樹T=(VT, ET)中添加圖G=(V, E)的任一頂點u0,使VT={u0},ET=空集
循環(huán)(重復下列操作至VT=V):從G中選擇滿足{(u, v)|u∈VT,v∈V-VT}且具有最小權值的邊(u, v),把v加入VT,這條邊加入ET
時間復雜度O(|V|^2)不依賴于|E|,因此適用于求解邊稠密的圖的最小生成樹。
struct{
VertexType adjvex; //與哪個VT中的點相連
VRType lowcost; //權值
}closeedge[MAX_SIZE];
//數(shù)組下標與圖的點數(shù)組下標一致
void MiniSpanTree(MGraph G, VertexType u)
{
k=LocateVex(G, u);
for(j=0;j<G.vexnum;++j)
if(j!=k)
closeedge[j]={u, G.arcs[k][j];
closeedge[k].lowcost=0;
for(i=0;i<G.vexnum;++i)
{
k=minimum(closeedge); //下一個要加入生成樹的點
printf(closeedge[k].adjvex, G.vexs[k]); //輸出生成樹上一條邊
closeedge[k].lowcost=0; //將k加入VT
for(j=0;j<G.vexnum;++j) //修改其它頂點的最小邊
if(G.arcs[k][j]<closeedge[j].lowcost)
closeedge[j]={G.vexs[k], G.arcs[k][j]}
}
Kruskal
Prim是選頂點,Kruskal是選邊
初始化:VT=V,ET=空集,每個頂點構成一棵獨立的樹
循環(huán)(重復下列操作至T是一棵樹):按G的邊的權值遞增順序依次從E-ET中選擇一條邊,若這條邊加入T后不構成回路,則加入ET,知道ET中有n-1條邊。
用類似于并查集的算法,通常采用堆來存放邊的集合,每次選擇最小權值的邊只需O(log|E|)
時間復雜度O(|E|log|E|),適用于邊稀疏而頂點較多的圖。
Typedef struct{
int vi, vj, w;
}edgeset[MAX_SIZE_E];
int set[MAX_SIZE_V];
int FindSet(int set[], int v)
{
i=v;
while(set[i]>0) i=set[i];
return i;
}
void krusal(edgeset get, int n, int e)
{
//get為權按從小到大到順序排序的邊集數(shù)組
for(i=0;i<=n;++i) set[i]=0;
i=1; //get中的下標
j=1; //生成樹中的邊數(shù)
while(j<n)//一共要獲得n-1條邊
{
v=FindSet(set, get[i].vi);
w=FindSet(set, get[i].vj);
if(v!=w){
//vi,vj不在同一集合,不會構成回路
cout<<get[i].vi<<","<<get[i].vj<<endl;
set[v]=w;
++j;
}
++i;
}
}