最小权顶点覆盖问题和最小权支配集

最小权顶点覆盖问题

最小权顶点覆盖问题

问题描述

给定一个赋权无向图 G=(V, E) ,每个顶点 v \in V 都有权值 w(v) 。如果 U \subseteq V ,且对任意 (u, v) \in E 有 u \in U 或 v \in U ,就称 U 为图 G 的一个顶点覆盖。 G 的最小权顶点覆盖是指 G 中所含顶点权之和最小的顶点覆盖。

算法设计

对于给定的无向图 G ,设计一个优先队列式分支限界法,计算 G 的最小权顶点覆盖。

数据输入

由文件 input.txt 给出输入数据。第 1 行有 2 个正整数 n 和 m ,表示给定的图 G 有 n 个顶点和 m 条边,顶点编号为 1, 2, ⋯, n 。第 2 行有 n 个正整数表示 n 个顶点的权。接下来的 m 行中,每行有 2 个正整数 u 和 v ,表示图 G 的一条边 (u, v) 。

结果输出

将计算的最小权顶点覆盖的顶点权之和以及最优解输出到文件 output.txt。文件的第 1 行是最小权顶点覆盖顶点权之和;第 2 行是最优解 x_i (1 \leq i \leq n) , x_i = 0 表示顶点 i 不在最小权顶点覆盖中, x_i = 1 表示顶点 i 在最小权顶点覆盖中。

示例

输入文件示例 (input.txt)

复制代码
7
110011110010
16
24
25
36
45
46
67

输出文件示例 (output.txt)

复制代码
13
1011001
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define MAXN 100    // 最大顶点数
#define MAXE 1000   // 最大边数
#define INF INT_MAX // 无穷大

// 边结构体
typedef struct {
    int u, v;       // 边的两个端点
} Edge;

// 优先队列节点(搜索节点)
typedef struct {
    int cost;       // 已选顶点的权值和 g(n)
    int h;          // 启发式函数值 h(n)
    int f;          // 优先级 f(n) = cost + h
    int selected[MAXN]; // 顶点选择状态 1-选 0-未选
    int covered[MAXE];  // 边覆盖状态 1-覆盖 0-未覆盖
} Node;

// 全局变量
int n, m;                    // 顶点数 边数
int w[MAXN];                 // 顶点权值
Edge edges[MAXE];            // 边集合
int best_cost;               // 最优解的权值和
int best_selected[MAXN];     // 最优解的顶点选择状态
int node_count;              // 生成节点计数(用于调试)

// 优先队列(最小堆)
Node heap[1000000];
int heap_size;

// 交换两个节点
void swap(Node *a, Node *b) {
    Node tmp = *a;
    *a = *b;
    *b = tmp;
}

// 堆的上浮操作
void heapify_up(int idx) {
    while (idx > 0) {
        int parent = (idx - 1) / 2;
        if (heap[parent].f <= heap[idx].f) break;
        swap(&heap[parent], &heap[idx]);
        idx = parent;
    }
}

// 堆的下沉操作
void heapify_down(int idx) {
    while (1) {
        int left = 2 * idx + 1;
        int right = 2 * idx + 2;
        int smallest = idx;
        if (left < heap_size && heap[left].f < heap[smallest].f)
            smallest = left;
        if (right < heap_size && heap[right].f < heap[smallest].f)
            smallest = right;
        if (smallest == idx) break;
        swap(&heap[idx], &heap[smallest]);
        idx = smallest;
    }
}

// 节点入队
void enqueue(Node node) {
    heap[heap_size++] = node;
    heapify_up(heap_size - 1);
    node_count++;
}

// 节点出队
Node dequeue() {
    Node top = heap[0];
    heap[0] = heap[--heap_size];
    heapify_down(0);
    return top;
}

// 计算启发式函数值 h(n)
int calc_heuristic(int covered[]) {
    int h = 0;
    for (int i = 0; i < m; i++) {
        if (!covered[i]) {
            // 未覆盖边取两端点权值较小值累加
            int u = edges[i].u - 1;
            int v = edges[i].v - 1;
            h += (w[u] < w[v]) ? w[u] : w[v];
        }
    }
    return h;
}

