貪心法求樹的最小支配集,最小點覆蓋,最大獨立集


定義:

最小支配集:對於圖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 }

 

參考書籍<<圖論及應用>>以及網上的其他資料.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM