原题地址
https://pintia.cn/problem-sets/1268384564738605056/problems/1281571555116650497
解题思路
BFS遍历图,要计算层数。
需要注意的是,一个结点与它自身的距离为0,因此也要计算在内。
方法一(比较麻烦的邻接表+层数记录)
用装模做样的伪代码写的邻接表(有一部分还是没顶住,用了现成的)写的BFS做法,但是是用存储头结点的数组来存端点对应的层数。也就是说只有这么一个数组来存结果,下标对应的是端点序号,数组元素的值变了就是变了。
这里就碰到了一个大坑!!遍历当前层的下一层时层数layer要+1,但是注意,做这一步之前一定要先判断端点是否访问过,不然就是WA...
需要这样做的原因主要是和我存层数的方式有关。
假设我没有经过判断,每一次层数都直接+1,那么设想下面的一种情况:
现在有两个点a,b,与起始点距离都是5,同时这两个点之间是连通的。那么现在我们进行快乐BFS的时候,假定a、b同时被push进了队列,标记已访问。然后继续访问下一层的时候,a先出列,对a的邻接点进行遍历,会遍历到b,那么这时候会发生什么?
for (next = graph->g[now].first; next; next = next->next) {
graph->g[next->v].layer = graph->g[now].layer + 1;
if (!vis[next->v]) {
q.push(next->v);
vis[next->v] = true;
}
}
没错!b对应的层数硬生生被+1了!一切都是乳齿秃然。
而因为我只用了一个数组来存层数,所以...(憨憨叹气)
那么我们要咋做呢?
其实很简单,先判断这个点是否访问过,然后再执行层数+1的操作就好了。
因此以下为正确示范:
for (next = graph->g[now].first; next; next = next->next) {
if (!vis[next->v]) {
//下面这句要先判断再执行
//否则碰到两个距离相同的点时,未经判断是否访问就加的话会多加
graph->g[next->v].layer = graph->g[now].layer + 1;
q.push(next->v);
vis[next->v] = true;
}
}
参考代码(方法一)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef double db;
typedef long long LL;
typedef vector<int> VI;
const int inf = 2e9;
const LL INF = 8e18;
const int maxn = 1e3 + 10;
bool vis[maxn];
struct vnode{
int v;
int layer;
vnode* next;
};
struct edge{
int v1, v2;
};
typedef struct hnode{
int layer;
vnode* first;
}vlist[maxn];
struct gnode{
int nv, ne;
vlist g;
};
void insert(gnode* graph, edge e) {
vnode* newNode = (vnode*)malloc(sizeof(struct vnode));
newNode->v = e.v2;
newNode->next = graph->g[e.v1].first;
graph->g[e.v1].first = newNode;
//无向图,反向还要来一次
newNode = (vnode*)malloc(sizeof(struct vnode));
newNode->v = e.v1;
newNode->next = graph->g[e.v2].first;
graph->g[e.v2].first = newNode;
}
gnode* create(int nv, int ne) {
gnode* graph = (gnode*)malloc(sizeof(struct gnode));
graph->nv = nv;
graph->ne = ne;
for (int i = 1; i <= nv; ++i) {
graph->g[i].first = NULL;
graph->g[i].layer = 0;
}
return graph;
}
void clear(gnode* graph) {
for (int i = 1; i <= graph->nv; ++i) {
graph->g[i].layer = 0;
}
}
int bfs(gnode* graph, int i) {
queue<int> q;
int now = i, sum = 0;
vis[now] = true;
q.push(now);
while (!q.empty()) {
now = q.front();
q.pop();
if (graph->g[now].layer <= 6) sum++;
//注意这里不能写=6
//如果有多个点距离都为6,就提前结束了
if (graph->g[now].layer > 6) break;
vnode* next;
for (next = graph->g[now].first; next; next = next->next) {
if (!vis[next->v]) {
//这句要先判断再执行
//否则碰到两个距离相同的点时,未经判断是否访问就加的话会多加
graph->g[next->v].layer = graph->g[now].layer + 1;
q.push(next->v);
vis[next->v] = true;
}
}
}
fill(vis, vis + maxn, 0);
clear(graph);
return sum;
}
int main() {
int nv, ne;
scanf("%d%d", &nv, &ne);
gnode* graph = create(nv, ne);
edge e;
for (int i = 0; i < ne; ++i) {
scanf("%d%d", &e.v1, &e.v2);
insert(graph, e);
}
int cnt;
for (int j = 1; j <= nv; ++j) {
cnt = bfs(graph, j);
printf("%d: %.2f%%\n", j, (1.0 * cnt / nv) * 100);
}
return 0;
}
方法二(比较麻烦的邻接表写法+巧妙计算层数)
这个方法是参考姥姥的MOOC视频~比较方便地就算出了层数。
具体的方案是用两个变量,last记录当前层最后一个端点值,tail跟踪记录下一层的最后一个端点值~
假设现在的端点是v,先把和v邻接的点统统push进队列,同时tail记录端点值,然后进行判断,如果此时的端点就是last的话,我们就可以将层数+1,然后更新last的值,也就是tail存储的下一层最后一个端点的值。
参考代码(方法二)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef double db;
typedef long long LL;
typedef vector<int> VI;
const int inf = 2e9;
const LL INF = 8e18;
const int maxn = 1e3 + 10;
bool vis[maxn];
struct vnode{
int v;
int layer;
vnode* next;
};
struct edge{
int v1, v2;
};
typedef struct hnode{
int layer;
vnode* first;
}vlist[maxn];
struct gnode{
int nv, ne;
vlist g;
};
void insert(gnode* graph, edge e) {
vnode* newNode = (vnode*)malloc(sizeof(struct vnode));
newNode->v = e.v2;
newNode->next = graph->g[e.v1].first;
graph->g[e.v1].first = newNode;
//无向图,反向还要来一次
newNode = (vnode*)malloc(sizeof(struct vnode));
newNode->v = e.v1;
newNode->next = graph->g[e.v2].first;
graph->g[e.v2].first = newNode;
}
gnode* create(int nv, int ne) {
gnode* graph = (gnode*)malloc(sizeof(struct gnode));
graph->nv = nv;
graph->ne = ne;
for (int i = 1; i <= nv; ++i) {
graph->g[i].first = NULL;
graph->g[i].layer = 0;
}
return graph;
}
void clear(gnode* graph) {
for (int i = 1; i <= graph->nv; ++i) {
graph->g[i].layer = 0;
}
}
int bfs(gnode* graph, int i) {
queue<int> q;
int now = i, cnt = 1, level = 0;
//last 当前层最后一个顶点
//tail 下一层最后一个顶点
int last, tail;
last = now;
vis[now] = true;
q.push(now);
vnode* tmp;
while (!q.empty()) {
now = q.front();
q.pop();
tmp = graph->g[now].first;
while (tmp) {
if (!vis[tmp->v]) {
cnt++;
vis[tmp->v] = true;
q.push(tmp->v);
tail = tmp->v;
}
tmp = tmp->next;
}
if (now == last) {
level++;
last = tail;
}
if (level == 6) break;
}
fill(vis, vis + maxn, 0);
clear(graph);
return cnt;
}
int main() {
int nv, ne;
scanf("%d%d", &nv, &ne);
gnode* graph = create(nv, ne);
edge e;
for (int i = 0; i < ne; ++i) {
scanf("%d%d", &e.v1, &e.v2);
insert(graph, e);
}
int cnt;
for (int j = 1; j <= nv; ++j) {
cnt = bfs(graph, j);
printf("%d: %.2f%%\n", j, (1.0 * cnt / nv) * 100);
}
return 0;
}
方法三(写法简单的邻接表+层数记录)
这里参考《算法笔记》的做法,用vector来存储邻接表,还是很简洁的~
这里有意思的地方在于,每个邻接表上的结点自己存储自己的层数,就不用担心会重复了,所以加层数的操作可以写在判断条件外面。
(如果碰到方法一所说的情况,即使多加了,也没关系,不会影响最后结果)
参考代码(方法三)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef double db;
typedef long long LL;
typedef vector<int> VI;
const int inf = 2e9;
const LL INF = 8e18;
const int maxn = 5e5 + 5;
struct Node{
int v; //顶点编号
int layer; //顶点层号
};
int N; //N为顶点个数
vector<vector<Node> > Adj;
bool vis[maxn];
void insert(Node a, Node b) {
Adj[a.v].pb(b);
Adj[b.v].pb(a);
}
int BFS(int s) {//s为起始顶点编号
queue<Node> q; //BFS队列
Node start; //起始顶点
start.v = s;
start.layer = 0;
q.push(start);
vis[start.v] = true;
int cnt = 0;
while (!q.empty()) {
Node now = q.front();
q.pop();
int v = now.v;
if (now.layer <= 6) cnt++;
else break;
for (int i = 0; i < Adj[v].size(); ++i) {
Node next = Adj[v][i];
next.layer = now.layer + 1;
if (!vis[next.v]) {
q.push(next);
vis[next.v] = true;
}
}
}
return cnt;
}
int main() {
int ne, v1, v2;
scanf("%d%d", &N, &ne);
Adj.resize(N + 1);
for (int i = 0; i < ne; ++i) {
scanf("%d%d", &v1, &v2);
insert(Node{v1, 0}, Node{v2, 0});
}
int cnt;
for (int i = 1; i <= N; ++i) {
cnt = BFS(i);
fill(vis, vis + N + 1, 0);
printf("%i: %.2f%%\n", i, (1.0 * cnt / N) * 100);
}
return 0;
}
表示快学不完了5555,最近看番有点上头。