1.图的定义

图结构是一种非线性的数据结构,由顶点(Vertex)和(Edge)组成,每条边的两端必须是图的两个顶点(可以是相同的顶点),记号G(V,E)表示图G的顶点集为V,边集为E。
图可以分为有向图无向图。有向图的所有边都有方向;无向图中所有边都是双向的,无向边连接的两个顶点可以互相到达。在一些问题中,可以把无向图当作所有边都是正向和负向的两条有向边组成。
顶点的是指和该顶点相连的边的条数,对于有向图来说,顶点的出边条数称为顶点的出度,入边条数称为入度。顶点和边都可以具有权值,分别叫做点权边权

2.图的存储结构

(1)邻接矩阵
设图G(V,E)的顶点标号为0,1,…,N-1,令二维数组G[N][N]的两维都表示图的顶点标号。如果G[i][j]=1,表示顶点i和j之间存在边,为0则不存在。如果存在边权,可以用G[i][j]存放边权,不存在的边可以把G[i][j]设为0、-1或是一个很大的数。
由于需要开一个二维数组,在顶点数较大的情况下可能会超过题目内存限制,因此邻接矩阵只适用于顶点数目不太大(一般不超过1000)的题目
(2)邻接表
把每个顶点的所有出边放在一个列表中,所有列表合起来被称为邻接表,记为Adj[N]。每个顶点的出边列表可以用链表来实现。
邻接表可以借助vector简易地实现,开一个vector数组Adj[N],这样每个Adj[i]就都是一个变长数组。如果邻接表只存放每条边的终点编号,不存放边权,可以如下定义:

vector<int> Adj[N];

添加一条从顶点1到顶点3的有向边:

Adj[1].push_back(3);

添加无向边:

Adj[1].push_back(3);
Adj[3].push_back(1);

如果需要同时存放顶点和边权:

struct Node{
	int v;
	int w;
};

vector<Node> Adj[N];

添加有向边:

Node temp;
temp.v=3;
temp.w=2;
Adj[1].push_back(temp);

3.图的遍历

(1)DFS
首先引入两个概念:
连通分量。在无向图中,如果两个顶点之间可以相互到达(可以是通过一定路径间接到达),就称这两个顶点连通。如果图G任意两个顶点都连通,则称G为连通图。否则为非连通图,其中的极大连通子图称为连通分量。
强连通分量。在有向图中,如果两个顶点可以各自通过一条有向路径到达另一个顶点,则称这两个顶点强连通。如果图G任意两个顶点都强连通,则称G为强连通图。否则为非强连通图,其中的极大连通子图称为强连通分量。

想要遍历整个图,只需对所有的连通分量分别进行遍历,将经过的顶点设置为已访问,下次递归碰到这个顶点时就不再处理,直到图中所有顶点都被访问。
1.邻接矩阵版

const int MAXV=1000;
const int INF=1000000000;
int n,G[MAXV][MAXV];
bool vis[MAXV]={false};

//访问顶点u
void DFS(int u,int depth){    //u为当前访问顶点,depth为深度
	vis[u]=true;
	for(int u=0;u<n;u++){
		if(vis[u]==false && G[MAXV][MAXV]!=INF)
			DFS(u,depth+1);
	}
}

//遍历图G
void DFSTrave(){
	for(int u=0;u<n;u++){
		if(vis[u]==false)
			DFS(u,1);    //初始为第一层
	}
}

2.邻接表版

const int MAXV=1000;
int n;
vector<int> Adj[MAXV];
bool vis[MAXV]={false};

//访问顶点u
void DFS(int u,int depth){
	vis[u]=true;
	for(int v=0;v<Adj[u].size();v++){
		int t=Adj[u][v];
		if(vis[t]==false)
			DFS(t,depth+1);
	}
}

//遍历图G
void DFSTrave(){
	for(int u=0;u<n;u++){
		if(vis[u]==false)
			DFS(u,1);    //初始为第一层
	}
}

实例:PAT 1034 Head of a Gang

One way that the police finds the head of a gang is to check people’s phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls made between the two persons. A “Gang” is a cluster of more than 2 persons who are related to each other with total relation weight being greater than a given threshold K. In each gang, the one with maximum total weight is the head. Now given a list of phone calls, you are supposed to find the gangs and the heads.

Input Specification:
Each input file contains one test case. For each case, the first line contains two positive numbers N and K (both less than or equal to 1000), the number of phone calls and the weight threthold, respectively. Then N lines follow, each in the following format:

