日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

图论 —— 二分图 —— KM 算法

發(fā)布時(shí)間:2025/3/17 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 图论 —— 二分图 —— KM 算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

【原理】

KM 算法是用于求帶權(quán)二分圖的最優(yōu)匹配的算法,其時(shí)間復(fù)雜度為 O(N^3)。

1.首先選擇頂點(diǎn)數(shù)較少的為 X 部(左點(diǎn)集),初始時(shí)對 X 部的每一個頂點(diǎn)設(shè)置頂標(biāo),頂標(biāo)的值為該點(diǎn)關(guān)聯(lián)的最大邊的權(quán)值,Y 部(右點(diǎn)集)的頂點(diǎn)頂標(biāo)為 0。

2.對于 X 部中的每個頂點(diǎn),在相等子圖中利用匈牙利算法找一條增廣路徑,如果沒有找到,則修改頂標(biāo),擴(kuò)大相等子圖,繼續(xù)找增廣路徑。

3.當(dāng) X 部的每個點(diǎn)都找到增廣路徑時(shí),此時(shí)意味著每個點(diǎn)都在匹配中,即找到了該二分圖的完全匹配。該完全匹配即為二分圖的最優(yōu)匹配。

【有關(guān)概念】

1)相等子圖:由于每個頂點(diǎn)有一個頂標(biāo),如果選擇邊權(quán)等于兩端點(diǎn)的頂標(biāo)之和的邊,它們組成的圖稱為相等子圖。

2)頂標(biāo):每個點(diǎn)的頂標(biāo)為該點(diǎn)關(guān)聯(lián)的最大邊的權(quán)值。

【頂標(biāo)的修改】

如果從 X 部中的某個點(diǎn) Xi 出發(fā)在相等子圖中沒有找到增廣路徑,則需要修改頂標(biāo)。

如果沒有找到增廣路徑,則一定找到了許多條從 Xi 出發(fā)并結(jié)束于 X 部的匹配邊與未匹配邊交替出現(xiàn)的路徑,即交錯路。

將交錯路中 X 部的頂點(diǎn)頂標(biāo)減去一個值 d,交錯路中屬于 Y 部的頂點(diǎn)頂標(biāo)加上一個值 d,那么會發(fā)現(xiàn):

  • 兩端都在交錯路中的邊(i,j),其頂標(biāo)和沒有變化,即:其原屬于相等子圖,現(xiàn)仍屬于相等子圖。
  • 兩端都不在交錯路中的邊(i,j),其頂標(biāo)也沒有變化,即:其原來屬于(或不屬于)相等子圖,現(xiàn)仍屬于(或不屬于)相等子圖。
  • X 端不在交錯路中,Y 端在交錯路中的邊(i,j),其頂標(biāo)和會增大,即:其原來不屬于相等子圖,現(xiàn)仍不屬于相等子圖。
  • X 端在交錯路中,Y 端不在交錯路中的邊(i,j),其頂標(biāo)和會減小,即:其原來不屬于相等子圖,現(xiàn)可能進(jìn)入相等子圖,從而使相等子圖得到擴(kuò)大。

修改頂標(biāo)的目的就是要擴(kuò)大相等子圖,為保證至少有一條邊進(jìn)入相等子圖,可以在交錯路的邊中尋找頂標(biāo)和與邊權(quán)之差最小的邊,也即前述的 d 值。

將交錯路中屬于 X 部的頂點(diǎn)減去 d,交錯路中屬于 Y 部的頂點(diǎn)加上 d,則可以保證至少有一條邊擴(kuò)充進(jìn)入相等子圖。

【相等子圖的性質(zhì)】

1)任意時(shí)刻,相等子圖的 最大權(quán)匹配 ≤ 相等子圖的頂標(biāo)和

2)任意時(shí)刻,相等子圖的 頂標(biāo)和=所有頂點(diǎn)的頂標(biāo)和

3)擴(kuò)充相等子圖后,相等子圖的頂標(biāo)和會減小

4)相等子圖的 最大匹配=原圖的完全匹配 時(shí),匹配邊的權(quán)值和=所有頂點(diǎn)的頂標(biāo)和,此匹配即為最優(yōu)匹配

【實(shí)現(xiàn)】

1.最優(yōu)匹配