// 检查当前节点是否为完整顶点覆盖
int is_complete(int covered[]) {
    for (int i = 0; i < m; i++) {
        if (!covered[i]) return 0;
    }
    return 1;
}

// 分支限界法主搜索函数
void branch_and_bound() {
    // 初始化最优解
    best_cost = INF;
    memset(best_selected, 0, sizeof(best_selected));
    heap_size = 0;
    node_count = 0;

    // 根节点初始化
    Node root;
    root.cost = 0;
    memset(root.selected, 0, sizeof(root.selected));
    memset(root.covered, 0, sizeof(root.covered));
    root.h = calc_heuristic(root.covered);
    root.f = root.cost + root.h;
    enqueue(root);

    while (heap_size > 0) {
        Node cur = dequeue();

        // 剪枝:当前 f 值 >= 最优解,无需扩展
        if (cur.f >= best_cost) continue;

        // 检查是否为完整覆盖,更新最优解
        if (is_complete(cur.covered)) {
            if (cur.cost < best_cost) {
                best_cost = cur.cost;
                memcpy(best_selected, cur.selected, sizeof(cur.selected));
            }
            continue;
        }

        // 找到第一条未覆盖的边,作为分支依据
        int e_idx = -1;
        for (int i = 0; i < m; i++) {
            if (!cur.covered[i]) {
                e_idx = i;
                break;
            }
        }
        if (e_idx == -1) continue;

        int u = edges[e_idx].u - 1;
        int v = edges[e_idx].v - 1;

        // 分支1:选择顶点 u
        Node child1 = cur;
        if (!child1.selected[u]) {
            child1.selected[u] = 1;
            child1.cost += w[u];
            // 标记所有与 u 相关的边为已覆盖
            for (int i = 0; i < m; i++) {
                if (edges[i].u - 1 == u || edges[i].v - 1 == u) {
                    child1.covered[i] = 1;
                }
            }
        }
        child1.h = calc_heuristic(child1.covered);
        child1.f = child1.cost + child1.h;
        if (child1.f < best_cost) {
            enqueue(child1);
        }

        // 分支2:选择顶点 v
        Node child2 = cur;
        if (!child2.selected[v]) {
            child2.selected[v] = 1;
            child2.cost += w[v];
            // 标记所有与 v 相关的边为已覆盖
            for (int i = 0; i < m; i++) {
                if (edges[i].u - 1 == v || edges[i].v - 1 == v) {
                    child2.covered[i] = 1;
                }
            }
        }
        child2.h = calc_heuristic(child2.covered);
        child2.f = child2.cost + child2.h;
        if (child2.f < best_cost) {
            enqueue(child2);
        }
    }
}

int main() {
    // 读取输入
    FILE *fin = fopen("input.txt", "r");
    if (!fin) {
        printf("无法打开输入文件 input.txt\n");
        return 1;
    }
    fscanf(fin, "%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        fscanf(fin, "%d", &w[i]);
    }
    for (int i = 0; i < m; i++) {
        fscanf(fin, "%d%d", &edges[i].u, &edges[i].v);
    }
    fclose(fin);

    // 执行分支限界搜索
    branch_and_bound();

    // 输出结果
    FILE *fout = fopen("output.txt", "w");
    if (!fout) {
        printf("无法打开输出文件 output.txt\n");
        return 1;
    }
    fprintf(fout, "%d\n", best_cost);
    for (int i = 0; i < n; i++) {
        fprintf(fout, "%d ", best_selected[i]);
    }
    fclose(fout);

    // 控制台输出信息
    printf("搜索完成!\n");
    printf("生成节点总数:%d\n", node_count);
    printf("最小权顶点覆盖总和:%d\n", best_cost);
    printf("最优解选择状态:");
    for (int i = 0; i < n; i++) {
        printf("%d ", best_selected[i]);
    }
    printf("\n");

    return 0;
}

最小权支配集问题

问题定义

