【PAT甲级 DFS 邻接表 C++实现】1034 Head of a Gang (30 分)

题目大意就是:

  • 输入N和K(N是图中边的数量, K是阈值)

  • 然后输入N条边:弧头 弧尾 弧权值

  • 如果一个连通块满足:

    • 所有边权值的总和必须要大于K
    • 人数(顶点数)大于2(3个及以上)

      那么该连通块就是一个Gang(而Gang的头头就是最大权值的顶点)

输出这个图中所有Gang的头头的名字(最大权值顶点的名字)和这个Gang的成员个数(单个连通块中顶点个数)

题中点权值等于和它相连的所有边的权值总和


想了一晚上,终于想出来适合自己的DFS邻接表使用的数据结构。

  • 当难以将顶点信息类型为VexType做为索引的时候,就需要用map<VexType, vector<ArcNode>> G;代替vector<VexType> G[顶点个数],做成顶点信息VexType和有向边集合vector<ArcNode>的映射
  • 因为是map,所以没有出度的边不会出现在上面的G中,如果要访问顶点的权值的话,还需要一个额外的顶点表map<VexType, int> V
  • 因为不会对ArcNode边进行复杂的操作,而只是访问操作,所以可以在ArcNode中加入一个访问标记vis(至于为什么不把顶点设置成一个结构体然后也设置一个访问标记vis,我觉得是因为要对顶点做的操作太多了,设置成结构体增加复杂性)
  • 当顶点信息是字符串时,可以对字符串进行hash再使用vector,就可以使用原来形式的图数据结构了(这个以后再试试)
# include <iostream>
# include <vector>
# include <map>
using namespace std;

/*
如果一个连通块:
    所有边权值的sum必须要大于K
    人数为大于2(3个及以上)
满足以上两个条件,那么就是一个Gang(而Gang的头头就是最大权值的顶点)

然后输出Gang的总个数 and
所有头头的名字和当前头头的Gang的总人数
*/


typedef string VexType;

// 边结点
struct ArcNode{
    
    
    VexType name; // 顶点名字(边的弧头)
    int weight;   // 以顶点为弧尾的边的权重
    bool vis;     // 标记当前边是否被访问过

    ArcNode(VexType _n, int _w, bool _v): name(_n), weight(_w), vis(_v){
    
    }
};

// 当难以将顶点信息做为索引的时候,就需要用map做成代替vector(顶点信息是字符串时,可以对字符串进行hash再使用vector)
// V[u]得到的是顶点u的权值
map<VexType, int> V; // 顶点表(顶点名字:顶点权重)

// 当要访问没有没有出度的顶点时,因为map对这些没加入map的使用默认初始化,所以没有出度的顶点的vector为空,也就不影响了
// G[u]得到的是u的邻接点的vector(或者说有向边集合)
map<VexType, vector<ArcNode>> G; // 邻接表(顶点名字:可达的邻接点集合(有的顶点没有出度,也就不会在这个映射里了))
map<VexType, bool> visited; // 标记顶点是否访问
map<VexType, int> Gang; // 某Gang的头头:帮派权重总和

int N, K;

// 因为一次调用DFS都会遍历一整个连通块,所以下面三个变量是针对一个连通块的
// maxWeightVex:最大权值点    sumArcWeight:边权之和  numPeople当前连通块的顶点个数
void DFS(VexType u, VexType &maxWeightVex, int &sumArcWeight, int &numPeople){
    
    
    // 访问u(这里对点u进行处理访问)
    numPeople++;
    if(V[u] > V[maxWeightVex]) // 如果当前顶点的权值大于之前找到的最大权值
        maxWeightVex = u;
    visited[u] = true;

    // 遍历u的所有邻接点(这里对从点u出去的边进行处理访问)
    for(ArcNode &arcnode: G[u]){
    
     // 似乎不用加引用也可以
        VexType v = arcnode.name; // v是邻接点的名字
        if(arcnode.vis == false){
    
     // 如果当前边的边权还没加入sumArcWeight中(也就是没被访问过),那就加入并标记已访问
            sumArcWeight += arcnode.weight;
            arcnode.vis = true;
        }
        // 如果u的当前邻接点未被访问就去访问,若已访问就进入下一次循环判断下一个邻接点
        if(visited[v] == false){
    
    
            DFS(v, maxWeightVex, sumArcWeight, numPeople); // 去到邻接点v访问邻接点v
        }
    }
}

