06-图3 六度空间

219 阅读2分钟

题目描述

“六度空间”理论又称作“六度分隔(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 用例超时

邻接表存图,时间换空间,体现在 O(N)O(N) 复杂度内知道两点是否连通。

#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):

邻接矩阵存图,空间换时间,体现在 O(1)O(1) 复杂度内知道两点是否联通。

注意:栈区可分配内存较少,直接在 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;
}