#include<cstdio> #include<cstring> #include<cmath> #define INF 0x3f3f3f3f #define N 1001 int n,m;//x、y中結(jié)點(diǎn)個數(shù),下標(biāo)從1開始 int G[N][N];//邊權(quán)值矩陣 int Lx[N],Ly[N];//x、y中每個點(diǎn)的期望值 bool visX[N],visY[N];//標(biāo)記左右點(diǎn)集是否已被訪問過 int linkX[N],linkY[N];//linkX[i]表示與X部中點(diǎn)i匹配的點(diǎn),linkY[i]表示與Y部中點(diǎn)i匹配的點(diǎn),-1時(shí)表示無匹配 bool dfs(int x){visX[x]=true;for(int y=1;y<=m;y++){if(!visY[y]){int temp=Lx[x]+Ly[y]-G[x][y];if(temp==0){//不在交替路中visY[y]=true;//放入交替路if(linkY[y]==-1 || dfs(linkY[y])){//如果是未匹配點(diǎn),說明交替路是增廣路linkX[x]=y;//交換路徑linkY[y]=x;return true;//返回成功}}}}return false;//不存在增廣路 } void update(){int minn=INF;for(int i=1;i<=n;i++){//找出邊權(quán)與頂標(biāo)和的最小的差值if(visX[i]){for(int j=1;j<=m;j++){if(!visY[j]){minn=min(minn,Lx[i]+Ly[j]-G[i][j]);}}}}for(int j=1;j<=n;j++)//將交錯路中X部的點(diǎn)的頂標(biāo)減去minnif(visX[j])Lx[j]-=minn;for(int j=1;j<=m;j++)//將交錯路中Y部的點(diǎn)的頂標(biāo)加上minnif(visY[j])Ly[j]+=minn; } int KM(){//更新理想值,納入更多的邊memset(linkX,-1,sizeof(linkX));memset(linkY,-1,sizeof(linkY));memset(Lx,0,sizeof(Lx));memset(Ly,0,sizeof(Ly));for(int i=1;i<=n;i++)//更新理想值for(int j=1;j<=m;j++)Lx[i]=max(Lx[i],G[i][j]);for(int i=1;i<=n;i++){while(true){memset(visX,false,sizeof(visX));memset(visY,false,sizeof(visY));if(dfs(i))break;elseupdate();}}int ans=0;for(int i=1;i<=n;i++)if(linkY[i]!=-1)//若存在邊ans+=G[linkY[i]][i];//統(tǒng)計(jì)邊權(quán)和return ans; }int main(){while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&G[i][j]);printf("%d\n",KM());}return 0; }

2.邊權(quán)和最小的完全匹配

使用 KM 算法只能求二分圖的最優(yōu)匹配,即邊權(quán)和最大的完全匹配,?這里有一個技巧,就是將所有的邊權(quán)取負(fù),再進(jìn)行 KM 算法,得到的解取負(fù)就是邊權(quán)和最小的完全匹配

假設(shè)存在一個最優(yōu)解 res,是所有解中花費(fèi)最小的,那么 -res 自然是所有花費(fèi)中最大的解,當(dāng)將所有邊權(quán)取負(fù)后,用 KM 算法得到的最優(yōu)匹配必然是那個花費(fèi)最大的解,取負(fù)后就是所需的最小邊權(quán)值的解