void DFSTraverse(){
    
    
    /* 遍历不同的连通块
    (通过寻找未访问过的节点,然后DFS访问它,然后它所在的连通块中的所有结点就会被访问,
    于是下一次寻找的未访问的顶点一定是别的连通块)
    */
    for(auto p:  V){
    
     // 遍历顶点表
        VexType u = p.first; // u是顶点名字
        if(visited[u] == false){
    
    
            // maxWVex:最大权值点    sumW:边权之和    numPeople当前连通块的顶点个数
            VexType maxWVex = u;
            int sumW = 0;
            int numPeople = 0;
            DFS(u ,maxWVex, sumW, numPeople);

            // 如果连通块总人数3人以上 && 总边权大于K,让就加入Gang集合
            if(numPeople >= 3 && sumW > K)
                Gang[maxWVex] = numPeople;
        }
    }
}




int main(){
    
    
    cin >> N >> K;
    for(int i=0;i<N;++i){
    
    
        VexType name1, name2;
        int weight;
        cin >> name1 >> name2 >> weight;
        G[name1].push_back({
    
    name2, weight, false}); // 建立邻接表,设置边权和初始化为未访问过
        V[name1] += weight; // 设置点权
        V[name2] += weight; // 设置点权
    }

    DFSTraverse();

    // 然后输出Gang的总个数
    cout << Gang.size() << endl;
    for(auto p: Gang){
    
    
        // 输出头头的名字和当前头头的Gang的总人数
        cout << p.first << " " << p.second << endl;
    }
    
    return 0;
}

下面附上模板DFS邻接表的模板:

# include <iostream>
# include <vector>
# include <map>
using namespace std;

typedef string VexType;

// 边结点
struct ArcNode{
    
    
    VexType name; // 顶点名字(边的弧头)
    int weight;   // 以顶点为弧尾的边的权重
    bool vis;     // 标记当前边是否被访问过
    ArcNode(VexType _n, int _w, bool _v): name(_n), weight(_w), vis(_v){
    
    }
};

// 当难以将顶点信息做为索引的时候,就需要用map做成代替vector(顶点信息是字符串时,可以对字符串进行hash再使用vector)
map<VexType, int> V; // 顶点表(顶点名字:顶点权重)
// 当要访问没有没有出度的顶点时,因为map对这些没加入map的使用默认初始化,所以没有出度的顶点的vector为空,也就不影响了
// G[u]得到的是u的邻接点的vector
map<VexType, vector<ArcNode>> G; // 邻接表(顶点名字:可达的邻接点集合(有的顶点没有出度,也就不会在这个映射里了))
map<VexType, bool> visited; // 标记顶点是否访问
int N; // 边的个数

/* 一次调用DFS都会遍历一整个连通块
    >>> 要统计当前连通块的数据的话可以在参数中添加变量(记得统计的变量是引用【如果不是全局变量的话】),
    >>> 如果是对点做统计那就在访问点那里写语句,
    >>> 如果是对边做统计的话就在访问边那里写) 
*/
void DFS(VexType u){
    
    
    // 访问u(这里对点u进行处理访问)
    cout << "点" <<u << endl;
    visited[u] = true;

    // 遍历u的所有邻接点(这里对从点u出去的边进行处理访问)
    for(ArcNode &arcnode: G[u]){
    
     // 似乎不用加引用也可以
        VexType v = arcnode.name; // v是u邻接点的名字
        if(arcnode.vis == false){
    
     // 如果当前边没被访问过,那就访问并标记
            // 访问边
            cout << "边" <<'[' << u << "-" << v << ']' << endl;
            arcnode.vis = true;
        }
        // 如果u的当前邻接点未被访问就去访问,若已访问就进入下一次循环判断下一个邻接点
        if(visited[v] == false){
    
    
            DFS(v); // 去到邻接点v访问邻接点v
        }
    }
}

void DFSTraverse(){
    
    
    /* 遍历不同的连通块
    (通过寻找未访问过的节点,然后DFS访问它,然后它所在的连通块中的所有结点就会被访问,
    于是下一次寻找的未访问的顶点一定是别的连通块)
    */
    int cnt = 0;
    for(auto p:  V){
    
     // 遍历顶点表
        VexType u = p.first; // u是顶点名字
        // 有多少个连通块这个if里面的语句就会被执行多少次
        if(visited[u] == false){
    
    
            // 可以在这里设立对一个连通块统计的变量
            printf("\n连通块%d:\n", (cnt++)+1);
            DFS(u);
            // 可以在这里处理在DFS里统计的数据
            cout << endl;
        }
    }
}




int main(){
    
    
    cin >> N >> K;
    for(int i=0;i<N;++i){
    
    
        VexType name1, name2;
        int weight;

        cin >> name1 >> name2 >> weight;
        G[name1].push_back({
    
    name2, weight, false}); // 建立邻接表,设置边权和初始化为未访问过
        V[name1] += weight; // 设置点权
        V[name2] += weight; // 设置点权
    }

    DFSTraverse();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/MYMarcoreus/article/details/113706635