1.问题分析

 要想解决最大团问题,也就是求最大完全子图。我们需要了解相关概念,现在有如下图:

(1)完全子图:

给定无向图G=(V,E),其中V是顶点集,E是边集。G'=(V',E')如果顶点集V'∈V,E'∈E,且G'种任意两个点有边相连,则称G'是G的完全子图。例如下面的几个图都是上图的完全子图:

(2)团:

G的完全子图是G的团,当且仅当G'不包含在G的更大的完全子图中,也就是说G'是G的极大完全子图。比如图中(c),(d)是G的团,而(a)(b)不是G的团,因为他们包含在G的更大的完全子图(c)中。

(3)最大团:

G的最大团是指G的所有团中,含顶点数最大的团,比如说(d)就是最大团。

2.算法设计

(1)约束条件:

最大团问题的解空间包含2^n个子集,这些子集存在集合中的某两个结点没边相连的情况。显然这种情况的可能姐不是问题的可行解,故需要设置约束条件来判断是否有情况可能导致问题的可行解。

假设现在扩展结点到t层,那么1~t-1个结点状态(是否已经在团里)已经确定。接下来沿着扩展点左分支进行扩展,此时需要判断是否将 t结点 放进团里。则判断 t结点 和1~t-1 个结点中被选中的结点是否有相连,相连就放进团里,x[t]=1,否则不放进团里,x[t]=0.如图所示:

(2)限界条件(剪枝函数):

假设当前的扩展结点为z,如果z处于第t层,前面1~t-1 层的结点的状态已经确定了。接下来确定t结点的状态。前t个结点状态确定当前已放团内的结点个数(用cn表示),假设t+1 ~ n 的所有结点都放进团,用fn表示,且fn=n-t ,则cn+fn是所有从根出发的路径中经过中间结点z的可行解所包含结点个数的上界。

如果fn+cn小于等于当前最优解bestn,则说明不再需要从中间结点z继续像子孙结点搜索。因此,限界条件可以描述为:cn+fn>bestn。

(3)搜索过程:

从根节点开始,以深度游戏按的方式进行。每次搜索到一个结点,判断约束条件,看是否可以将当前结点加入到团中。如果可以,则沿着当前结点左分支继续向下搜索,如果不可以,判断戒指函数,如果满足则沿着当前结点的右分支继续向下搜索。

3.源代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=111; //设置矩阵的大小
int graph[N][N]; //用邻接矩阵存储图
bool x[N]; //是否将i个顶点加入团中
bool bestx[N]; //记录最优已经放入团中的顶点
int bestn; //记录最优值
int cn; //已经放入团中的结点数
int n;//顶点个数
int m; //边的个数

bool place(int t) //判断是否能放进团中
{
    bool OK =true;
    for (int j=1;j<t;j++)  //判断目前扩展的t顶点和前面t-1个顶点是否相连。
    {
        if(x[j] && graph[t][j]==0) //如果不相连
        {
            OK=false; //返回false
            break;
        }
    }
    return OK; //如果相连。返回true
}

void backtrack(int t) //回溯,递推
{
    if(t>n) //当到达叶子结点
    {
        for(int i=1;i<=n;i++)
        {
            bestx[i]=x[i]; //记录最优值结点号
        }
        bestn=cn; //记录最优值
        return;
    }
    if(place(t)) //如果能放进团中
    {
        x[t]=1;//标为1
        cn++; //个数+1
        backtrack(t+1); //向下递推
        cn--; //向上回溯
    }
    if(cn+n-t>bestn) //限界条件,进入右子树,不能加入团中。
    {
        x[t]=0; //不能放入团中,标为0
        backtrack(t+1); //向下递推。
    }
}

int main()
{
    int u; //结点1
    int v; //结点2
    cout << "请输入结点的个数n;"<< endl;
    cin >> n;
    cout << "请输入边数m:"<< endl;
    cin >>m;
    memset(graph,0,sizeof(graph));
    cout <<"请输入有边相连的两个顶点u和v:"<< endl;
    for (int i=1;i<=m;i++)
    {
        cin >> u>> v;
        graph[u][v]=1;
        graph[v][u]=1;
    }
    bestn=0;
    cn=0;
    backtrack(1);
    cout << "最大团的结点个数有:"<< bestn << endl;
    cout << "结点有:"<< endl;
    for (int i=1;i<=n;i++)
    {
        if(bestx[i])
        {
            cout << i << "  "; //输出的是结点号
        }
    }
    return 0;
}

 

4.测试结果

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