目的
我们将使用1.3节中获得的brightkite_nodes_available.txt文件和brightkite_edges_available.txt文件来构建空间网络图结构。brightkite_nodes_available.txt文件保存的是点的信息,每行表示一个点;brightkite_edges_available.txt文件保存的是边的信息,每行表示一条边。
构建空间网络是为了将文本数据映射成程序可处理的图结构,使用图结构来存储空间图数据能提高处理效率。
存储结构
图的存储结构一般可分为三种
第一种是 用点集数组和边集数组存储一个图
第二种是 用邻接矩阵存储一个图
第三种是 用邻接链表存储一个图
我们采用第三种存储方式,原因是邻接链表结构删除操作效率高,在之后的社区挖掘算法中存在大量删除操作。
下面给出了一个图以及它对应的邻接链表存储结构。
图:
邻接链表存储结构:
构建过程
数据结构
在邻接链表表示图的存储结构中,众多节点构成了一个链表,众多链表构成了一个图。所以要先从节点类的设计开始。
点类
代码:
/**
* 空间网络节点类
* 每个节点由三个属性:id 、 位置 、度
* @author dxt
*
*/
public class Node {
protected Node next; //指向下一个顶点
protected int id; //唯一确定空间中的一个节点
protected double x; //位置 x--纬度
protected double y; //位置y--经度
protected int degree; //度:邻接点的个数
protected int index; //用于搜索的索引
protected int curDegree; //当前的度
//构造方法
public Node(){}
public Node(int id){
super();
this.id = id;
}
public Node(int id, double x, double y){
super();
this.id = id;
this.x = x;
this.y = y;
}
public Node(int id, double x, double y, int degree){
super();
this.id = id;
this.x = x;
this.y = y;
this.degree = degree;
}
public Node(Node next, int id, double x, double y, int degree){
super();
this.next = next;
this.id = id;
this.x = x;
this.y = y;
this.degree = degree;
}
public Node(int id, double x, double y, int degree, int index){
super();
this.id = id;
this.x = x;
this.y = y;
this.degree = degree;
this.index = index;
}
//javabean
public void setId(int id){
this.id = id;
}
public int getId(){
return this.id;
}
public void setX(double x){
this.x = x;
}
public double getX(){
return this.x;
}
public void setY(double y){
this.y = y;
}
public double getY(){
return this.y;
}
public void setDegree(int degree){
this.degree = degree;
}
public int getDegree(){
return this.degree;
}
public void setNext(Node next){
this.next = next;
}
public Node getNext(){
return this.next;
}
public void setIndex(int index){
this.index = index;
}
public int getIndex(){
return this.index;
}
public void setCurDegree(int d){
this.curDegree = d;
}
public int getCurDegree(){
return this.curDegree;
}
public void incCurDegree(){
this.curDegree++;
}
/**
* 判断两个节点是否相同,
* 如果两个节点的id相同,则返回true; 否则返回false
* @param n
* @return
*/
public boolean equals(Node n){
if(this.id == n.getId()){
return true;
}else{
return false;
}
}
/**
* 以欧式距离公式计算以获得相似性
* 没有多少参考价值,因为位置坐标是以经纬度标识的,以经纬度计算欧氏距离,所得的结果可用来比较大小,但没有实际意义
* @param n
* @return
*/
public double getSimilarity(Node n){
double len = 0;
double d_x = Math.abs((this.x - n.x));
double d_y = Math.abs((this.y - n.y));
len = Math.sqrt((d_x*d_x + d_y*d_y));
len = Math.round(len * 1000) / 1000.0;//保留三位小数,精确到米
return len;
}
/**
* 计算两点间的距离
* 因为数据是基于位置的
* 可以依据经纬度计算出两点间的距离
* @return
*/
public double computeSim(Node n){
double earth_radius = 6378.137; //将地球看作圆,地球的平均半径,单位km
double lat1 = this.x; //获取位置1的纬度,北纬为正,南纬为负
double lon1 = this.y; //获取位置1的经度,东经为正,西经为负
double lat2 = n.x; //获取位置2的纬度
double lon2 = n.y; //获取位置2的经度
double radLon1 = lon1 * (float)(Math.PI /180.0); //转换为弧度
double radLon2 = lon2 * (float)(Math.PI /180.0);
double a = radLon1 - radLon2;
double radLat1 = lat1 * (float)(Math.PI /180.0); //转换为弧度
double radLat2 = lat2 * (float)(Math.PI /180.0);
double b = radLat1 - radLat2;
double dis = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)+
Math.cos(radLon1) * Math.cos(radLon2)* Math.pow(Math.sin(b / 2), 2)));
dis = dis * earth_radius;
dis = Math.round(dis * 1000) / 1000.0;//保留三位小数,精确到米
return dis;
}
}
解释:
一个点具有众多属性。如果两个点的id相同,则认为它们是同一个点。给出了两种计算两点距离的方法,一种是欧式距离,一种是实际距离,两者的优缺点自己判断。
当初写代码时人傻了,没有封装好,获取属性的方式也不统一。
链表类
代码:
/**
* 一条链表
* @author dxt
*
*/
public class NodeLinkedList {
private Node head;
private int size;
//构造方法
public NodeLinkedList(){
super();
this.head = null;
this.size = 0;
}
public NodeLinkedList(Node n){
super();
this.head = n;
this.size = 1;
}
//获取链表元素个数
public int getSize(){
return this.size;
}
//获取链表头
public Node getHead(){
return this.head;
}
//获取头结点
public Node getHeadNode(){
int id = this.head.getId();
double x = this.head.getX();
double y = this.head.getY();
int degree = this.head.getDegree();
int index = this.head.getIndex();
Node temp_node = new Node(id, x, y, degree, index);
temp_node.setNext(null); //指向空
return temp_node;
}
//判断链表是否为空
public boolean isEmpty(){
if(this.size == 0){
return true;
}else{
return false;
}
}
/**
* 向链表头部添加节点n
* @param n
*/
public void addFirst(Node n){
n.setNext(head); //将n的下一个节点设为现有的头结点
this.head = n; //将n设为新的头节点
this.size++; //修改链表长度
}
/**
* 向链表中index位置添加元素
* @param n
* @param index
*/
public void add(Node n, int index){
if(index < 0 || index > size){
throw new IllegalArgumentException("Index is error");
}
//1.如果是向链表头部添加节点
if(index == 0){
this.addFirst(n);
}else{
//2.向非头部添加节点
Node preNode = this.head;
//2.1找到要插入节点的前一个节点
for(int i=0; i<index-1; i++){
preNode = preNode.next;
}
//2.2 进行插入操作
n.setNext(preNode.next); //要插入的节点n的下一个节点指向preNode节点的下一个节点
preNode.next = n; //preNode的下一个节点指向要插入节点node
this.size++; //修改size
}
}
/**
* 向链表尾插入元素
* @param n
*/
public void addLast(Node n){
this.add(n, this.size);
}
/**
* 删除链表中与节点n相同的节点
* @param n
*/
public void remove(Node n){
if(head == null){
System.out.println("链表为空,无元素可以删除。");
return;
}
//1.删除的节点为头结点
while(head.equals(n)){
head = head.next;
this.size--;
}
//2.删除的节点不是头结点
Node cur = this.head;
while(cur.next != null){
if(cur.next.equals(n)){
cur.next = cur.next.next;
this.size--;
}else{
cur = cur.next;
}
}
}
/**
* 判断节点n是否在此链表中,在则返回true,否则返回false
* @param n
* @return
*/
public boolean contains(Node n){
Node cur = this.head;
while(cur != null){
if(cur.equals(n)){
return true;
}else{
cur = cur.next;
}
}
return false;
}
/**
* 删除NodeLinkedList中不相似的点
* r为相似性阈值,如果大于r,则两点不相似,如果小于r则两点相似
*/
public void deleteDissim(double r){
Node cur = this.head;
while(cur.getNext() != null){
double distance = this.head.computeSim(cur.getNext());
if(distance > r){
cur.next = cur.next.next;
this.size--;
}else{
cur = cur.next;
}
}
//删除后,更改头结点的度
this.head.setDegree(this.size-1);
}
/**
* 用于测试
*/
public String toString(){
StringBuffer sb = new StringBuffer();
Node cur = this.head;
while(cur != null){
sb.append(cur.getId()+" -> ");
cur = cur.getNext();
}
sb.append("null");
return sb.toString();
}
public static void main(String[] args){
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
NodeLinkedList nll = new NodeLinkedList();
System.out.println(nll.getSize());
nll.addFirst(n1);
nll.addLast(n2);
nll.add(n3, 2);
nll.addLast(n4);
nll.add(n5, 4);
System.out.println(nll);
Node n6 = new Node(5);
System.out.println(nll.contains(n6));
nll.addLast(n6);
nll.remove(n6);
System.out.println(nll);
nll.remove(n1);
System.out.println(nll);
System.out.println(nll.getSize());
}
}
解释:
main方法是为了测试创建的类是否正确。
图类
代码:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 图类
* 以ArrayList来保存图中的节点
* 节点后的链接节点都是其在图中直接连接的节点
* @author dxt
*
*/
public class Graph {
private List<NodeLinkedList> nodeList;
private int size; //图中的节点个数
//构造方法
public Graph(){
super();
this.nodeList = new ArrayList<NodeLinkedList>();
this.size = 0;
}
//获取nodeList的长度
public int getSize(){
size = this.nodeList.size();
return this.size;
}
//获取图的信息
public List<NodeLinkedList> getNodeList(){
return this.nodeList;
}
//构造图
public void setNodeList(NodeLinkedList nll){
this.nodeList.add(nll);
this.size++;
}
/**
* 依据id获取对应id节点,此节点指向空
* @param id
* @return
*/
public Node getNLLNode(int id){
for(int i=0; i<this.nodeList.size(); i++){
if(id == this.nodeList.get(i).getHead().getId()){
return this.nodeList.get(i).getHeadNode();
}
}
return null;
}
}
解释:
只展示图类的基本结构,没有添加功能方法,即上面展示的代码并不全。在以后的介绍中会向此图类中添加方法,以实现社区挖掘算法。
构造空间图
代码:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 图类
* 以ArrayList来保存图中的节点
* 节点后的链接节点都是其在图中直接连接的节点
* @author dxt
*
*/
public class Graph {
private List<NodeLinkedList> nodeList;
private int size; //图中的节点个数
//构造方法
public Graph(){
super();
this.nodeList = new ArrayList<NodeLinkedList>();
this.size = 0;
}
//获取nodeList的长度
public int getSize(){
size = this.nodeList.size();
return this.size;
}
//获取图的信息
public List<NodeLinkedList> getNodeList(){
return this.nodeList;
}
//构造图
public void setNodeList(NodeLinkedList nll){
this.nodeList.add(nll);
this.size++;
}
/**
* 依据id获取对应id节点,此节点指向空
* @param id
* @return
*/
public Node getNLLNode(int id){
for(int i=0; i<this.nodeList.size(); i++){
if(id == this.nodeList.get(i).getHead().getId()){
return this.nodeList.get(i).getHeadNode();
}
}
return null;
}
public void initial(File nodeSrc, File edgeSrc){
//1.首先读取节点文件,初始化各个节点,作为NodeLinkedList的头结点
int index = 0; //记录共有多少个节点
BufferedReader br_node = null;
try {
br_node = new BufferedReader(new FileReader(nodeSrc));
//1.1 读取数据
String line = null;
int id;
double x;
double y;
int degree;
while((line = br_node.readLine()) != null){
//1.1.1 获取每一行数据的内容,并分割
String[] datas = line.split("\t");
id = Integer.valueOf(datas[0]);
x = Double.valueOf(datas[1]);
y = Double.valueOf(datas[2]);
degree = Integer.valueOf(datas[3]);
//1.2初始构造nodeList,nodeList中的每个NodeLinkedList只含有头结点
//1.2.1 首先构造Node
Node temp_node = new Node(id, x, y, degree, index);
index++;
//1.2.2构造NodeLinkedList
NodeLinkedList temp_nll = new NodeLinkedList(temp_node);
//1.2.3初始构造nodeList
this.nodeList.add(temp_nll);
}
br_node.close();
this.size = index; //初始化size
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
//2. 读取边文件,构造nodeList中的每一个NodeLinkedList
BufferedReader br_edge = null;
try {
br_edge = new BufferedReader(new FileReader(edgeSrc));
//2.1 读取边的信息
String line = null;
int id_one, id_two;
Node n_two = null;
int id_one_pre = -1; //保存上一行数据中的id_one,初始设为-1, 提高构造效率效率
int add_index = -1; //维持一个索引,充分利用ArrayList的特点来提高效率
while((line=br_edge.readLine()) != null){
//2.1.1获取每一行的数据,并分割
String[] datas = line.split("\t");
id_one = Integer.valueOf(datas[0]);
id_two = Integer.valueOf(datas[1]);
//2.2 构造每一个NodeLinkedList
//2.2.1 首先依据id_two找到对应的Node
n_two = this.getNLLNode(id_two);
//2.2.2 然后找到对应的NodeLinkedList,将n_two添加到NodeLinkedList中
if(n_two != null){
if(id_one != id_one_pre){
for(int i=0; i<this.nodeList.size(); i++){
if(id_one == this.nodeList.get(i).getHead().getId()){
this.nodeList.get(i).addLast(n_two); //将n_two添加到NodeLinkedList中
add_index = i;
break;
}
}
}else{
this.nodeList.get(add_index).addLast(n_two);
}
}
//2.2.3 更新id_one_pre
id_one_pre = id_one;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}
//用于测试是否已构建图,图是否正确
public static void main(String[] args){
File nodeSrc = new File("src//source//brightkite_nodes_available.txt");
File edgeSrc = new File("src//source//brightkite_edges_available.txt");
Graph g = new Graph();
g.initial(nodeSrc, edgeSrc);
System.out.println(g.getSize());
}
}
解释:
其中initial(File f1, File f2)方法就是根据点文件f1和边文件f2两个文件构造一个图。
具体的思想是:先依据点文件构造出每个链表的头结点,然后依据边文件向头结点所在的链表插入节点。