给定一个赋权无向图 G = (V, E) ,其中:

· V = {1, 2, . . . , n} 是顶点集合,包含 n 个顶点

· E 是边集合,包含 m 条边

· 每个顶点 v ∈ V 有一个非负权值 w(v) > 0

如果子集 D \subseteq V 满足:对于任意顶点 v \in V ,要么 v \in D ,要么存在一个顶点 u \in D 使得边 (u, v) \in E ,则称 D 为图 G 的一个支配集。

图 G 的最小权重支配集是指 G 中所有支配集中顶点权重之和最小的支配集。

算法设计

对于给定的无向图 G ,设计一个优先队列式分支限界法,计算 G 的最小权重支配集。

数据输入

输入数据由文件 input.txt 给出,格式如下:

复制代码
n m
w(1) w(2) ... w(n)
u₁ v₁
u₂ v₂
...
uₘ vₘ

其中:

· 第1行包含2个正整数 n 和 m ,表示给定的图 G 有 n 个顶点和 m 条边

· 第2行包含 n 个正整数,表示 n 个顶点的权重

· 接下来的 m 行,每行包含2个正整数 u 和 v ,表示图 G 的一条边 (u, v)

结果输出

将计算的最小权重支配集的顶点权重之和以及最优解输出到文件 output.txt,格式如下:

复制代码
最小权重支配集顶点权重之和
x₁ x₂ ... xₙ

其中:

· 第1行是最小权重支配集的顶点权重之和

· 第2行是最优解 x_i (1 \leq i \leq n) , x_i = 0 表示顶点 i 不在最小权重支配集中, x_i = 1 表示顶点 i 在最小权重支配集中

示例

输入文件示例 (input.txt)

复制代码
7 9
3 5 2 4 6 1 3
1 2
1 3
2 4
2 5
3 6
3 7
4 5
5 6
6 7

图结构说明:

· 顶点数:7,边数:9

· 顶点权重:w(1)=3, w(2)=5, w(3)=2, w(4)=4, w(5)=6, w(6)=1, w(7)=3

· 边连接情况如上所示

输出文件示例 (output.txt)

复制代码
8
0 1 1 0 0 0 0

解释:

· 最小权重支配集的权重之和为8

· 选择的顶点集合为 {2, 3},对应向量为 0 1 1 0 0 0 0

· 顶点2支配顶点1,2,4,5

· 顶点3支配顶点3,6,7

· 所有顶点都被支配(每个顶点要么在{2,3}中,要么与{2,3}中至少一个顶点相邻)

问题特性

  1. NP难问题:最小权重支配集问题是NP难的,即使所有权重都为1。
  2. 近似难度:对于一般图,不存在多项式时间的 (1-\varepsilon)\ln n 近似算法,除非 NP \subseteq DTIME(n^{O(\log \log n)}) 。
  3. 贪婪近似:存在 O(\log n) 近似算法,基于贪心选择单位成本覆盖最多未支配顶点的顶点。

与最小权顶点覆盖的区别

方面 最小权顶点覆盖 最小权重支配集

覆盖对象 边(每条边至少一个端点在集合中) 顶点(每个顶点要么在集合中,要么有邻居在集合中)

约束条件 对每条边(u,v):x_u + x_v ≥ 1 对每个顶点v:∑_{u∈N[v]} x_u ≥ 1,其中N[v]是v的闭邻域

近似比 2-近似 O(log n)-近似

二分图 多项式时间可解 NP难

应用领域

最小权重支配集问题在以下领域有重要应用:

  1. 无线传感器网络:选择最少数量的传感器(考虑部署成本)来监控所有区域
  2. 社交网络:选择最少的有影响力的人(考虑成本)来影响整个网络
  3. 设施选址:选择最少的设施点(考虑建设成本)服务所有客户
  4. 网络安全:选择最少的监控点(考虑部署成本)来监控整个网络
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define MAXN 25          // 最大节点数,适配 n ≤ 20
#define MAXM 200         // 最大边数
#define INF INT_MAX      // 无穷大,代表不可达状态

