1. 程式人生 > >圖的點著色、區間著色問題及其應用(基於貪心思想的DFS回溯法求點著色問題和區間著色演算法求解任務排程問題)

圖的點著色、區間著色問題及其應用(基於貪心思想的DFS回溯法求點著色問題和區間著色演算法求解任務排程問題)

Graph Coloring
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 4503 Accepted: 2059 Special Judge

Description

You are to write a program that tries to find an optimal coloring for a given graph. Colors are applied to the nodes of the graph and the only available colors are black and white. The coloring of the graph is called optimal if a maximum of nodes is black. The coloring is restricted by the rule that no two connected nodes may be black. 


 
Figure 1: An optimal graph with three black nodes 

Input

The graph is given as a set of nodes denoted by numbers 1...n, n <= 100, and a set of undirected edges denoted by pairs of node numbers (n1, n2), n1 != n2. The input file contains m graphs. The number m is given on the first line. The first line of each graph contains n and k, the number of nodes and the number of edges, respectively. The following k lines contain the edges given by a pair of node numbers, which are separated by a space.

Output

The output should consists of 2m lines, two lines for each graph found in the input file. The first line of should contain the maximum number of nodes that can be colored black in the graph. The second line should contain one possible optimal coloring. It is given by the list of black nodes, separated by a blank.

Sample Input

1
6 8
1 2
1 3
2 4
2 5
3 4
3 6
4 6
5 6

Sample Output

3
1 4 5

Source


題意:求出在合法條件(The coloring is restricted by the rule that no two connected nodes may be black. )下的方案使得染成黑色的頂點最多。

思路:在這一題中,題目給定的圖一定是二分圖,所以我們可以用到一種基於貪心思想的染色演算法--順序染色法,有的圖是不能用順序染色法來求解的。這種演算法是一種近似的有效演算法。由於每次從u最小的鄰接頂點開始染色,所以輸出的數字一定是嚴格的上升序。

順序染色:

(1)用i表示頂點序號,i=1;

(2)用c表示給頂點i著色為第c中顏色,c=1;

(3)對第i個頂點著色:考慮它的每個鄰接頂點,如果都沒有使用第c中顏色 ,則給頂點i著色為第c種顏色,並轉向第(5)步;否則,轉向第(4)步。

(4)c = c+1,並轉向第(3)步。

(5)若還有其他的頂點未著色,則i = i+1,並轉向第(2)步,否則演算法結束。

注意,這只是一種近似的有效演算法。例如,二分圖中i就可以用順序染色,而有的圖用這種演算法求出來的結果是錯誤的。