#include<cstdio> #include<cstring> #include<cmath> #define INF 0x3f3f3f3f #define N 1001 int n,m;//x、y中結(jié)點(diǎn)個數(shù),下標(biāo)從1開始 int G[N][N];//邊權(quán)值矩陣 int Lx[N],Ly[N];//x、y中每個點(diǎn)的期望值 bool visX[N],visY[N];//標(biāo)記左右點(diǎn)集是否已被訪問過 int linkX[N],linkY[N];//linkX[i]表示與X部中點(diǎn)i匹配的點(diǎn),linkY[i]表示與Y部中點(diǎn)i匹配的點(diǎn),-1時(shí)表示無匹配 bool dfs(int x){visX[x]=true;for(int y=1;y<=m;y++){if(!visY[y]){int temp=Lx[x]+Ly[y]-G[x][y];if(temp==0){//不在交替路中visY[y]=true;//放入交替路if(linkY[y]==-1 || dfs(linkY[y])){//如果是未匹配點(diǎn),說明交替路是增廣路linkX[x]=y;//交換路徑linkY[y]=x;return true;//返回成功}}}}return false;//不存在增廣路 } void update(){int minn=INF;for(int i=1;i<=n;i++){//找出邊權(quán)與頂標(biāo)和的最小的差值if(visX[i]){for(int j=1;j<=m;j++){if(!visY[j]){minn=min(minn,Lx[i]+Ly[j]-G[i][j]);}}}}for(int j=1;j<=n;j++)//將交錯路中X部的點(diǎn)的頂標(biāo)減去minnif(visX[j])Lx[j]-=minn;for(int j=1;j<=m;j++)//將交錯路中Y部的點(diǎn)的頂標(biāo)加上minnif(visY[j])Ly[j]+=minn; } int KM(){//更新理想值,納入更多的邊memset(linkX,-1,sizeof(linkX));memset(linkY,-1,sizeof(linkY));memset(Lx,0,sizeof(Lx));memset(Ly,0,sizeof(Ly));for(int i=1;i<=n;i++)//更新理想值for(int j=1;j<=m;j++)Lx[i]=max(Lx[i],G[i][j]);for(int i=1;i<=n;i++){while(true){memset(visX,false,sizeof(visX));memset(visY,false,sizeof(visY));if(dfs(i))break;elseupdate();}}int ans=0;for(int i=1;i<=n;i++)if(linkY[i]!=-1)ans+=G[linkY[i]][i];return ans; }int main(){while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&G[i][j]);G[i][j]=-G[i][j];printf("%d\n",-KM());}return 0; }

3.最小有向環(huán)覆蓋權(quán)值和

若原圖能由多個不相交的有向環(huán)覆蓋,那么二分圖一定存在完全匹配,比如:原圖中有向環(huán)為 1-2-3-1,則二分圖的完全匹配就是 1-2',2-3',3-1'

由于有向環(huán)覆蓋對應(yīng)一個二分圖的完全匹配,該完全匹配的權(quán)值對應(yīng)有向環(huán)覆蓋的權(quán)值,因此原圖權(quán)值最大的有向環(huán)匹配就是二分圖最優(yōu)匹配的值

要求最小權(quán)值和,因此將邊的權(quán)值全部取負(fù),再進(jìn)行計(jì)算,最后結(jié)果再取負(fù)即可

#include<cstdio> #include<cstring> #include<cmath> #define INF 0x3f3f3f3f #define N 1001 int n,m; int G[N][N]; int Lx[N],Ly[N]; bool visX[N],visY[N]; int linkX[N],linkY[N]; bool dfs(int x){visX[x]=true;for(int y=1;y<=m;y++){if(!visY[y]){int temp=Lx[x]+Ly[y]-G[x][y];if(temp==0){visY[y]=true;if(linkY[y]==-1 || dfs(linkY[y])){linkX[x]=y;linkY[y]=x;return true;}}}}return false; } void update(){int minn=INF;for(int i=1;i<=n;i++)if(visX[i])for(int j=1;j<=m;j++)if(!visY[j])minn=min(minn,Lx[i]+Ly[j]-G[i][j]);for(int i=1;i<=n;i++)if(visX[i])Lx[i]-=minn;for(int i=1;i<=m;i++)if(visY[i])Ly[i]+=minn; } int KM(){memset(linkX,-1,sizeof(linkX));memset(linkY,-1,sizeof(linkY));for(int i=1;i<=n;i++){Lx[i]=Ly[i]=0;for(int j=1;j<=m;j++)Lx[i]=max(Lx[i],G[i][j]);}for(int i=1;i<=n;i++){while(true){memset(visX,false,sizeof(visX));memset(visY,false,sizeof(visY));if(dfs(i))break;elseupdate();}}int ans=0;for(int i=1;i<=m;i++){if(G[linkY[i]][i]==-INF){return 1;}ans+=G[linkY[i]][i];}return ans; } int main(){while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)G[i][j]=-INF;while(m--){int x,y,w;scanf("%d%d%d",&x,&y,&w);G[x][y]=max(G[x][y],-w);//處理重邊G[y][x]=G[x][y];//無向圖}int res=-KM();if(res==-1)printf("NO\n";elseprintf("%d\n",res);}return 0; }