Name1 Name2 Time

where Name1 and Name2 are the names of people at the two ends of the call, and Time is the length of the call. A name is a string of three capital letters chosen from A-Z. A time length is a positive integer which is no more than 1000 minutes.

Output Specification:
For each test case, first print in a line the total number of gangs. Then for each gang, print in a line the name of the head and the total number of the members. It is guaranteed that the head is unique for each gang. The output must be sorted according to the alphabetical order of the names of the heads.

Sample Input 1:

8 59
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10

Sample Output 1:

2
AAA 3
GGG 3

Sample Input 2:

8 70
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10

Sample Output 2:

0

题意:给出若干人的姓名和他们之间的通话时长,通过通话关系分为不同的组,每个组总边权为改组的所有通话时长总和。如果一个组总边权超过给定阈值K,并且成员数量大于2,则认定该组是一个犯罪团伙,点权最大的成员被认定为头目。题目要求输出团伙数量以及每个团伙的头目姓名和成员数量。
思路:题目给定的是字符串姓名,要想形成图先要通过map将字符串(姓名)映射成数字(编号),在读入数据时将每个顶点的点权加上权值。接着使用DFS遍历图,获取每个连通块的头目、成员个数、总边权,判断是否能形成团伙。最后将团伙的头目和成员数量使用map形成映射输出即可。

AC代码:

#include <stdio.h>
#include <string>
#include <iostream>
#include <map>
using namespace std;

const int MAXV=2010;
int N,K;
int G[MAXV][MAXV]={0};
int weight[MAXV];
bool vis[MAXV]={false};
int num;    //当前人数

map<string,int> StringToInt;
map<int,string> IntToString;
map<string,int> gang;

int change(string str){
    if(StringToInt.find(str)!=StringToInt.end())
        return StringToInt[str];
    else{
        StringToInt[str]=num;
        IntToString[num]=str;
        return num++;
    }
}

void DFS(int u,int& head,int& numMember,int& totalValue){
    numMember++;
    vis[u]=true;
    if(weight[u]>weight[head])
        head=u;
    for(int v=0;v<num;v++){
        if(G[u][v]!=0){
            totalValue+=G[u][v];
            G[u][v]=G[v][u]=0;    //删除这条边,防止回头
            if(vis[v]==false)
                DFS(v,head,numMember,totalValue);
        }
    }
}

void DFSTrave(){
    for(int u=0;u<num;u++){
        if(vis[u]==false){
            int head=u,numMember=0,totalValue=0;
            DFS(u,head,numMember,totalValue);
            if(numMember>2 && totalValue>K)
                gang[IntToString[head]]=numMember;
        }
    }
}

int main(){
    string str1,str2;
    int w;
    scanf("%d%d",&N,&K);
    for(int i=1;i<=N;i++){
        cin>>str1>>str2>>w;
        int id1=change(str1);
        int id2=change(str2);
        G[id1][id2]+=w;
        G[id2][id1]+=w;
        weight[id1]+=w;
        weight[id2]+=w;
    }
    DFSTrave();
    printf("%d\n",gang.size());
    for(map<string,int>::iterator it=gang.begin();it!=gang.end();it++){
        cout<<it->first<<" "<<it->second<<endl;
    }
    return 0;
}

(2)BFS
使用BFS遍历图需要一个队列,通过反复取出队首顶点,将该顶点可以到达的未曾加入队列的顶点全部入队,直到队列为空时遍历结束。
1.邻接矩阵版

const int MAXV=1000;
int n;
int G[MAXV][MAXV]={0};
bool vis[MAXV]={false}    //如果顶点i入过队,则vis[i]=true

void BFS(int u){    //遍历顶点u所在的连通块
	queue<int> q;
	q.push(u);
	vis[u]=true;
	while(!q.empty()){
		int t=q.front();
		q.pop();
		for(int v=0;v<n;v++){
			if(G[t][v]!=0 && vis[v]==false){
				q.push(v);
				vis[v]=true;
			}
		}
	}
}

void BFSTrave(){    //遍历图G
	for(int u=0;u<n;u++){
		if(vis[u]==false)
			BFS(u);
	}
}

2.邻接表版

const int MAXV=2000;
int n;
vector<int> Adj[MAXV];
bool vis[MAXV]={false};