My AC code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#define PI acos(-1.0)
#define LINF 1000000000000000000LL
#define eps 1e-8
#define LL long long
#define MAXN 100010 
#define MOD 1000000007
using namespace std;
const int INF=0x3f3f3f3f;
int color[111];
vector<int>vec[111];//vec[i]儲存第i種著色方案 
vector<int>e[111];//邊 
int cnt,n;//n為頂點數,cnt為黑色的點數 ,cnti可指定求在某一種特定條件下的最優方案是第幾種方案 
int ans,ansi;
bool Ok(int k)//k表示第k個頂點
{
	if(color[k]==1)
	{
		for(int i=0;i<e[k].size();i++)
		{
			if(color[e[k][i]]==color[k])
				return false;
		}
	}
	return true;
}
void dfs(int u,int color_num)//u為頂點編號,注意:著色問題深搜是按頂點編號深搜下去,不是沿著鄰邊的下個頂點深搜下去!!!  
{
	if(u==n+1)//第(5)步  
	{
		/*for(int i=1;i<=n;i++)//輸出所有染色方案 
		{
			printf("%d",color[i]);
		}
		puts("");*/
		if(cnt>ansi)
		{
			ansi=cnt;
			for(int i=1;i<=n;i++)
			{
				if(color[i]==1)
					vec[ansi].push_back(i);
			}
		}
		return;
	}
	if(cnt+(n-u)+1<=ansi)//剪枝,若就算剩下的點數都可以染成黑色,再加上當前黑色的點數還沒有前面的染色方案得到的最大黑色點數多,則捨棄當前這種方案 
		return;
	for(int co=1;co<=color_num;co++)//color_num表示顏色種類數,1表示黑色,2表示白色 
	{
		color[u]=co;//對頂點u染第co種顏色  //第3步或第4步  
		if(Ok(u)) //第(3)步   
		{
			if(co==1)//當前頂點u染的是黑色,黑色的點數加一 
				cnt++;
			dfs(u+1,color_num);//注意:著色問題深搜是按頂點編號深搜下去,不是沿著鄰邊的下個頂點深搜下去!!! 
			color[u]=0;//回溯 
			if(co==1)
				cnt--;	
		}
	}
}
int main()  
{  
	//freopen("D:\in.txt","r",stdin);
    int t,i,u,v,m;
    scanf("%d",&t);
    while(t--)
    {
    	scanf("%d%d",&n,&m);
    	for(i=0;i<=n+2;i++)
    	{
    		vec[i].clear();
    		e[i].clear();
    		color[i]=0;
		}
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d",&u,&v);
    		e[u].push_back(v);
    		e[v].push_back(u);
		}
		cnt=0;
		ansi=0;
		dfs(1,2);//注意:著色問題深搜是按頂點編號深搜下去,不是沿著鄰邊的下個頂點深搜下去!!! 
		printf("%d\n",ansi);
		if(ansi!=0)
		{
			printf("%d",vec[ansi][0]);
			for(int i=1;i<vec[ansi].size();i++)
			{
				printf(" %d",vec[ansi][i]);
			}
			printf("\n");
		}
	}
    return 0;  
}

Link:http://poj.org/problem?id=1129
Channel Allocation
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 13340 Accepted: 6825

Description

When a radio station is broadcasting over a very large area, repeaters are used to retransmit the signal so that every receiver has a strong signal. However, the channels used by each repeater must be carefully chosen so that nearby repeaters do not interfere with one another. This condition is satisfied if adjacent repeaters use different channels. 

Since the radio frequency spectrum is a precious resource, the number of channels required by a given network of repeaters should be minimised. You have to write a program that reads in a description of a repeater network and determines the minimum number of channels required.

Input

The input consists of a number of maps of repeater networks. Each map begins with a line containing the number of repeaters. This is between 1 and 26, and the repeaters are referred to by consecutive upper-case letters of the alphabet starting with A. For example, ten repeaters would have the names A,B,C,...,I and J. A network with zero repeaters indicates the end of input. 

Following the number of repeaters is a list of adjacency relationships. Each line has the form: 

A:BCDH 

which indicates that the repeaters B, C, D and H are adjacent to the repeater A. The first line describes those adjacent to repeater A, the second those adjacent to B, and so on for all of the repeaters. If a repeater is not adjacent to any other, its line has the form 

A: 

The repeaters are listed in alphabetical order. 

Note that the adjacency is a symmetric relationship; if A is adjacent to B, then B is necessarily adjacent to A. Also, since the repeaters lie in a plane, the graph formed by connecting adjacent repeaters does not have any line segments that cross. 

Output

For each map (except the final one with no repeaters), print a line containing the minumum number of channels needed so that no adjacent channels interfere. The sample output shows the format of this line. Take care that channels is in the singular form when only one channel is required.

Sample Input

2
A:
B:
4
A:BC
B:ACD
C:ABD
D:BC
4
A:BCD
B:ACD
C:ABD
D:ABC
0

Sample Output

1 channel needed.
3 channels needed.
4 channels needed. 

Source


題意:給定無向圖,對點著色,使得相鄰的結點顏色不同,問最少需要多少種顏色。

