数据结构-2020夏 06-图3 六度空间 (30分)

原题地址

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,最近看番有点上头。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值