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

概念:

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 屬於要求的點的集合.

代碼:

#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 MDS() {
    bool s[maxn] = {0};
    bool set[maxn] = {0};
    int ans = 0;
    for(int i = n - 1; i >= 0; i--) {//逆序進行貪心
        int t = newpos[i];
        if(!s[t]) { //如果當前點沒被覆蓋
            if(! set[ pre[t] ]) {//當前點的父節點不屬於支配集
                set[ pre[t] ] = true;//當前點的父節點加入支配集
                ans ++;  //支配集節點個數加 1
            }
            s[t] = true; //標記當前點已被覆蓋
            s[ pre[t] ] = true;// 標記當前點的父節點被覆蓋
            s[ pre[ 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;
}



 (2)最小點覆蓋

貪心策略:同樣需要按照反方向的深度優先遍歷序列來進行貪心.每檢查一個結點,如果當前點和當前點的父節點都不屬於頂點覆蓋集合,則將父節點加入到頂點覆蓋集合,並標記當前節點和其父節點都被覆蓋.注意此貪心策略不適用於根節點,所以要把根節點排除在外.

具體實現:實現上基本和求最小支配集差不多,僅僅需要改動貪心部分即可.

代碼:

#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 MVC() {
    bool s[maxn] = {0};
    bool set[maxn] = {0};
    int ans = 0;
    for(int i = n - 1; i >= 1; i--) {//逆序進行貪心,排除掉其根節點
        int t = newpos[i];
        if(!s[t] && !s[ pre[t] ]) {//如果當前節點和其父節點都不屬於頂點覆蓋集合
            set[ pre[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;
}


 

(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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章