AC code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#define PI acos(-1.0)
#define LINF 1000000000000000000LL
#define eps 1e-8
#define LL long long
#define MAXN 100010 
#define MOD 1000000007
using namespace std;
const int INF=0x3f3f3f3f;
int color[111];
vector<int>vec[111];//vec[i]儲存第i種著色方案 
vector<int>e[111];//邊 
int cnt,n;//n為頂點數,cnt為黑色的點數 ,cnti可指定求在某一種特定條件下的最優方案是第幾種方案 
int ans,ansi;
bool Ok(int k)//k表示第k個頂點
{
	for(int i=0;i<e[k].size();i++)
	{
		if(color[e[k][i]]==color[k])
			return false;
	}
	return true;
}
void dfs(int u,int color_num)//u為頂點編號,注意:著色問題深搜是按頂點編號深搜下去,不是沿著鄰邊的下個頂點深搜下去!!!  
{
	if(u==n+1)//第(5)步  
	{
		/*for(int i=1;i<=n;i++)//輸出所有染色方案 
		{
			printf("%d",color[i]);
		}
		puts("");*/
		//if(cnt>ansi)
		//{
			//ansi=cnt;
			ansi=color_num;
			/*for(int i=1;i<=n;i++)
			{
				if(color[i]==1)
					vec[ansi].push_back(i);
			}
		}*/
		return;
	}
	//if(cnt+(n-u)+1<=ansi)//剪枝,若就算剩下的點數都可以染成黑色,再加上當前黑色的點數還沒有前面的染色方案得到的最大黑色點數多,則捨棄當前這種方案 
	//	return;
	for(int co=1;co<=color_num;co++)//color_num表示顏色種類數,1表示黑色,2表示白色 
	{
		color[u]=co;//對頂點u染第co種顏色  //第3步或第4步  
		if(Ok(u)) //第(3)步   
		{
			dfs(u+1,color_num);//注意:著色問題深搜是按頂點編號深搜下去,不是沿著鄰邊的下個頂點深搜下去!!! 
			color[u]=0;//回溯 
		}
	}
}
char s[33];
int main()  
{  
	//freopen("D:\in.txt","r",stdin);
    int t,i,j,m;
    while(~scanf("%d",&n))
    {
    	if(n==0)
    		break;
    	for(j=0;j<=n;j++)
		{
			vec[j].clear();
			e[j].clear();
			color[j]=0;
		}
	    for(i=1;i<=n;i++)
	    {
	    	scanf("%s",s);
	    	int len=strlen(s);
	    	int u=s[0]-'A'+1;
	    	for(j=2;j<len;j++)
	    	{
	    		int v=s[j]-'A'+1;
	    		e[u].push_back(v);
	    		e[v].push_back(u);
			}
		}
		for(int color_num=1;;color_num++)//從少到多列舉顏色種類數 
		{
			cnt=0;
			ansi=INF;
			dfs(1,color_num);//注意:著色問題深搜是按頂點編號深搜下去,不是沿著鄰邊的下個頂點深搜下去!!! 
			if(ansi!=INF)
			{
				if(ansi==1) 
					printf("%d channel needed.\n",ansi);
				else
					printf("%d channels needed.\n",ansi);
				break;
			}
			/*if(ansi!=0)
			{
				printf("%d",vec[ansi][0]);
				for(int i=1;i<vec[ansi].size();i++)
				{
					printf(" %d",vec[ansi][i]);
				}
				printf("\n");
			}*/
		}
	}
    return 0;  
}

下面附上部落格:http://blog.csdn.net/suwei19870312/article/details/5282932的相關資料以供理解。

四色問題:

