圖的m着色問題也是經典的回溯問題,我們也可以使用DFS來解決。圖的着色還有一個作用,那就判斷二分圖,將一個羣體分成兩撥,則這兩撥應該着不同顏色。
問題描述
給定無向圖G和m種不同的顏色,用這些顏色爲圖G的各頂點着色,每個頂點着一種顏色。是否有一種着色法,使G中每條邊的2個頂點着有不同的顏色?這個問題是圖的m可着色判定問題。若一個圖最少需要m種顏色才能使圖中每條邊連接的2個頂點着不同顏色,則稱這個數m爲圖的色數。求一個圖的色數m的問題稱爲圖的m可着色優化問題。
如果一個圖的所有頂點和邊都能用某種方式畫在平面上且沒有任何兩邊相交,則稱這個圖是可平面圖。著名的平面圖的四色猜想是圖的m可着色性判定問題的特殊情形。
算法設計
我們需要寫出的圖的鄰接矩陣,某個點的相鄰節點我們都要着異色。如果不是m可着色的,我們就給出否定答案,否則找出所有不同的着色方法。
整數1,2…,m表示m種不同的顏色,頂點i所着顏色用x[i]表示。我們的解空間是一個n+1層m叉的樹,樹中每個內節點都有m個兒子,這些點都可以着不同的顏色,所以我們的解空間應該是一個排列樹。一共有n個點,每個點都有m個着色可能,所以總的着色種類有m*m...*m=m^n
下圖爲n=3,m=3時問題的解空間樹
Backtrack(i)用來搜索第i層的子樹,類Color的數據成員記錄解空間中結點信息,以減少傳給Backtrack的參數,sum記錄當前已經找到的可m着色方案。
當i>n時,說明我們已經到達了葉子結點處,得到新的m着色方案,當前找到的可m着色方案數sum+1。
當i<=n時,如果當前着色點和它的相鄰點着了同色,那麼我們就應該換一個顏色對其着色。我們採用深度優先搜索的方式遞歸地對可行子樹搜索,或剪去不可行子樹。
代碼
1.遞歸法
class Color{
friend int mColoring(int,int,int**);
private:
bool ok(int i);
void Backtrack(int t);
int n,//圖中的頂點數
m,//顏色種類
**a,//圖的鄰接矩陣
*x,//當前着色結果
long sum;//方案數
bool Color::ok(int i)
{
for(int j = 1;j <= n;j++)
{
if((a[i][j]==1) && (x[i]==x[j]))
return false;
}
return true;
}
void Color::Backtrack(int t)
{
if(t > n)
{
sum++;
for(int i = 1;i <= n;i++)
cout<<x[i]<<" ";
}
else
{
for(int i = 1;i <= m;i++)
{
x[t] = i;
if(ok(t))
{
Backtrack(t+1);
}
x[t] = 0;
}
}
}
int mColoring(int n,int m,int **a)
{
Color X;
X.n = n;
X.m = m;
X.a = a;
X.sum = 0;
int *p = new int[n+1];
for(int i = 0;i <= n;i++)
p[i] = 0;
X.x = p;
X.Backtrack(1);
delete []p;
return X.sum;
}
};
2.迭代法
class Color{
friend int mColoring(int,int,int**);
private:
bool ok(int i);
void GraphColor(int n,int m,int color[]);
int n,//圖中的頂點數
m,//顏色種類
**a,//圖的鄰接矩陣
*x,//當前着色結果
long sum;//方案數
bool Color::ok(int i)
{
for(int j = 1;j <= n;j++)
{
if((a[i][j]==1) && (x[i]==x[j]))
return false;
}
return true;
}
void Color::GraphColor(int n,int m,int color[])
{
int i,k;
//初始化color
for(i = 0;i < n;i++)
color[i] = 0;
k = 0;
while(k >= 0)
{
color[k] = color[k] + 1;
while((color[k] <= m) && (!ok(color,k,c,n)))
//當前不滿足要求則可以換一種顏色填入
color[k] = color[k] + 1;
if(color[k] <= m)
{
//向下遞歸,逐層向下塗色
if(k == n - 1)
break;
else
{
k += 1;
}
}
else
{
//不滿足要求的話就回溯,將這個顏色撤出
color[k] = 0;
k -= 1;
}
}
}
int mColoring(int n,int m,int **a)
{
Color X;
X.n = n;
X.m = m;
X.a = a;
X.sum = 0;
int *p = new int[n+1];
for(int i = 0;i <= n;i++)
p[i] = 0;
X.x = p;
X.Backtrack(1);
delete []p;
return X.sum;
}
};
總結
我們發現,圖中的內結點數爲對於每個內結點,在最壞情況下,用ok()函數檢查當前擴展結點的每個兒子所對應的顏色的可行性需耗時O(mn)。因此,回溯法總的時間耗費爲:
共m^(n-1)個內結點(最後一層是葉子結點),每個結點需要耗時O(mn)來檢查。