4.優(yōu)先用原匹配邊構(gòu)建的最優(yōu)匹配

最優(yōu)匹配可以直接使用 KM 模版,但是要在原匹配邊的基礎(chǔ)上使得改變的邊最少,可以進(jìn)行如下的處理:

左邊點(diǎn)集有 n?個點(diǎn),且 n<=m,則最優(yōu)匹配必有 n 條邊,讓原圖中的每條邊的權(quán)值都乘以 (n+1),即擴(kuò)大 n+1 倍,且若某邊本來就是原匹配用的其中一條邊,那么該邊權(quán)值在擴(kuò)大 n+1 倍后,再加1。

因此任意一條邊的權(quán)值只能是 n+1 的倍數(shù)(n+1 的倍數(shù))+1,要在這種權(quán)值的邊中選出 n 條來,最終得到的最優(yōu)權(quán)值和 ans? 除以 n+1,即為最優(yōu)匹配解,因此就算是所有邊均使用原先的匹配,也即在所有權(quán)值的基礎(chǔ)上加了 n 個 1,此時(shí)除以 n+1,減去原匹配的值就是最優(yōu)匹配比原匹配增長的值

如果在新二分圖中求出的權(quán)值和為 n+1 的倍數(shù),則說明最優(yōu)匹配中一條老邊都沒有復(fù)用。

綜上:所有邊權(quán)值*(n+1),老邊再 +1,最終?ans%(n+1) 就是復(fù)用舊邊的條數(shù),ans/(n+1)-oldVal 就是最優(yōu)匹配比原匹配增長的值。

#include<cstdio> #include<cstring> #include<cmath> #define INF 0x3f3f3f3f #define N 1001 int n,m; int G[N][N]; int Lx[N],Ly[N]; bool visX[N],visY[N]; int linkX[N],linkY[N]; bool dfs(int x){visX[x]=true;for(int y=1;y<=m;y++){if(!visY[y]){int temp=Lx[x]+Ly[y]-G[x][y];if(temp==0){visY[y]=true;if(linkY[y]==-1 || dfs(linkY[y])){linkX[x]=y;linkY[y]=x;return true;}}}}return false; } void update(){int minn=INF;for(int i=1;i<=n;i++)if(visX[i])for(int j=1;j<=m;j++)if(!visY[j])minn=min(minn,Lx[i]+Ly[j]-G[i][j]);for(int i=1;i<=n;i++)if(visX[i])Lx[i]-=minn;for(int i=1;i<=m;i++)if(visY[i])Ly[i]+=minn; } int KM(){memset(linkX,-1,sizeof(linkX));memset(linkY,-1,sizeof(linkY));for(int i=1;i<=n;i++){Lx[i]=Ly[i]=0;for(int j=1;j<=m;j++)Lx[i]=max(Lx[i],G[i][j]);}for(int i=1;i<=n;i++){while(true){memset(visX,false,sizeof(visX));memset(visY,false,sizeof(visY));if(dfs(i))break;elseupdate();}}int ans=0;for(int i=1;i<=m;i++)if(linkY[i]!=-1)ans+=G[linkY[i]][i];return ans; } int main(){while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&G[i][j]);G[i][j]=G[i][j]*(n+1);//每條邊乘以(n+1)}}int oldVal=0;//記錄原匹配權(quán)值和for(int i=1;i<=n;i++){int j;scanf("%d",&j);oldVal+=(G[i][j]/(n+1));//累計(jì)原匹配權(quán)值G[i][j]++;//老邊+1}int ans=KM();int v1=ans/(n+1);//最優(yōu)匹配的權(quán)值和int v2=v1-oldVal;//最優(yōu)匹配比原匹配相比多的權(quán)值數(shù)int v3=ans%(n+1);//最優(yōu)匹配使用的老邊數(shù)int v4=n-v3;//最優(yōu)匹配使用的新邊數(shù)printf("%d\n",v1);printf("%d\n",v2);printf("%d\n",v3);printf("%d\n",v4);}return 0; }

?

總結(jié)

以上是生活随笔為你收集整理的图论 —— 二分图 —— KM 算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。