四色問題是m圖著色問題的一個特列,根據四色原理,證明平面或球面上的任何地圖的所有區域都至多可用四種、顏色來著色,並使任何兩個有一段公共邊界的相鄰區域沒有相同的顏色。這個問題可轉換成對一平面圖的4-著色判定問題(平面圖是一個能畫於平面上而邊無任何交叉的圖)。將地圖的每個區域變成一個結點,若兩個區域相鄰,則相應的結點用一條邊連線起來。多年來,雖然已證明用5種顏色足以對任一幅地圖著色,但是一直找不到一定要求多於4種顏色的地圖。直到1976年這個問題才由愛普爾(k.i.apple),黑肯(w.haken)和考西(j.koch)利用電子計算機的幫助得以解決。他們證明了4種顏色足以對任何地圖著色。

在這一節,不是隻考慮那些由地圖產生出來的圖,而是所有的圖。討論在至多使用m種顏色的情況下,可對一給定的圖著色的所有不同方法。

m圖著色問題:

題目大意:

1,已知一個圖g和m>0種顏色,在只准使用這m種顏色對g的結點著色的情況下,是否能使圖中任何相鄰的兩個結點都具有不同的顏色呢?這個問題稱為m-著色判定問題

2,在m-著色最優化問題則是求可對圖g著色的最小整數m。這個整數稱為圖g的色數。這是求圖的最少著色問題,求出m的值。

題目的解法:

第一個問題,m-著色判定問題:

可以通過回溯的方法,不斷的為每一個節點著色,在前面n-1個節點都合法的著色之後,開始對第n個節點進行著色,這時候列舉可用的m個顏色,通過和第n個節點相鄰的節點的顏色,來判斷這個顏色是否合法,如果找到那麼一種顏色使得第n個節點能夠著色,那麼說明m種顏色的方案是可行的。返回真即可:

//用於判斷當前節點上塗上這個顏色可不可行,與其鄰接節點的顏色做判斷,這裡用鄰接表來儲存圖的資訊
bool isok(int step)
{
     vector<int>::iterator iter;
     for(iter = input[step].begin(); iter != input[step].end(); iter++)
     {
              if(Color[step] == Color[*iter]) return false;
     }
     return true;
}
//step表示0->n的節點,color_num是指給color_num的顏色的個數可用
//判斷如果給color_num的顏色的個數是否可行,如果可行返回true,否則false 
bool DFS(int step, int color_num)
{
     if(step >= n) return true;
     else
     {
         int i;
         for(i = 1; i<= color_num; i++)
         {
               Color[step] = i;
               if(isok(step))
               {
                     if(DFS(step + 1, color_num))
                          return true;
               }
               Color[step] = 0;
         }
     }
     return false;
}

第二個問題:求出最少的著色數m

有了上面的問題的積累,對於這個問題就很簡單了,只要從1到n列舉顏色數,來呼叫上面的DFS(0, m),如果有一次呼叫返回true,那麼這時這個顏色就是我們要求的最少的著色數。

for(i = 1; i<= n; i++)
{
      if(DFS(0, i))
      {
             cout << "the min colors :" << i << endl;
             break;
      }
}

參考:

1. n個學生對m個宣講會中的若干個感興趣,如何安排宣講會的時間(每個宣講會持續的時間相同),使得每個學生對自己感興趣的宣講會時間不衝突,且宣講會的總時間最短?

此問題可以轉化成頂點著色問題。

把每個宣講會看作是一些散佈的點,對於每個學生,把他感興趣的宣講會之間兩兩相連,如:

學生A希望參加宣講會1、2、3

學生B希望參加宣講會1、3、4          則:

 

設宣講會1、2、3、4的著色分別為c1、c2、c3、c2,4與2著同樣的顏色,則可以這樣安排宣講會的時間,假設持續時間為一小時,宣講會1在1點開,2在2點開,3在3點開,4與2時間一樣在2點開。

2. 對N個區間進行著色,有重疊的區間不能著同樣的顏色。

可轉化為頂點著色問題,但頂點著色問題是NP的,至今沒有一個已知的好演算法,所以對圖的色數進行估計以及在尋找使用的顏色數“不太大”的情形下頂點著色的某些方法又有非常重要的意義。於是頂點著色的貪婪演算法應運而生。