// 边结构体
typedef struct {
    int u, v;
} Edge;

// 优先队列节点(搜索状态)
typedef struct {
    int cost;            // 已选节点的总成本 g(n)
    int h;               // 启发式下界 h(n)
    int f;               // 优先级 f(n) = cost + h
    int selected[MAXN];  // 节点选择状态 1-部署 0-不部署
    int covered[MAXN];   // 节点覆盖状态 1-覆盖 0-未覆盖
} Node;

// 全局变量
int n, m;
int w[MAXN];             // 节点部署成本
Edge edges[MAXM];
int adj[MAXN][MAXN];     // 邻接矩阵:adj[u][v]=1 表示 u 和 v 相邻
int best_cost;           // 最优解总成本
int best_selected[MAXN]; // 最优解选择方案
int heap_size;           // 优先队列大小
Node heap[1000000];      // 优先队列(最小堆)

// 交换两个节点
static void swap(Node *a, Node *b) {
    Node tmp = *a;
    *a = *b;
    *b = tmp;
}

// 堆上浮操作,维护最小堆性质
static void heapify_up(int idx) {
    while (idx > 0) {
        int parent = (idx - 1) / 2;
        if (heap[parent].f <= heap[idx].f) break;
        swap(&heap[parent], &heap[idx]);
        idx = parent;
    }
}

// 堆下沉操作,维护最小堆性质
static void heapify_down(int idx) {
    while (1) {
        int left = 2 * idx + 1;
        int right = 2 * idx + 2;
        int smallest = idx;

        if (left < heap_size && heap[left].f < heap[smallest].f)
            smallest = left;
        if (right < heap_size && heap[right].f < heap[smallest].f)
            smallest = right;
        if (smallest == idx) break;

        swap(&heap[idx], &heap[smallest]);
        idx = smallest;
    }
}

// 节点入队
static void enqueue(Node node) {
    heap[heap_size++] = node;
    heapify_up(heap_size - 1);
}

// 节点出队(返回堆顶,即 f 值最小的节点)
static Node dequeue() {
    Node top = heap[0];
    heap[0] = heap[--heap_size];
    heapify_down(0);
    return top;
}

// 计算启发式下界 h(n):对未覆盖节点,取自身或相邻节点的最小成本累加
static int calc_heuristic(int covered[]) {
    int h = 0;
    for (int u = 1; u <= n; u++) {
        if (covered[u]) continue;

        // 找 u 自身和相邻节点的最小成本
        int min_w = w[u];
        for (int v = 1; v <= n; v++) {
            if (adj[u][v] && w[v] < min_w) {
                min_w = w[v];
            }
        }
        h += min_w;
    }
    return h;
}

// 检查是否所有节点都被覆盖
static int is_all_covered(int covered[]) {
    for (int u = 1; u <= n; u++) {
        if (!covered[u]) return 0;
    }
    return 1;
}

// 部署节点 u 后,更新覆盖状态
static void update_cover(int covered[], int u, int val) {
    covered[u] = val;
    for (int v = 1; v <= n; v++) {
        if (adj[u][v]) {
            covered[v] = val;
        }
    }
}

