“六度空间”理论又称作“六度分隔(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%
代码长度限制
16 KB
时间限制
2500 ms
内存限制
64 MB
解题思路:
根据题意,需要用到图和队列的结构,图的存储方式有邻接矩阵和邻接表两种,根据题意,
因为使用邻接矩阵所需要最大空间太大,所以邻接表更适合当前情况。
1.邻接表设置的简要解释:
邻接表由表头结点和邻接表组成,data存放出发结点的名称,*first存放了一条链,链内存放各个能从出发结点有连接的其他结点。最后图n,m中存放结点数和边数,Linjie存放整个邻接表。
//定义邻接表的节点
typedef struct ArcNode
{
//结点名
int name;
//下一个结点
struct ArcNode *next;
//该节点是否被选中过
int info;
}ArcNode;
//定义邻接表结构
typedef struct Grape
{
//顶点的信息
int data;
//顶点所能到达结点的集合
ArcNode *first;
}VNode, List[MaxSize];//所有顶点都包含
//图
typedef struct
{
List LinJie;
int n, m;
}ALGraph;
2.邻接表创建:
创建邻接表需要图G,结点数n和边数m。
首先,将n和m输入到图中,因为题中结点的名称编号从1开始,我们便也从1开始为邻接表的头结点依次赋值。一开始还没输入边的信息时,将头结点对应的链表指向NULL方便操作。
然后,输入可以连通的两个结点,通过两个结点的编号确定他们对应的头结点,将两个结点互相添加到各自链表中去(头插法即可)。
//创建邻接表
bool CreateUDG(ALGraph* G, int n, int m)
{
int i, j, k;
//先输入总顶点数和总边数
G->n = n;
G->m = m;
//scanf("%d %d", &G->n, &G->m);
//输入顶点值(从下表1开始存储的)
for(i = 1; i <= G->n; i++)
{
//每个顶点
G->LinJie[i].data = i;
//初始化头结点指针为NULL
G->LinJie[i].first = NULL;
}
//输入各边,构造邻接矩阵
for(k = 0; k < G->m; k++)
{
//每条边依附的两个点
int v1, v2;
scanf("%d %d", &v1, &v2);
//确定v1和v2所在顶点的位置
i = v1; j = v2;
//生成一个新的边节点
ArcNode* p1 = (ArcNode*)malloc(sizeof(ArcNode));
p1->name = j;
//把新节点插入头部
p1->next = G->LinJie[i].first;
G->LinJie[i].first = p1;
//生成一个新的边节点
ArcNode* p2 = (ArcNode*)malloc(sizeof(ArcNode));
p2->name = i;
//把新节点插入头部
p2->next = G->LinJie[j].first;
G->LinJie[j].first = p2;
}
return true;
}
3.广度优先遍历 :
广度优先遍历需要图G和出发点v。visited数组来判断是否被选择过。队列用来输出答案。
首先,用结点st存放出发点的数据,cur代表结点名,step代表距离出发点的阶数。初始化visited数组都为0,代表都未被选择过,先将出发点v的visited值改为true,代表选择过了。把st压入队列。
然后,为了输出全部路径,设置队长 == 0 为循环条件,但题目要求只需要连六个人,所以当出队的值的step == 6 时便跳出循环即可。p结点指针指向临界表链中的第一个元素,将邻接表中所有元素都入队
//广度优先遍历
int BFS(ALGraph* G, int v)
{
Node st,pt,curq;
st.cur = v;
st.step = 0;
//用c来记一共有多少六步能达到的结点
int c = 1;
//先让判断是否选中的数组都等于零
bool visited[MaxSize] = {0};
//临时遍历u用来放当前结点值,
int u;
//搞一个队列用来输出
Queue Q;
InitQueue(&Q);
//出发点先弄上
visited[v] = true;
EnQueue(&Q, st);
//一个临时存放邻接表结点
ArcNode* p;
//队列空就结束循环
while(LengthQueue(Q))
{
DeQueue(&Q, &curq);
//根据题意,到六跳出循环
if(curq.step == 6) break;
//指向邻接表的链表第一个元素
p = G->LinJie[curq.cur].first;
//当链表到尾部的NULL了结束循环
while(p)
{
pt.step = curq.step;
pt.cur = p->name;
if(!visited[p->name])
{
c++;
pt.step++;
EnQueue(&Q, pt);
visited[p->name] = true;
}
p = p->next;
}
}
return c;
}
4.所有代码:
#include <stdio.h>
#include <stdlib.h>
#define bool char
#define true 1
#define false 0
#define MaxSize 10000
/****************队列结构*******************/
typedef struct Node
{
int step;
int cur;
}Node;
typedef struct Queue
{
Node *data;
int front;
int rear;
}Queue;
//初始化
bool InitQueue(Queue* Q)
{
Q->front = Q->rear = 0;
Q->data = (Node*)malloc(sizeof(Node)*MaxSize);
return true;
}
//入队
bool EnQueue(Queue* Q, Node e)
{
if((Q->rear+1)%MaxSize == Q->front) return false;
Q->data[Q->rear] = e;
Q->rear = (Q->rear+1)%MaxSize;
return true;
}
//出队
bool DeQueue(Queue* Q, Node* e)
{
if(Q->rear == Q->front) return false;
*e = Q->data[Q->front];
Q->front = (Q->front+1)%MaxSize;
return true;
}
//打印队列元素
bool PrintQueue(Queue Q)
{
for(int i = 0; i < (Q.rear-Q.front+MaxSize)%MaxSize; i++)
{
if(i == 0)
printf("%d", Q.data[i].cur);
else
printf(" %d", Q.data[i].cur);
}
return true;
}
//队列长度
int LengthQueue(Queue Q)
{
return (Q.rear-Q.front+MaxSize)%MaxSize;
}
/****************邻接表结构*******************/
//定义邻接图的节点
typedef struct ArcNode
{
//结点名
int name;
//下一个结点
struct ArcNode *next;
//该节点是否被选中过
int info;
}ArcNode;
//定义邻接图结构
typedef struct Grape
{
//顶点的信息
int data;
//顶点所能到达结点的集合
ArcNode *first;
}VNode, List[MaxSize];//所有顶点都包含
//邻接表
typedef struct
{
List LinJie;
int n, m;
}ALGraph;
//创建邻接表
bool CreateUDG(ALGraph* G, int n, int m)
{
int i, j, k;
//先输入总顶点数和总边数
G->n = n;
G->m = m;
//scanf("%d %d", &G->n, &G->m);
//输入顶点值(从下表1开始存储的)
for(i = 1; i <= G->n; i++)
{
//每个顶点
G->LinJie[i].data = i;
//初始化头结点指针为NULL
G->LinJie[i].first = NULL;
}
//输入各边,构造邻接矩阵
for(k = 0; k < G->m; k++)
{
//每条边依附的两个点
int v1, v2;
scanf("%d %d", &v1, &v2);
//确定v1和v2所在顶点的位置
i = v1; j = v2;
//生成一个新的边节点
ArcNode* p1 = (ArcNode*)malloc(sizeof(ArcNode));
p1->name = j;
//把新节点插入头部
p1->next = G->LinJie[i].first;
G->LinJie[i].first = p1;
//生成一个新的边节点
ArcNode* p2 = (ArcNode*)malloc(sizeof(ArcNode));
p2->name = i;
//把新节点插入头部
p2->next = G->LinJie[j].first;
G->LinJie[j].first = p2;
}
return true;
}
//遍历图
bool Print(ALGraph G, int n)
{
int i = 0;
for(i = 1; i <= n; i++)
{
//输出顶点名
printf("%d:",G.LinJie[i].data);
ArcNode* p = G.LinJie[i].first;
while(p)
{
printf("%d->", p->name);
p = p->next;
}
printf("\n");
}
return true;
}
//广度优先遍历
int BFS(ALGraph* G, int v)
{
Node st,pt,curq;
st.cur = v;
st.step = 0;
int c = 1;
//先让判断是否选中的数组都等于零
bool visited[MaxSize] = {0};
//临时遍历u用来放当前结点值,
int u;
//搞一个队列用来输出
Queue Q;
InitQueue(&Q);
//出发点先弄上
visited[v] = true;
EnQueue(&Q, st);
//一个临时存放邻接表结点
ArcNode* p;
while(LengthQueue(Q))
{
DeQueue(&Q, &curq);
//printf("%d->",curq.cur);
if(curq.step == 6) break;
p = G->LinJie[curq.cur].first;
while(p)
{
pt.step = curq.step;
pt.cur = p->name;
if(!visited[p->name])
{
c++;
pt.step++;
EnQueue(&Q, pt);
visited[p->name] = true;
}
p = p->next;
}
}
return c;
}
int main()
{
ALGraph G;
int n, m;
scanf("%d %d",&n,&m);
CreateUDG(&G, n, m);
//Print(G, 10); //测试用遍历
//printf("\n");
for(int i = 1; i <= n; i++)
{
float s;
s = BFS(&G, i);
printf("%d: %.2f%%\n", i, s/n*100);
}
return 0;
}