頂點著色的貪婪演算法是按順序取第一個可用的顏色而忽略對以後的頂點可能會產生的後果。使用正整數對頂點著色,因此可以談論一種顏色小於另一種顏色了。

(1) 色數

設G =(V,E)是一個圖,G的頂點著色就是G的每個頂點指定一種顏色,且使得相鄰頂點有不同的顏色。如果這些顏色選自於一個 k 種顏色的集合而不管 k 種顏色是否都用到,那麼頂點著色稱為 k-頂點著色,簡稱為k-著色。G有一個 k 著色,那麼G是 k-可著色的。使得G是 k-可著色的最小的 k 稱為G的色數χ(G)表示。

(2) 頂點著色的貪婪演算法

設G是一個圖,它的頂點按某一順序記為x1,x2,...,xn。

i ) 對頂點x1指定顏色1.

ii) 對每個 i = 2,3,...,n,另 p 是與 xi 鄰接的頂點 x1,x2,...,xi-1 中沒有任何一個頂點著色 p 的最小的顏色,並且對 xi 指定顏色 p。

下面是一個定理:

設G是一個圖,對於該圖頂點的最大度Δ,那麼貪婪演算法產生G的頂點的一-著色(但並不意味著Δ + 1種顏色都用到),

(3) 演算法設計

回到原始問題:對N個區間進行著色,有重疊的區間不能著同樣的顏色。下面是解決這個問題的演算法,摘自<程式設計之美>。

共N個區間,對這N個區間按照區間開始從小到大排序,設為:

[b[i], e[i]] 0 <= i < N

color[i]為顏色號 0 <= i < N

color[i] = 0 i = 0時,

color[i+1] = color[i] 第 i 區間與第 i + 1 區間無重疊時,

color[i + 1] = color[i] + 1 第 i 區間與第 i + 1 區間有重疊時。

演算法如下:

int overlap(int b1, int e1, int b2, int e2) {   //判斷兩區間是否有重疊的函式
    if (b2 >= e1 || b1 >= e2)
        return 0;      //沒有重疊
    else
        return 1;      //有重疊
}

int intervalColoring(int b[], int e[], int color[], int N)
{
    int nMaxColors = 0, i, k, j;
    int isForbidden[N];//false:0;true:1
    memset(isForbidden, 0, sizeof isForbidden);

    for (i = 0; i < N;i++) {
        for (k = 0;k < nMaxColors;k++)  /** a */
            isForbidden[k] = 0;
        for (j = 0;j < i;j++)           /** b */
            if (overlap(b[j], e[j], b[i], e[i]))
                isForbidden[color[j]] = 1;
        for (k = 0;k < nMaxColors;k++)  /** c */
            if (isForbidden[color[k]] == 0)
                break;
        if (k < nMaxColors)
            color[i] = k;
        else {
            color[i] = nMaxColors;
            ++nMaxColors;
        }
    }// for
    return nMaxColors;
}

分析:

(a) 把這當前已著色的nMaxColors種顏色設為可用。

(b) j從區間0開始到區間 i-1與當前待著色區間 i 判斷兩區間是否有重疊,若有則著區間 j 的顏色不能用來著區間 i 。

(c) k從0到nMaxColors - 1,判斷這nMaxColors種顏色有能著區間 i 的。

測試程式碼如下:
#include <stdio.h>
#include <string.h>

int overlap(int b1, int e1, int b2, int e2);
int intervalColoring(int b[], int e[], int color[], int N);
int main(void)
{
    int i, N = 4;
    int b[] = {1, 2, 3, 3}, e[] = {5, 3, 4, 6};
    int color[N], nMaxColors = 0;

    nMaxColors = intervalColoring(b, e, color, N);

    printf("nMaxColors = %d\n", nMaxColors);
    printf("\ncolor of the intervals:\n");
    for (i = 0;i < N;i++)
        printf("interval %d: color %d\n",i, color[i]);

    return 0;
}

輸出: