概念:
1、最小支配集:在圖中用盡量少的點去覆蓋所有的點(規則:當一個點被覆蓋後,在圖中與其相鄰的點也將被覆蓋);
2、最小點覆蓋:在圖中用盡量少的點去覆蓋所有的邊(規則:當一個點被覆蓋後,在圖中與其相鄰的邊將被覆蓋);
3、最大獨立集:在圖中選擇儘量多的點(規則:保證被選則的點在圖中沒有邊相連);
對於任意圖上述三個問題不存在多項式時間內的算法
但對於樹,上述三個問題可以使用貪心算法和動規算法解決
具體見:點擊打開鏈接
4、最大團:在圖中選擇儘量多的點(規則:保正所有的點之間都有邊相連)
(1)最小支配集
貪心策略:首先選擇一點爲樹根,再按照深度優先遍歷得到遍歷序列,按照所得序列的反向序列的順序進行貪心,對於一個即不屬於支配集也不與支配集中的點相連的點來說,如果他的父節點不屬於支配集,將其父節點加入到支配集.
僞代碼:
第一步:以根節點深度優先遍歷整棵樹,求出每個點在深度優先遍歷序列中的編號和每個點的父節點編號.
第二步:按照深度優先遍歷的反向順序檢查每個點,如果當前點不屬於支配集也不與支配集的點相連,且它的父節點不屬於支配集,將其父節點加入到支配集,支配集中點的個數加 1, 標記當前節點, 當前節點的父節點, 當前節點的父節點的父節點,因爲這些節點要麼屬於支配集(當前點的父節點),要麼與支配集中的點相連(當前節點 和 當前節點的父節點的父節點).
具體實現:
採用鏈式前向星存儲整棵樹.整形數組newpos[i] 表示深度優先遍歷序列的第 i 個點是哪個點, now 表示當前深度優先遍歷序列已經有多少個點了. bool形數組visit[]用於深度優先遍歷的判重,整形pre[i]表示點 i 的父節點編號, bool型數組s[i]如果爲 true, 表示第 i 個點被覆蓋, bool型數組set[i]如果爲 true,表示點 i 屬於要求的點的集合.
代碼:
(2)最小點覆蓋
貪心策略:同樣需要按照反方向的深度優先遍歷序列來進行貪心.每檢查一個結點,如果當前點和當前點的父節點都不屬於頂點覆蓋集合,則將父節點加入到頂點覆蓋集合,並標記當前節點和其父節點都被覆蓋.注意此貪心策略不適用於根節點,所以要把根節點排除在外.
具體實現:實現上基本和求最小支配集差不多,僅僅需要改動貪心部分即可.
代碼:
(3)最大獨立集
貪心策略:同樣和以上兩個貪心問題的貪心方法差不多,需要反向遍歷DFS的遍歷序列,檢查每一個點,如果當前節點沒有被覆蓋,則將當前節點加入獨立集,並標記當前點和其父節點都被覆蓋.
代碼:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000;
int pre[maxn];//存儲父節點
bool visit[maxn];//DFS標記數組
int newpos[maxn];//遍歷序列
int now;
int n, m;
int head[maxn];//鏈式前向星
struct Node {int to; int next;};
Node edge[maxn];
void DFS(int x) {
newpos[now ++] = x;//記錄遍歷序列
for(int k = head[x]; k != -1; k = edge[k].next) {
if(!visit[ edge[k].to ]) {
visit[ edge[k].to ] = true;
pre[edge[k].to] = x;//記錄父節點
DFS(edge[k].to);
}
}
}
int MIS() {
bool s[maxn] = {0};
bool set[maxn] = {0};
int ans = 0;
for(int i = n - 1; i >= 0; i--) {//按照DFS遍歷序列的逆序進行貪心
int t = newpos[i];
if(!s[t]) {//如果當前節點沒有被覆蓋
set[t] = true;//把當前節點加入到獨立集
ans ++;//獨立集中點的個數加 1
s[t] = true;//標記當前點已經被覆蓋
s[ pre[t] ] = true;//標記當前點的父節點已經被覆蓋
}
}
return ans;
}
int main() {
/* read Graph message*/ //建圖
memset(visit, false, sizeof(visit));//初始化
now = 0;
visit[1] = true;
pre[1] = 1;
DFS(1);//從第一個根節點開始尋找遍歷序列
MDS();
return 0;
}