题目描述
“六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”如图1所示。
图1 六度空间示意图
“六度空间”理论虽然得到广泛的认同,并且正在得到越来越多的应用。但是数十年来,试图验证这个理论始终是许多社会学家努力追求的目标。然而由于历史的原因,这样的研究具有太大的局限性和困难。随着当代人的联络主要依赖于电话、短信、微信以及因特网上即时通信等工具,能够体现社交网络关系的一手数据已经逐渐使得“六度空间”理论的验证成为可能。
假如给你一个社交网络图,请你对每个节点计算符合“六度空间”理论的结点占结点总数的百分比。
输入格式
输入第1行给出两个正整数,分别表示社交网络图的结点数N(1<N≤103,表示人数)、边数M(≤33×N,表示社交关系数)。随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个结点的编号(节点从1到N编号)。
输出格式
对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比,精确到小数点后2位。每个结节点输出一行,格式为“结点编号:(空格)百分比%”。
输入样例
10 9
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
输出样例
1: 70.00%
2: 80.00%
3: 90.00%
4: 100.00%
5: 100.00%
6: 100.00%
7: 100.00%
8: 90.00%
9: 80.00%
10: 70.00%
题解
题意理解:应用 BFS 搜索到第六层为止,输出搜索到的人(包括自己)占整个网络总人数的比例。
BFS 模板已经不需要多说,需要滚瓜烂熟。此题的关键在于如何修改 BFS 使其只遍历到第六层。核心算法如下:
- 首先进入 BFS ,把起点入队,标记访问过
- 初始化 count = 1 ,level = 0 ,last = v 进入的时候当前层的最后一个元素就是 v
- 两重循环,一重不断出队,一重遍历该出队元素的邻接点。
- 内重循环中的 tail 负责保存遍历过程的最后一个结点,循环结束 tail 即为该层最后一个结点。
- 退出内重循环后判断刚刚遍历完邻接点的出队结点是不是 last ,如果是就说明一层遍历完成,更新 last 为该组更新出来的 tail ,并且 level++
- 退出循环后当 level == 6 时,已经遍历完六层,退出循环,输出结果
- 这里还需要明确的是 level 的含义:结束对一个出对元素邻接点遍历后目前遍历完的层数,如果不更新说明一直在 level 层,一旦要更新,更新前的 level 表示目前出队结点所在的层数,但由于其邻接点是下一层结点,所以退出条件就是 level == 6 ,它表示接下来出队的结点会是第六层的结点
第一次尝试(WA):最大 N 、M 用例超时
邻接表存图,时间换空间,体现在 复杂度内知道两点是否连通。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct GNode *PtrToGNode;
struct GNode {
int Data;
PtrToGNode Next;
};
void BuildGraph(struct GNode G[], int *N, int *M)
{
int i, a, b;
scanf("%d%d", &(*N), &(*M));
for (i = 1; i <= *N; i++) {
G[i].Data = i;
G[i].Next = NULL;
}
for (i = 0; i < *M; i++) {
scanf("%d%d", &a, &b);
PtrToGNode NewNode;
NewNode = (PtrToGNode)malloc(sizeof(struct GNode));
NewNode->Data = b;
NewNode->Next = G[a].Next;
G[a].Next = NewNode;
NewNode = (PtrToGNode)malloc(sizeof(struct GNode));
NewNode->Data = a;
NewNode->Next = G[b].Next;
G[b].Next = NewNode;
}
}
bool IsConnected(struct GNode G[], int N, int v, int w)
{
int i;
PtrToGNode p;
for (p = G[v].Next; p; p = p->Next) {
if (w == p->Data) break;
}
if (p) return true;
else return false;
}
int front, rear, queue[1000];
void Solve(struct GNode G[], int N, int i)
{
int count, level, last, tail, v, w, visited[1001] = {0};
front = rear = 0;
v = i;
queue[rear++] = v;
visited[v] = 1;
count = 1;
level = 0;
last = v;
while (front != rear) {
v = queue[front++];
for (w = 1; w <= N; w++) {
if (!visited[w] && IsConnected(G, N, v, w)) {
queue[rear++] = w;
visited[w] = 1;
count++;
tail = w;
}
}
if (v == last) {
last = tail;
level++;
}
if (level == 6) break;
}
printf("%d: %.2f%%\n", i, (double)count / N * 100);
}
int main()
{
int N, M;
struct GNode G[1001];
BuildGraph(G, &N, &M);
for (int i = 1; i <= N; i++)
Solve(G, N, i);
return 0;
}
第二次尝试(AC):
邻接矩阵存图,空间换时间,体现在 复杂度内知道两点是否联通。
注意:栈区可分配内存较少,直接在 main 里声明 1000*1000 的数组会爆栈
#include <stdio.h>
#include <stdlib.h>
void BuildGraph(int G[][1001], int *N, int *M)
{
int i, a, b;
scanf("%d%d", &(*N), &(*M));
for (i = 0; i < *M; i++) {
scanf("%d%d", &a, &b);
G[a][b] = G[b][a] = 1;
}
}
int front, rear, queue[1000];
void Solve(int G[][1001], int N, int i)
{
int count, level, last, tail, v, w, visited[1001] = {0};
front = rear = 0;
v = i;
queue[rear++] = v;
visited[v] = 1;
count = 1;
level = 0;
last = v;
while (front != rear) {
v = queue[front++];
for (w = 1; w <= N; w++) {
if (!visited[w] && G[v][w]) {
queue[rear++] = w;
visited[w] = 1;
count++;
tail = w;
}
}
if (v == last) {
last = tail;
level++;
}
if (level == 6) break;
}
printf("%d: %.2f%%\n", i, (double)count / N * 100);
}
int G[1001][1001] = {0};
int main()
{
int N, M;
BuildGraph(G, &N, &M);
for (int i = 1; i <= N; i++)
Solve(G, N, i);
return 0;
}