关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 算法设计 »算法题系列:顶点覆盖问题

    算法题系列:顶点覆盖问题

    2015-05-21 00:00:00 出处:2liang.me
    分享

    顶点覆盖问题可以用几种不同的算法来实现,该文文章使用的是分支限界法来实现,或许以后会介绍其他的实现算法,嘿嘿。

    1.问题描述

    给定一个N个点M条边的无向图G(点的编号从1至N),问是否存在一个不超过K个点的集合S,使得G中的每条边都至少有一个点在集合S中。

    例如,如下图所示的无向图G(报告中算法分析过程中一直使用下面的图G)

    (1)假如选择包含点1,2,6这3个点的集合S不能满足条件,因为边(3,7)两个端点都不在S中。

    (2)假如选择包含点1,2,6,7这4个点的集合S虽然满足条件,但是它使用了4个点,其实可以使用更少的点,如下面(3)所示

    (3)假如选择包含点1,3,5这3个点的集合S便满足条件,使得G中的每条边都至少有一个点在集合S中。

    2.解题思路

    我的解题思路基于分支定界和贪心两个策略,用一个优先队列维护当前可行的节点,每个节点维护着该节点情况下还可以选择的顶点数目k、需要覆盖的剩余边数e、顶点的状态state、顶点的边数edge等信息,这些节点的排序遵循下面的贪心策略,节点的扩展遵循下面的分支定界策略。总体思路是:

    ①将原图数据构造成一个解空间树的节点,利用定界策略判断是否有解,假如无解直接退出,假如有可能有解则插入到优先队列中;

    ②若优先队列不为空,那么便从优先队列中取出第一个可行的节点,进入步骤③,假如优先队列为空则退出;

    ③判断当前节点是否满足解的条件,假如满足便输出解退出,假如不满足便进入步骤④;

    ④检查当前节点是否可以扩展,不能扩展的话便进入②继续循环,假如能扩展的话则扩展,然后验证扩展到左右节点是否有解,将有解的扩展节点插入到优先队列中,然后进入②继续循环。

    下面分别介绍下分支定界和贪心这两个策略:

    (1)分支定界策略

    首先,界的选择。在一个确定的无向图G中,每个顶点的边即确定了,那么对于该无向图中k个顶点能够覆盖的最多的边数e也就可以确定了!只要对顶点按照边的数目降序排列,然后选择前k个顶点,将它们的边数相加即能得到一个边数上界!因为这k个顶点相互之间可能有边存在也可能没有,所以这是个上界,而且有可能达到。以图G为例,各个顶点的边数统计,并采用降序排列的结果如下:

    假设取k=3个点,那么有Up(e)=(3+3+2)=8 > 7 条边(7为图G的总边数),也就是说,假如从图G中取3个点,要覆盖8条边是有可能的。但是,假如取k=2个点,那么有Up(e)=(3+3)=6 < 7 条边,说明从图G中取2个点,是不可能覆盖G中的全部7条边的!基于这个上界,可以在分支树中扩展出来的节点进行验证,已知它还可以选择的顶点数目以及还需要覆盖的边的条数,加上顶点的状态(下面会分析说明)即可判断当前节点是否存在解!假如不存在即可进行剪枝了。

    其次,顶点的状态。该策略中顶点有三种状态,分别为已经选择了的状态S1,不选择的状态S2,可以选择的状态S3。其中,不选择的状态S2对应解空间树中的右节点,不选择该节点,然后设置该节点为不选择状态S2。这点很重要,因为有了这个状态,可以使得上界的判断更为精确,因为只能从剩余顶点集中选择那些状态S3的顶点,状态S1和S2都不行,那么上界便会更小,也就更加精确,从而利于剪枝!

    (2)贪心策略

    贪心的策略是指可行的结点都是按照还需要覆盖的剩余边数的降序排列,即,每次选择的节点都是可行节点中还需要覆盖的边数最小的那个节点,因为它最接近结果了。

    (3)例子分析

    以图G为例,此时e=7(要覆盖的边数),取k=3,图G用邻接矩阵保存为全局数据,计算每个顶点的边数,然后降序排列。

    步骤①判断是否可能有解,Up(e)=3+3+2=8>7,可能有解,那么将图G构造成一个解空间树的节点,它包含了还能选择的点数k=3,还需要覆盖的边数e=7,每个顶点的边数以及按边数大小的降序排列(上表),每个顶点的状态(初始时都是可选择的状态S3)。然后,将该节点插入到优先队列中,该优先队列是用最小堆实现的,按照前面的贪心策略对队列中的节点进行降序排列。

    步骤②取出了优先队列中的根节点,很显然,还需要覆盖的边数为7,不为0,所以还不满足条件。接下来要检查是否能够进行扩展,从顶点集合中选择状态为可以选择的顶点中边数最多的点,该点存在为顶点2,接着进行扩展,扩展左节点时将还能选择的点数k-1=2,然后计算选择了该点之后删除了几条未覆盖的边,得到还需要覆盖的边数e=4,然后更新所有其他顶点的边数,并重新排序,最后将顶点2的状态设置为已经选择了;扩展右节点时,只要将顶点2的状态设置为不能选择,还能选择的点数k(=3),还需要覆盖的边数e(=7)保持不变。扩展完了之后,同样判断左右节点是否可能有解,假如有解,将该节点插入到优先队列中。这里左右节点都有解,那么将左右节点都插入到优先队列中,因为左节点还需要覆盖的边数e=4小于右节点的e=7,所以根据贪心策略,左节点在右节点的前面。上面两个步骤的图示如下,其中标明了顶点状态颜色。

    算法然后继续进入步骤②,此时取出的是节点是刚才插入的左节点,很显然,还需要覆盖的边数为4,不为0,所以还不满足条件。接下来要检查是否能够进行扩展,从顶点集合中选择状态为可以选择的顶点中边数最多的点,该点存在为顶点3,接着进行扩展,扩展左节点时将还能选择的点数k-1=1,然后计算选择了该点之后删除了几条未覆盖的边,得到还需要覆盖的边数e=2,然后更新所有其他顶点的边数,并重新排序,最后将顶点3的状态设置为已经选择了;扩展右节点时,只要将顶点3的状态设置为不能选择,还能选择的点数k(=3),还需要覆盖的边数e(=7)保持不变。扩展完了之后,同样判断左右节点是否可能有解,假如有解,将该节点插入到优先队列中。这里左右节点都不可能有解,那么直接进入步骤②继续循环。上面这一步的图示如下:

    算法按照上面的方式不断进行,最后满足条件的分支的过程是:

    ①不选择顶点2;②选择顶点3;③选择顶点1;④选择顶点5。

    最后得到的满足条件的解是选择顶点1,3,5。

    (4)复杂度分析

    该算法优先队列使用的是最小堆实现的(O(nlgn)),对顶点按照边排序使用的是快速排序算法(O(nlgn)),解空间树的深度最多为顶点数目n,每层都要进行分支定界,所以每层的时间复杂度为O(nlgn),所以算法总的时间复杂度为O(n^2 lgn)。但是,为了实现分支定界,每个节点保存的信息量较多,空间复杂度较大。(有木有分析错了,我不太会分析复杂度)

    青橙OJ系统的结果为:时间 156ms 空间 1.0MB

    本人对指针领悟能力有限,C++也是一知半解,OJ只能用C或者C++,所以下面的C++代码效率不高,仅供参考,:-)

    #include <iostream>
    #include <vector>
    using namespace std;
    #define MAX_NODE 101
    #define INDEBUG 0
    int8_t graph[MAX_NODE][MAX_NODE];//int -> int8_t
    //int edges[MAX_NODE];//0 is redudent
    //int nodes[MAX_NODE];//the order of node
    int t,m,n,k,a,b;
    class VCNode {//Vertex Cover Node
    public:
        int p;//points can be used
        int e;//edges to cover!!
        int index[MAX_NODE];//the index of each node in array [node], index[k]=i!!
        int edge[MAX_NODE];//MAX_NODE the edge number of each node, edge[i]=j!!
        int node[MAX_NODE];//the order of the node
        int state[MAX_NODE];//the state of each node ** 0 can be used / 1 used / -1 can not be used
    //    int graph[MAX_NODE][MAX_NODE];//the graph on the node//no need,just use the global graph
        // node k is in index[k]=i position in array [node]
        // node i has number of edge[i]=j edges
    };
    class Minheap {//Min Heap
    public:
        vector<VCNode> nodes;
    
        void insert(VCNode node);
        VCNode popmin();
    //  void print();
    };
    void Minheap::insert(VCNode node) {
        nodes.push_back(node);
        //  cout << "size is " << nodes.size() << endl;//
        int curpos = (int)nodes.size() - 1; // current position
        int parent = (curpos - 1) / 2; //parent position
        while (curpos != parent && parent >= 0) { //parent is still in heap
            if (nodes[parent].e > nodes[curpos].e) { //swap parent and child
                VCNode temp = nodes[parent];
                nodes[parent] = nodes[curpos];
                nodes[curpos] = temp;
            } else {
                break; //no longer level up!!!
            }
            curpos = parent; //when curpos=parent=0, exit!!!
            parent = (curpos - 1) / 2; //relocate the parent position
        }
    }
    VCNode Minheap::popmin() {
        VCNode node;
        if (nodes.size() > 0) { //have nodes left
            node = nodes[0]; //get the first element
            nodes.erase(nodes.begin()); //remove the first element
            if (nodes.size() > 0) { //at least have one element more
                VCNode last = nodes[nodes.size() - 1]; //get the last element
                nodes.pop_back(); //pop the last element
                nodes.insert(nodes.begin(), last); //put it in the first place
                int csize = (int)nodes.size(); //current size
                int curpos = 0; //current position
    
                // rebuild the minheap
                while (curpos < (csize / 2)) { //reach to the last parent node!!
                    int left = 2 * curpos + 1; //left child
                    int right = 2 * curpos + 2; //right child
                    int min = left; //min store the min child
                    if (right < csize) { //have left and right childs
                        if (nodes[right].e < nodes[left].e) {
                            min = right;
                        }
                    }
                    if (min < csize) { //min child exist!!
                        if (nodes[min].e < nodes[curpos].e) { //need to swap current position with child
                            VCNode temp = nodes[min];
                            nodes[min] = nodes[curpos];
                            nodes[curpos] = temp;
                        }else { //min child no exits!! exit!!
                            break; //can break now!!
                        }
                    }
                    curpos = min;
                }
            }
        }
        return node;
    }
    //void Minheap::print() {
    //  cout << "print heap" << endl;
    //  for (int i = 0; i < (int)nodes.size(); i++) {
    //      cout << "edge: " << nodes[i].e << " node: " << nodes[i].p << endl;
    //  }
    //  cout << "heap end" << endl;
    //}
    // print array
    void printArray(int a[], int start, int end){
        if (INDEBUG) {
            cout << "print array form " << start << " to " << end << endl;
            for (int i=start; i<=end; i++) {
                cout << a[i] << " ";
            }
            cout << endl << "print array end" << endl;
        }
    }
    // print the graph
    void printGraph(int graph[][MAX_NODE]){
        if (INDEBUG) {
            for(int i=1;i<=n;i++){//0 no need
                for(int j=1;j<=n;j++){
                    cout << graph[i][j] << " ";
                }
                cout << endl;
            }
        }
    }
    // partition function for quick sort
    int partition2(int a[], int low, int high, int b[]){
        int key = a[high];
        int i=low-1;
        for (int j=low; j<high; j++) {
            if (a[j]>=key) {
                i++;
                swap(a[i], a[j]);
                swap(b[i], b[j]);
            }
        }
        swap(a[high], a[i+1]);
        swap(b[high], b[i+1]);
        return i+1;
    }
    // quick sort
    void quicksort2(int a[], int low, int high, int b[]) {
        if (low < high) {
            int p = partition2(a,low,high, b);
            quicksort2(a, low, p-1, b);
            quicksort2(a, p+1, high, b);
        }
    }
    // sum of the first k elements with state==0!!!
    int sumofkmax(int edges[], int p, int nodes[], int state[]){
        quicksort2(edges, 1, n, nodes);
        int sum=0,count=0;
        // edges[i] corresponse to nodes[i], its state is state[nodes[i]]
        for(int i=1;i<=n;i++){//attention to i range!!
            if (state[nodes[i]]==0) {
                sum+=edges[i];
                count++;
                if (count == p) {//enough!
                    break;
                }
            }
        }
        return sum;
    }
    // verify the current node can be achievable
    bool verify(int edges[], int p, int e, int nodes[], int state[]){
        //caculate the sum of the first p max elements in array edges!!
        int sum = sumofkmax(edges, p, nodes, state);
        // edge of nodes[i] is edges[i]!!!
        if(sum >= e){// may be this can be achieved
            return true;
        }
        return false;
    }
    // build the index of node in array [index]
    void buildIndex(int node[],int index[]){
        for (int i=1; i<=n; i++) {
            index[node[i]] = i;
        }
    }
    // get the next node: state==0 && order first!!!
    int nextNode(int state[], int nodes[]){
        for (int i=1; i<=n; i++) {
            if (state[nodes[i]]==0) {
                return nodes[i];
            }
        }
        return -1;
    }
    // generate the left child
    VCNode genLeft(VCNode curnode, int label){
        VCNode left;//choose node label!
        left.p = curnode.p - 1;//remove one node
        left.e = curnode.e;
        for (int i=0; i<=n; i++) {//first copy all infos
            left.index[i]=curnode.index[i];
            left.state[i]=curnode.state[i];//init node state
            left.edge[i]=curnode.edge[i];//copy edge info
            left.node[i]=curnode.node[i];//copy node info
    //        for (int j=0; j<=n; j++) {
    //            left.graph[i][j] = curnode.graph[i][j];
    //        }
        }
        // following code will not use curnode anymore!!
    
        ///
        int sum=0;//removed edge
        for (int j=1; j<=n; j++) {
            //new
            if (label < j && left.state[j]!=1 && graph[label][j]==1 ) {//row!
                sum++;
    //            left.graph[label][j]=0;
                left.edge[left.index[j]]--;//how to cut it down
            }else if(label > j && left.state[j]!=1 && graph[j][label]==1 ){ // col
                sum++;
    //            left.graph[j][label]=0;
                left.edge[left.index[j]]--;//how to cut it down
            }
        }
        ///
    
        left.state[label] = 1;//use label directly!
        left.edge[left.index[label]] = 0;//only use index!!
    //    cout << "remove edge sum is " << sum << endl;
        quicksort2(left.edge, 1, n, left.node);
        left.e = left.e - sum;//remove some edges
        buildIndex(left.node, left.index);
    
        if (INDEBUG) {
            cout << "======== " << label << " gen left begin===========" << endl;
            cout << "edge is " << left.e << " node is " << left.p << endl;
            cout << "array edge:" << endl;
            printArray(left.edge,1,n);
            cout << "array node:" << endl;
            printArray(left.node, 1, n);
            cout << "array index:" << endl;
            printArray(left.index, 1, n);
            cout << "array state:" << endl;
            printArray(left.state, 1, n);
    //        printGraph(left.graph);
            cout << "======== " << label << " gen left end===========" << endl;
        }
    
        return left;
    }
    // generate the right child
    VCNode genRight(VCNode curnode, int label){
        VCNode right;//choose node label!
        right.p = curnode.p;//remain
        right.e = curnode.e;
        for (int i=0; i<=n; i++) {//first copy all infos
            right.index[i]=curnode.index[i];
            right.state[i]=curnode.state[i];//init node state
            right.edge[i]=curnode.edge[i];//copy edge info
            right.node[i]=curnode.node[i];//copy node info
    //        for (int j=0; j<=n; j++) {
    //            right.graph[i][j] = curnode.graph[i][j];
    //        }
        }
        // following code will not use curnode anymore!!
        right.state[label] = -1;//use label directly!
    
        if (INDEBUG) {
            cout << "======== " << label << " gen right begin===========" << endl;
            cout << "edge is " << right.e << " node is " << right.p << endl;
    //        cout << "array edge:" << endl;
    //        printArray(right.edge,1,n);
    //        cout << "array node:" << endl;
    //        printArray(right.node, 1, n);
    //        cout << "array index:" << endl;
    //        printArray(right.index, 1, n);
    //        cout << "array state:" << endl;
    //        printArray(right.state, 1, n);
    //        printGraph(right.graph);
            cout << "======== " << label << " gen right end===========" << endl;
        }
    
        return right;
    }
    // greedy find a way to solve VCP
    void greedyFind(int edges[], int nodes[]/*, int graph[][MAX_NODE]*/){
        VCNode node;
        node.e = m;
        node.p = k;
    
        for (int i=0; i<=n; i++) {
            node.index[i]=0;
            node.state[i]=0;//init node state
            node.edge[i]=edges[i];//copy edge info
            node.node[i]=nodes[i];//copy node info
    //        for (int j=0; j<=n; j++) {
    //            node.graph[i][j] = graph[i][j];
    //        }
        }
        buildIndex(node.node, node.index);
    
        Minheap minheap;
        minheap.insert(node);
    
        while (minheap.nodes.size() > 0) {
            // get the heap top node to extend
            VCNode curnode = minheap.popmin();
    
    //        if (INDEBUG) {
    //            cout << "...current graph..." << endl;
    //            printGraph(curnode.graph);
    //        }
    
            // validate the current node
            if (curnode.e == 0) {
                int points = k - curnode.e;
                cout << points << endl;
                int count = 1;
                for (int i=1; i<=n; i++) {
                    if (curnode.state[i]==1) {
                        if(count == points){
                            cout << i;
                        }else{
                            cout << i << " ";
                        }
                        count++;
                    }
                }
                cout << endl;
                return;
            }
    
            // generate child nodes
            int label = nextNode(curnode.state, curnode.node);//the label of the node
            if (label != -1) {
                // node i is in index[k] position in array [node]
                // node i has number of edge[i] edges
                VCNode left = genLeft(curnode, label);
                VCNode right = genRight(curnode, label);
                if (verify(left.edge, left.p, left.e, left.node, left.state)) {
    //                cout << "insert " << label << " left" << endl;
                    minheap.insert(left);
                }
                if (verify(right.edge, right.p, right.e, right.node, right.state)) {
    //                cout << "insert " << label << " right" << endl;
                    minheap.insert(right);
                }
            }
    
        }
    
        // if not find, then return -1
        cout << -1 << endl;
    
    }
    int main() {
    //    freopen("/Volumes/hujiawei/Users/hujiawei/workspace/appleworkspace/algorithmworks/Exp1-2/Exp1-2/in3.txt", "rt", stdin);//
        cin >> t;
        while(t-->0){
            cin >> n >> m >> k;
    //        int graph[n+1][MAX_NODE];
            for (int i=0; i<= n; i++) {
                for (int j=0; j<= n; j++) {
                    graph[i][j]=0;
                }
            }
            int edges[n+1], nodes[n+1], state[n+1];
            for (int i=0; i<= n; i++) {
                edges[i]=0;
                state[i]=0;
                nodes[i]=i;
            }
            int temp = m;
            while(temp-->0){
                cin >> a >> b;
                graph[min(a, b)][max(a,b)]=1;
    //          graph[a][b]=1;
    //          graph[b][a]=1;//just save half a<=b
                edges[a]++;
                edges[b]++;
            }
            bool flag = verify(edges, k, m, nodes, state);
    
            if (!flag) {//must not be achieved!!!
                cout << -1 << endl;
            }else{
                greedyFind(edges,nodes/*,graph*/);
            }
        }
    
        return 0;
    }
    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    如何从 Windows 11 中完全删除/卸载 OneNote?无解!

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    IT头条

    华为Pura80系列新机预热,余承东力赞其复杂光线下的视频拍摄实力

    01:28

    阿里千问3开源首战告捷:全球下载破千万,国产AI模型崛起新高度!

    01:22

    DeepSeek R1小版本试升级:网友实测编程能力已达到国际一线水平

    23:15

    NVIDIA 与 Dell 合作,大规模交付 Blackwell AI 系统

    20:52

    Cerebras 以最快的 Llama 4 Maverick 性能引领 LLM 推理竞赛

    20:51

    技术热点

    PHP中的随机性——你觉得自己幸运吗?

    搞定Ubuntu Linux下WPA无线上网

    Java使用内存映射实现大文件的上传

    MySQL安全性指南

    MySQL两项性能的基本测试浅谈

    教您使用UniqueIdentifier选取SQL Server主键

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。