// 分支限界法主搜索函数
static void branch_and_bound() {
    // 初始化最优解
    best_cost = INF;
    memset(best_selected, 0, sizeof(best_selected));
    heap_size = 0;

    // 初始化根节点:无部署,无覆盖
    Node root;
    root.cost = 0;
    memset(root.selected, 0, sizeof(root.selected));
    memset(root.covered, 0, sizeof(root.covered));
    root.h = calc_heuristic(root.covered);
    root.f = root.cost + root.h;
    enqueue(root);

    while (heap_size > 0) {
        Node cur = dequeue();

        // 剪枝:当前估计总成本 ≥ 已知最优解,无需扩展
        if (cur.f >= best_cost) continue;

        // 找到完整覆盖方案,更新最优解
        if (is_all_covered(cur.covered)) {
            if (cur.cost < best_cost) {
                best_cost = cur.cost;
                memcpy(best_selected, cur.selected, sizeof(best_selected));
            }
            continue;
        }

        // 选择第一个未覆盖的节点作为分支点
        int u = -1;
        for (int i = 1; i <= n; i++) {
            if (!cur.covered[i]) {
                u = i;
                break;
            }
        }
        if (u == -1) continue;

        // 分支1:在 u 节点部署监控
        Node child1 = cur;
        if (!child1.selected[u]) {
            child1.selected[u] = 1;
            child1.cost += w[u];
            update_cover(child1.covered, u, 1);
        }
        child1.h = calc_heuristic(child1.covered);
        child1.f = child1.cost + child1.h;
        if (child1.f < best_cost) {
            enqueue(child1);
        }

        // 分支2:不在 u 部署,选择 u 的一个相邻节点 v 部署
        for (int v = 1; v <= n; v++) {
            if (!adj[u][v] || child1.selected[v]) continue;

            Node child2 = cur;
            child2.selected[v] = 1;
            child2.cost += w[v];
            update_cover(child2.covered, v, 1);
            child2.h = calc_heuristic(child2.covered);
            child2.f = child2.cost + child2.h;
            if (child2.f < best_cost) {
                enqueue(child2);
            }
        }
    }
}

// 读取输入
static void read_input() {
    FILE *fin = fopen("input.txt", "r");
    if (!fin) {
        printf("Error: 无法打开 input.txt\n");
        exit(1);
    }

    fscanf(fin, "%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        fscanf(fin, "%d", &w[i]);
    }

    // 初始化邻接矩阵
    memset(adj, 0, sizeof(adj));
    for (int i = 0; i < m; i++) {
        int u, v;
        fscanf(fin, "%d%d", &u, &v);
        edges[i] = (Edge){u, v};
        adj[u][v] = 1;
        adj[v][u] = 1;
    }

    fclose(fin);
}

// 写入输出
static void write_output() {
    FILE *fout = fopen("output.txt", "w");
    if (!fout) {
        printf("Error: 无法打开 output.txt\n");
        exit(1);
    }

    if (best_cost == INF) {
        fprintf(fout, "-1\n");
        fprintf(fout, "无解\n");
    } else {
        fprintf(fout, "%d\n", best_cost);
        for (int i = 1; i <= n; i++) {
            fprintf(fout, "%d ", best_selected[i]);
        }
        fprintf(fout, "\n");
    }

    fclose(fout);
}

int main() {
    read_input();
    branch_and_bound();
    write_output();

    // 控制台打印结果
    printf("最小部署成本: %d\n", best_cost);
    printf("部署方案: ");
    for (int i = 1; i <= n; i++) {
        printf("%d ", best_selected[i]);
    }
    printf("\n");

    return 0;
}
相关推荐
旺仔小拳头..10 小时前
数据结构(四)————图
图论
点云SLAM1 天前
boost中boost::adjacency_list 与 boost::adjacency_list_traits
数据结构·图论·最大流·boos中图模块·泛型算法·traits 解耦设计·adjacency_list
Bruce_kaizy1 天前
c++图论————最短路之Floyd&Dijkstra算法
c++·算法·图论
xu_yule1 天前
算法基础(图论)—拓扑排序
c++·算法·动态规划·图论·拓扑排序·aov网
Andyshengwx2 天前
图论 最小生成树 MST问题
c++·算法·图论
賬號封禁中miu2 天前
图论之最小生成树
java·数据结构·算法·图论
脑海科技实验室2 天前
Ageing Res Rev:绘制阿尔茨海默病分期进展图:一项利用静息态fMRI和图论的综合性横断面及纵向研究
图论·fmri·阿尔茨海默病
闻缺陷则喜何志丹2 天前
【图论 拓扑排序 贪心 临项交换】P5603 小 C 与桌游 题解|普及+
c++·算法·图论·贪心·拓扑排序·洛谷·临项交换
闻缺陷则喜何志丹2 天前
【图论 BFS染色 并集查找 】P3663 [USACO17FEB] Why Did the Cow Cross the Road III S|普及+
c++·算法·图论·染色法·宽度优先·并集查找