void BFS(int u){
	queue<int> q;
	q.push(u);
	while(!q.empty()){
		int t=q.front();
		q.pop();
		for(int i=0;i<Adj[u].size();i++){
			int v=Adj[t][i];
			if(vis[v]==false){
				q.push(v);
				vis[v]=true;
			}
		}
	}
}

void BFSTrave(){
	for(int u=0;u<n;u++){
		if(vis[u]==false)
			BFS(u);
	}
}

如果在给定BFS初始点情况下,需要输出该连通块内所有其他顶点的层号,可以使用结构体:

//邻接表版(邻接矩阵同理)
struct Node{
	int id;
	int layer;
};

const int MAXV=2000;
int n;
vector<Node> Adj[MAXV];
bool vis[MAXV]={false};

void BFS(int u){
	Node start;
	start.id=u;
	start.layer=0;
	queue<Node> q;
	q.push(start);
	vis[start.id]=true;
	while(!q.empty()){
		Node top=q.front();
		q.pop();
		vis[top.id]=true;
		int t=top.id;
		for(int i=0;i<Adj[t].size();i++){
			Node v=Adj[t][i];
			v.layer=top.layer+1;
			if(vis[v.id]==false){
				q.push(v);
				vis[v.id]=true;
			}
		}
	}
}

实例:PAT 1076 Forwards on Weibo

Weibo is known as the Chinese version of Twitter. One user on Weibo may have many followers, and may follow many other users as well. Hence a social network is formed with followers relations. When a user makes a post on Weibo, all his/her followers can view and forward his/her post, which can then be forwarded again by their followers. Now given a social network, you are supposed to calculate the maximum potential amount of forwards for any specific user, assuming that only L levels of indirect followers are counted.

Input Specification:
Each input file contains one test case. For each case, the first line contains 2 positive integers: N (≤1000), the number of users; and L (≤6), the number of levels of indirect followers that are counted. Hence it is assumed that all the users are numbered from 1 to N. Then N lines follow, each in the format:

M[i] user_list[i]

where M[i] (≤100) is the total number of people that user[i] follows; and user_list[i] is a list of the M[i] users that followed by user[i]. It is guaranteed that no one can follow oneself. All the numbers are separated by a space.
Then finally a positive K is given, followed by K UserID’s for query.

Output Specification:
For each UserID, you are supposed to print in one line the maximum potential amount of forwards this user can trigger, assuming that everyone who can view the initial post will forward it once, and that only L levels of indirect followers are counted.

Sample Input:

7 3
3 2 3 4
0
2 5 6
2 3 1
2 3 4
1 4
1 5
2 2 6

Sample Output:

4
5

题意:给出用户数量N和规定层数L,分别输入N个用户关注的用户编号(从1开始),最后给出指定用户编号,要求计算该用户发布的信息最多能被转发多少次(转发层数不超过L,关注了该用户的其他用户可以看到消息并转发)。
思路:首先根据用户的关注情况构建图,从被关注者指向关注者建立有向边。由于需要控制层数,可以建立结构体存储用户编号和层数。最后从待查询的编号开始BFS,返回转发总数即可。

AC代码:

#include <stdio.h>
#include <vector>
#include <queue>
#include <string.h>
using namespace std;

struct Node{
    int id;
    int layer;
};
const int MAXV=1010;
int n,l;
vector<Node> Adj[MAXV];
bool vis[MAXV]={false};

int BFS(int u){
    int number=0;
    queue<Node> q;
    Node start;
    start.id=u;
    start.layer=0;
    q.push(start);
    vis[start.id]=true;
    while(!q.empty()){
        Node top=q.front();
        q.pop();
        int u=top.id;
        for(int i=0;i<Adj[u].size();i++){
            Node next=Adj[u][i];
            next.layer=top.layer+1;
            if(vis[next.id]==false && next.layer<=l){
                q.push(next);
                vis[next.id]=true;
                number++;
            }
        }
    }
    return number;
}

int main(){
    int k,num,a,q;
    scanf("%d%d",&n,&l);
    for(int i=1;i<=n;i++){
        Node t;
        t.id=i;
        scanf("%d",&num);
        for(int i=0;i<num;i++){
            scanf("%d",&a);
            Adj[a].push_back(t);
        }
    }
    scanf("%d",&k);
    for(int i=0;i<k;i++){
        memset(vis,false,sizeof(vis));
        scanf("%d",&q);
        int number=BFS(q);
        printf("%d\n",number);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值