定義:
最小支配集:對於圖G = (V, E) 來說,最小支配集指的是從 V 中取盡量少的點組成一個集合, 使得 V 中剩余的點都與取出來的點有邊相連.也就是說,設 V' 是圖的一個支配集,則對於圖中的任意一個頂點 u ,要么屬於集合 V', 要么與 V' 中的頂點相鄰. 在 V' 中除去任何元素后 V' 不再是支配集, 則支配集 V' 是極小支配集.稱G 的所有支配集中頂點個數最少的支配集為最小支配集,最小支配集中的頂點個數稱為支配數.
最小點覆蓋:對於圖G = (V, E) 來說,最小點覆蓋指的是從 V 中取盡量少的點組成一個集合, 使得 E 中所有邊都與取出來的點相連.也就是說設 V' 是圖 G 的一個頂點覆蓋,則對於圖中任意一條邊(u, v), 要么 u 屬於集合 V', 要么 v 屬於集合 V'. 在集合 V' 中除去任何元素后 V' 不再是頂點覆蓋, 則 V' 是極小點覆蓋. 稱 G 的所有頂點覆蓋中頂點個數最小的覆蓋為最小點覆蓋.
最大獨立集: 對於圖G = (V, E) 來說,最大獨立集指的是從 V 中取盡量多的點組成一個集合,使得這些點之間沒有邊相連.也就是說設 V' 是圖 G 的一個獨立集,則對於圖中任意一條邊(u, v), u 和 v 不能同時屬於集合 V', 甚至可以 u 和 v 都不屬於集合 V'. 在 V' 中添加任何不屬於 V' 元素后 V' 不再是獨立集,則 V' 是極大獨立集.稱 G 的所有頂點獨立集中頂點個數最多的獨立即為最大獨立集.
求解:
對於任意圖 G 來說,最小支配集, 最小點覆蓋, 最大獨立集問題是不存在多項式時間的解法,即屬於NP問題.但是這里所要求的圖 G 是一棵樹.可以在多項式的時間內給予解決.在這里利用貪心的思想來解決樹上的這三個問題.
(1)最小支配集
貪心策略:首先選擇一點為樹根,再按照深度優先遍歷得到遍歷序列,按照所得序列的反向序列的順序進行貪心,對於一個即不屬於支配集也不與支配集中的點相連的點來說,如果他的父節點不屬於支配集,將其父節點加入到支配集.
偽代碼:
第一步:以根節點深度優先遍歷整棵樹,求出每個點在深度優先遍歷序列中的編號和每個點的父節點編號.
第二步:按照深度優先遍歷的反向順序檢查每個點,如果當前點不屬於支配集也不與支配集的點相連,且它的父節點不屬於支配集,將其父節點加入到支配集,支配集中點的個數加 1, 標記當前節點, 當前節點的父節點, 當前節點的父節點的父節點,因為這些節點要么屬於支配集(當前點的父節點),要么與支配集中的點相連(當前節點 和 當前節點的父節點的父節點).
具體實現:
采用鏈式前向星存儲整棵樹.整形數組newpos[i] 表示深度優先遍歷序列的第 i 個點是哪個點, now 表示當前深度優先遍歷序列已經有多少個點了. bool形數組visit[]用於深度優先遍歷的判重,整形pre[i]表示點 i 的父節點編號, bool型數組s[i]如果為 true, 表示第 i 個點被覆蓋, bool型數組set[i]如果為 true,表示點 i 屬於要求的點的集合.
代碼:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int maxn = 1000; 5 int pre[maxn];//存儲父節點 6 bool visit[maxn];//DFS標記數組 7 int newpos[maxn];//遍歷序列 8 int now; 9 int n, m; 10 11 int head[maxn];//鏈式前向星 12 struct Node {int to; int next;}; 13 Node edge[maxn]; 14 15 void DFS(int x) { 16 newpos[now ++] = x;//記錄遍歷序列 17 for(int k = head[x]; k != -1; k = edge[k].next) { 18 if(!visit[ edge[k].to ]) { 19 visit[ edge[k].to ] = true; 20 pre[edge[k].to] = x;//記錄父節點 21 DFS(edge[k].to); 22 } 23 } 24 } 25 26 int MDS() { 27 bool s[maxn] = {0}; 28 bool set[maxn] = {0}; 29 int ans = 0; 30 for(int i = n - 1; i >= 0; i--) {//逆序進行貪心 31 int t = newpos[i]; 32 if(!s[t]) { //如果當前點沒被覆蓋 33 if(! set[ pre[t] ]) {//當前點的父節點不屬於支配集 34 set[ pre[t] ] = true;//當前點的父節點加入支配集 35 ans ++; //支配集節點個數加 1 36 } 37 s[t] = true; //標記當前點已被覆蓋 38 s[ pre[t] ] = true;// 標記當前點的父節點被覆蓋 39 s[ pre[ pre[t] ] ] = true;//標記當前點的父節點的父節點被覆蓋 40 } 41 } 42 return ans; 43 } 44 45 int main() { 46 /* read Graph message*/ //建圖 47 memset(visit, false, sizeof(visit));//初始化 48 now = 0; 49 visit[1] = true; 50 pre[1] = 1; 51 DFS(1);//從根節點開始尋摘遍歷序列 52 MDS(); 53 return 0; 54 }
(2)最小點覆蓋
貪心策略:同樣需要按照反方向的深度優先遍歷序列來進行貪心.每檢查一個結點,如果當前點和當前點的父節點都不屬於頂點覆蓋集合,則將父節點加入到頂點覆蓋集合,並標記當前節點和其父節點都被覆蓋.注意此貪心策略不適用於根節點,所以要把根節點排除在外.
具體實現:實現上基本和求最小支配集差不多,僅僅需要改動貪心部分即可.
代碼:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int maxn = 1000; 5 int pre[maxn];//存儲父節點 6 bool visit[maxn];//DFS標記數組 7 int newpos[maxn];//遍歷序列 8 int now; 9 int n, m; 10 11 int head[maxn];//鏈式前向星 12 struct Node {int to; int next;}; 13 Node edge[maxn]; 14 15 void DFS(int x) { 16 newpos[now ++] = x;//記錄遍歷序列 17 for(int k = head[x]; k != -1; k = edge[k].next) { 18 if(!visit[ edge[k].to ]) { 19 visit[ edge[k].to ] = true; 20 pre[edge[k].to] = x;//記錄父節點 21 DFS(edge[k].to); 22 } 23 } 24 } 25 26 int MVC() { 27 bool s[maxn] = {0}; 28 bool set[maxn] = {0}; 29 int ans = 0; 30 for(int i = n - 1; i >= 1; i--) {//逆序進行貪心,排除掉其根節點 31 int t = newpos[i]; 32 if(!s[t] && !s[ pre[t] ]) {//如果當前節點和其父節點都不屬於頂點覆蓋集合 33 set[ pre[t] ] = true;//把其父節點加入到頂點覆蓋集合 34 ans ++; //集合內頂點個數加 1 35 s[t] = true;//標記當前節點被覆蓋 36 s[ pre[t] ] = true;//標記其父節點被覆蓋 37 } 38 } 39 return ans; 40 } 41 42 int main() { 43 /* read Graph message*/ //建圖 44 memset(visit, false, sizeof(visit));//初始化 45 now = 0; 46 visit[1] = true; 47 pre[1] = 1; 48 DFS(1);//從第一個根節點開始尋找遍歷序列 49 MDS(); 50 return 0; 51 }
(3)最大獨立集
貪心策略:同樣和以上兩個貪心問題的貪心方法差不多,需要反向遍歷DFS的遍歷序列,檢查每一個點,如果當前節點沒有被覆蓋,則將當前節點加入獨立集,並標記當前點和其父節點都被覆蓋.
代碼:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int maxn = 1000; 5 int pre[maxn];//存儲父節點 6 bool visit[maxn];//DFS標記數組 7 int newpos[maxn];//遍歷序列 8 int now; 9 int n, m; 10 11 int head[maxn];//鏈式前向星 12 struct Node {int to; int next;}; 13 Node edge[maxn]; 14 15 void DFS(int x) { 16 newpos[now ++] = x;//記錄遍歷序列 17 for(int k = head[x]; k != -1; k = edge[k].next) { 18 if(!visit[ edge[k].to ]) { 19 visit[ edge[k].to ] = true; 20 pre[edge[k].to] = x;//記錄父節點 21 DFS(edge[k].to); 22 } 23 } 24 } 25 26 int MIS() { 27 bool s[maxn] = {0}; 28 bool set[maxn] = {0}; 29 int ans = 0; 30 for(int i = n - 1; i >= 0; i--) {//按照DFS遍歷序列的逆序進行貪心 31 int t = newpos[i]; 32 if(!s[t]) {//如果當前節點沒有被覆蓋 33 set[t] = true;//把當前節點加入到獨立集 34 ans ++;//獨立集中點的個數加 1 35 s[t] = true;//標記當前點已經被覆蓋 36 s[ pre[t] ] = true;//標記當前點的父節點已經被覆蓋 37 } 38 } 39 return ans; 40 } 41 42 int main() { 43 /* read Graph message*/ //建圖 44 memset(visit, false, sizeof(visit));//初始化 45 now = 0; 46 visit[1] = true; 47 pre[1] = 1; 48 DFS(1);//從第一個根節點開始尋找遍歷序列 49 MDS(); 50 return 0; 51 }
參考書籍<<圖論及應用>>以及網上的其他資料.
