1.4 构建空间网络图

目的

      我们将使用1.3节中获得的brightkite_nodes_available.txt文件和brightkite_edges_available.txt文件来构建空间网络图结构。brightkite_nodes_available.txt文件保存的是点的信息,每行表示一个点;brightkite_edges_available.txt文件保存的是边的信息,每行表示一条边。
      构建空间网络是为了将文本数据映射成程序可处理的图结构,使用图结构来存储空间图数据能提高处理效率。

存储结构

      图的存储结构一般可分为三种
      第一种是 用点集数组和边集数组存储一个图
      第二种是 用邻接矩阵存储一个图
      第三种是 用邻接链表存储一个图
      我们采用第三种存储方式,原因是邻接链表结构删除操作效率高,在之后的社区挖掘算法中存在大量删除操作。
      下面给出了一个图以及它对应的邻接链表存储结构。
图:
graph
邻接链表存储结构:
在这里插入图片描述

构建过程

数据结构

      在邻接链表表示图的存储结构中,众多节点构成了一个链表,众多链表构成了一个图。所以要先从节点类的设计开始。
点类
代码:

/**
 * 空间网络节点类
 * 每个节点由三个属性: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两个文件构造一个图。
      具体的思想是:先依据点文件构造出每个链表的头结点,然后依据边文件向头结点所在的链表插入节点。

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页