标签:
小世界现象(又称小世界效应),也称六度分隔理论(英文:Six Degrees of Separation)。
假设世界上所有互不相识的人只需要很少中间人就能建立起联系。后来1967年哈佛大学的心理学教授斯坦利·米尔格拉姆根据这概念做过一次连锁信实验,尝试证明平均只需要5个中间人就可以联系任何两个互不相识的美国人。
NowCoder最近获得了社交网站Footbook的好友关系资料,请你帮忙分析一下某两个用户之间至
少需要几个中间人才能建立联系?
输入第一行是一个整数t,表示紧接着有t组数据。
每组数据包含两部分:第一部分是好友关系资料;第二部分是待分析的用户数据。
好友资料部分第一行包含一个整数n (5≤n≤50),表示有n个用户,用户id用1->n表示。
紧接着是一个只包含0和1的n×n矩阵,其中第y行第x列的值表示id是y的用户是否是id为x的用户的好友(1代表是,0代表不是)。假设好友关系是相互的,即A是B的好友意味着B也是A的好友。
待分析的用户数据第一行包含一个整数m,紧接着有m行用户组数据。
每组有两个用户ID,A和B (1≤A, B≤n; A != B)。
对于每组待分析的用户,输出用户A至少需要通过几个中间人才能认识用户B。
如果A无论如何也无法认识B,输出“Sorry”。
2
5
1 0 1 0 1
0 1 1 1 0
1 1 1 0 0
0 1 0 1 0
1 0 0 0 1
3
1 2
2 4
3 5
6
1 1 0 0 1 0
1 1 0 1 0 1
0 0 1 0 0 1
0 1 0 1 0 1
1 0 0 0 1 0
0 1 1 1 0 1
4
2 3
3 6
5 1
4 2
1
0
1
1
0
0
0
题目要求某两个人之间最少通过多少个中间人才能建立联系,人与人之间的关系用一个图进行表示,有直接关系的使用1表示,没有关系的使用0表示。可以对这个关系矩阵进行改进,将自身与身的关系计为1,<v,w>存在直接关系记为1,不存在直接关系的记为+∞。要求,<x,y>最少通过多少个中间人可以取得联系,可以先计算,<x,y>之间的最短路径,因为边的权权重都是1,所以最短路径就是,<x,y>所经过的最少的边的数目e,而,<x,y>最少的联系人数目就是,<x,y>最少边所在线段中间的顶点数,即e-1。
经过分析可以得,该题可以通过Dijkstra、Bellman-Ford或者Floyd算法进行处理。本题分析过程讲解Floyd。Dijkstra方法见【016-回家过年】算法实现。
问题的提出:已知一个有向网(或无向网),对每一对顶点
解决该问题的方法有:
1) 轮流以每个顶点为源点,重复执行Dijkstra算法(或Bellman-Ford算法)n次,就可求出每一对顶点之间的最短路径和最短路径长度,总的时间复杂度是
2) 采用Floyd(弗洛伊德)算法。Floyd 算法的时间复杂度也是
Floyd(弗洛伊德)算法的基本思想是:对一个顶点个数为n的有向网(或无向网),设置一个n×n的方阵A(k),其中除对角线的矩阵元素都等于0外,其他元素A(k)[i][j](i≠j)表示从顶点
初始时:A(-1)= Edge(图的邻接矩阵),即初始时,以任意两个顶点之间的直接有向边的权值作为最短路径长度:
1) 对于任意两个顶点
2) 若它们之间不存在有向边,则以MAX作为它们之间的最短路径。
以后逐步尝试在原路径中加入其他顶点作为中间顶点,如果增加中间顶点后,得到的路径比原来的最短路径长度减少了,则以此新路径代替原路径,修改矩阵元素,更新为新的更短的路径长度。
例如,在图1所示的有向网中,初始时,从顶点
图1 Floyd算法:有向网及其邻接矩阵
将
在下一步中又增加顶点
如图1所示,A[2][3]在引入中间顶点
Floyd算法的描述如下。
定义一个n阶方阵序列:
……
……
采用递推方式计算
增加顶点vk作为中间顶点后,对于图中的每一对顶点
因此,Floyd 算法的递推公式为:
Floyd 算法在实现时,需要使用两个数组:
1) 数组A:使用同一个数组A[i][j]来存放一系列的
2) path数组:path[i][j]是从顶点
Floyd算法具体实现代码详见例2.1。
例2.1 利用Floyd算法求图1(a)中各顶点间的最短路径长度,并输出对应的最短路径。
假设数据输入时采用如下的格式进行输入:首先输入顶点个数n,然后输入每条边的数据。每条边的数据格式为:u v w,分别表示这条边的起点、终点和边上的权值。顶点序号从0 开始计起。最后一行为-1 -1 -1,表示输入数据的结束。
分析:
如图2所示,初始时,数组A实际上就是邻接矩阵。path数组的初始值:如果顶点
以从
当Floyd算法运算完毕,如何根据path 数组确定顶点
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
/**
* Author: 王俊超
* Time: 2016-05-12 11:52
* CSDN: http://blog.csdn.net/derrantcm
* Github: https://github.com/Wang-Jun-Chao
* Declaration: All Rights Reserved !!!
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data3.txt"));
int group = scanner.nextInt();
for (int i = 0; i < group; i++) {
// 用户个数
int n = scanner.nextInt();
int[][] edge = new int[n][n];
for (int j = 0; j < n; j++) {
edge[j] = new int[n];
for (int k = 0; k < n; k++) {
edge[j][k] = scanner.nextInt();
}
}
// 用户组
int m = scanner.nextInt();
List<Integer> pairs = new ArrayList<>(m * 2);
m *= 2;
for (int j = 0; j < m; j++) {
// 因为数组下标从0开始,而人的编号从1开始,将人的编号全部减1
pairs.add(scanner.nextInt() - 1);
}
// 对输入的关系矩阵进行处理(v, v)设置为1,(v, w)不直接可达的设置为Integer.MAX_VALUE
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (j == k) {
edge[j][k] = 0;
} else if (edge[j][k] == 0) {
edge[j][k] = Integer.MAX_VALUE;
}
}
}
List<Integer> result = floyd(edge, pairs);
// List<Integer> result = dijkstra(edge, pairs);
// 输入结果,因求出的是(v,w)之前的边的数目,它们之前的顶点数就是最少的联系人数目
// 最少的联系人数目=(v, w)最少的边数-1
for (Integer r : result) {
if (r < Integer.MAX_VALUE) {
System.out.println(r - 1);
} else {
System.out.println("Sorry");
}
}
}
scanner.close();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// 解法一:Floyd方法求任意两点间的距离
/////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 使用Floyd算法求图任意两点之间的最短距离
*
* @param edge 图的邻接矩阵
* @param pairs 所要求的(v, w)点的集合
* @return (v, w)的最短路路径
*/
private static List<Integer> floyd(int[][] edge, List<Integer> pairs) {
int MAX = Integer.MAX_VALUE;
// 顶点数
int N = edge.length;
// 记录任意两点的最短路径
int[][] A = new int[N][N];
// 记录最短路径的走法,在本题中可以不使用
int[][] path = new int[N][N];
// 初始化A和path
for (int i = 0; i < N; i++) {
A[i] = new int[N];
path[i] = new int[N];
for (int j = 0; j < N; j++) {
A[i][j] = edge[i][j];
// (i, j)有路径
if (i != j && A[i][j] < MAX) {
path[i][j] = i;
}
// 从i到j没有路径
else {
path[i][j] = -1;
}
}
}
// /从A(-1)递推到A(0), A(1), ..., A(n-1),或者理解成依次将v0,v1,...,v(n-1)作为中间顶点
for (int k = 0; k < N; k++) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (k == i || k == j) {
continue;
}
if (A[i][k] < MAX && A[k][j] < MAX && A[i][k] + A[k][j] < A[i][j]) {
A[i][j] = A[i][k] + A[k][j];
// path[i][j]是从顶点vi到顶点vj的最短路径上顶点j的前一顶点的序号
// 现在path[i][j]中j的前一个顶点就是path[k][j]中j的前一个顶点
path[i][j] = path[k][j];
}
}
}
}
List<Integer> result = new LinkedList<>();
while (!pairs.isEmpty()) {
int x = pairs.remove(0);
int y = pairs.remove(0);
result.add(A[x][y]);
}
return result;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// 解法二:Dijkstra方法求任意两点间的距离
/////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 使用Dijkstra算法求图任意两点之间的最短距离
*
* @param edge 图的邻接矩阵
* @param pairs 所要求的(v, w)点的集合
* @return (v, w)的最短路路径
*/
private static List<Integer> dijkstra(int[][] edge, List<Integer> pairs) {
int N = edge.length;
int MAX = Integer.MAX_VALUE;
// 标记顶点是否已经访问过
boolean[] S = new boolean[N];
// 记录起点到各点的最短距离
int[][] DIST = new int[N][N];
// 记录前驱顶点,通过找前驱可以找到从(v, w)的最短路径的走法,在本题中可以不使用
int[][] PREV = new int[N][N];
List<Integer> result = new ArrayList<>();
// 处理每一个(v, w)
for (int v = 0; v < N; v++) {
DIST[v] = new int[N];
PREV[v] = new int[N];
// 处理第一个点
for (int i = 0; i < N; i++) {
S[i] = false;
DIST[v][i] = edge[v][i];
// 如果是最大值,说明(0, i)不存在。所以PREV[i]不存在
if (DIST[v][i] == MAX) {
PREV[v][i] = -1;
} else {
PREV[v][i] = 0;
}
}
// 标记v号顶点已经处理过
S[v] = true;
// 处理其余的点
for (int i = 1; i < N; i++) {
int min = MAX;
int u = 0;
// 找未访问过的顶点j,并且DIST[j]的值最小
for (int j = 0; j < N; j++) {
if (!S[j] && DIST[v][j] < min) {
u = j;
min = DIST[v][j];
}
}
// 标记u已经被访问过了
S[u] = true;
for (int j = 0; j < N; j++) {
// j没有被访问过,并且(u, j)可达
if (!S[j] && edge[u][j] < MAX) {
int weight = DIST[v][u] + edge[u][j];
// 从0->...->u->j比0->...->j(其它路径)短
if (DIST[v][u] < MAX && edge[u][j] < MAX && weight < DIST[v][j]) {
DIST[v][j] = weight;
// j是通过u访问到的
PREV[v][j] = u;
}
}
}
}
}
for (int i = 0; i < pairs.size(); i += 2) {
int v = pairs.get(i);
int w = pairs.get(i + 1);
result.add(DIST[v][w]);
}
return result;
}
private static void print(int[][] arr) {
for (int[] line : arr) {
print(line);
}
}
private static void print(int[] arr) {
for (int val : arr) {
if (val != Integer.MAX_VALUE) {
System.out.print(val + " ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
}
因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】。
标签:
原文地址:http://blog.csdn.net/derrantcm/article/details/51813814