基于十字链表储存大型路网Java实现


在城市路网中,往往存在几万个节点,数十万条边,由路网构成的网络比较稀疏。如何存储相关数据,设计路径规划算法至关重要。本文简单介绍了基于十字链表的存储方法,并且基于Java语言实现了相关功能。

1 十字链表

1.1 基本概念

十字链表是一种综合了邻接表和逆邻接表的一种链式储存结构。邻接表是图的标准表示方法,先简单介绍邻接表,再介绍十字链表。

1.1.1 邻接表与逆邻接表

邻接表主要是为了节省稀疏图的储存空间,采用链表的方式进行图的存储。邻接表主要是反映顶点出度的情况,逆邻接表主要是反映入度情况。举个例子。
网络有向图示意图
其中,其邻接表表示为(节点结构为:顶点编号、权值、下一个节点位置):
邻接表示意图
其中,其逆邻接表表示为:
逆邻接表示意图
由上面两张图片可以看出,领接表与逆邻接表主要是出度与入度的差别。

1.1.2 十字链表

为什么已经有邻接表了还需要介绍十字链表呢?原因就是对于出度和入度,邻接表不能兼得。在大型路网中,计算从A点出发,前往最近的K个点用邻接表很容易计算,但是基于邻接表计算从其他节点出发,到达A点最近的K个节点就不好计算了。原因就是出度和入度不能兼得。为了解决在有向图中的相关问题,在大型路网中可以基于十字链表进行图算法设计。

以下部分是对相关blog的总结,侵删(传送门1传送门2)。图片主要源于书籍(大话数据结构)。

顶点结构
在这里插入图片描述

  • firstin、firstout 分别指向入边表、出边表中的第一个结点

边结构
在这里插入图片描述

  • tailvex、headvex分别指弧起点(即弧尾)、弧终点(即弧头)在顶点表中的下标。
  • headlink是指入边表指针域,指向终点相同的下一条边(即弧头相同,所以命名为headlink)
  • taillink是指出边表指针域,指向起点相同的下一条边(即弧尾相同,所以命名为taillink)

网络表示
十字链表标示图
看完上面的介绍,初学者的我表示:…,都是些啥哟?

后来稍微有点理解了之后我会这样子介绍十字链表。
十字链表作者自认为的表示方法
当然,我画的图并不严谨,只是我是这么理解的。在表示网络时,我们先设置一个点集,每个Node对象中存在两个属性,其中adjacentLinks存储该点的后继边,也就是领接表中的出度;predecessorLinks存储该点的前继边,也就是逆邻接表的入度。这是不是很容易理解了?一个节点无非就只有这两种属性,从这个节点出来的边和进来的边。所以说,十字链表关注的重点是顶点(一家之言,欢迎指正)。

1.2 基本操作

作为一个网络结构。一般需要有的功能是:

  • 增加节点
  • 增加一条边
  • 删除节点
  • 删除一条边
  • 属性统计(网络节点数、边数、节点出度、入度、节点是否包含邻接边)

具体操作看代码吧

2 十字链表java实现

2.1 结点定义

package Graph;

import java.util.HashMap;
import SSP.*;

public class NetNode {
	private int m_nodeID;
	private HashMap<Integer, NetLink> m_pAdjacentLinkset;
	private HashMap<Integer, NetLink> m_pPredecessorLinks;
	private Label label;
	
	public NetNode(int iNodeID, HashMap<Integer, NetLink> pAdjacentLinkset, HashMap<Integer, NetLink> pPredecessorLinks)throws Exception
	{
        if (iNodeID < 0)
        { throw new Exception("NetNode encounter an error." + "nodeID should large than 0: " + iNodeID); }
        if (pAdjacentLinkset == null || pPredecessorLinks == null)
        { throw new Exception("NetNode encounter an error." + "pAdjacentLinkset and pPredecessorLinks should not be null"); }
		m_nodeID = iNodeID;
        m_pAdjacentLinkset = pAdjacentLinkset;
        m_pPredecessorLinks = pPredecessorLinks;
        label = null;
     }

    public NetNode(int iNodeID){
         m_nodeID = iNodeID;
         m_pAdjacentLinkset = new HashMap<>();;
         m_pPredecessorLinks = new HashMap<>();
         label = null;
     }
     
    public int NodeID(){
    	 return m_nodeID;
     }

    public int AdjacentLinkCount(){
    	 return m_pAdjacentLinkset.size();
     }
    
    public Label getLabel()
    {
    	return this.label;
    }
    
    public void setLabel(Label newLabel) {
    	this.label = newLabel;
    }
    
    public HashMap<Integer, NetLink> AdjacentLinks(){
    	return m_pAdjacentLinkset;
    }

     public HashMap<Integer, NetLink> PredecessorLinks(){
    	 return m_pPredecessorLinks;
     }
     
     public Boolean hasAdjacentLink(Integer iHeadNodeID)
     {
         return m_pAdjacentLinkset.containsKey(iHeadNodeID);
     }

     public NetLink GetAdjacentLink(Integer iHeadNodeID)
     {
         try
         {
             return m_pAdjacentLinkset.get(iHeadNodeID);
         }
         catch (Exception e)
         {
        	 System.out.println("NetNode encounter an error." + "node " + m_nodeID + " does not contain any adjacent node:" + iHeadNodeID);
         }
		return m_pAdjacentLinkset.get(iHeadNodeID);
     }

     public void AddAdjacentLink(NetLink pAdjacentLink)
     {
         m_pAdjacentLinkset.put(pAdjacentLink.headNodeID, pAdjacentLink);
     }

     public void RemoveAdjacentLink(Integer iHeadNodeID)
     {
         if (m_pAdjacentLinkset.containsKey(iHeadNodeID))
         {
             m_pAdjacentLinkset.remove(iHeadNodeID);
         }
     }

     public void Release()
     {
         m_pAdjacentLinkset.clear();
         m_pPredecessorLinks.clear();
     }

     public void AddPredecessorLink(NetLink pPredecessorLink) throws Exception
     {
         if (pPredecessorLink == null)
         { throw new Exception("NetNode encounter an error." + "pPredecessorLink should not be null"); }
         m_pPredecessorLinks.put(pPredecessorLink.tailNodeID, pPredecessorLink);
     }

     public void RemovePredecessorLink(int iTailNodeID) throws Exception
     {
         if (m_pPredecessorLinks.containsKey(iTailNodeID))
         {
             m_pPredecessorLinks.remove(iTailNodeID);
         }
         else
         {
        	 throw new Exception("NetNode encounter an error.input tail node does not exist:");
         }
     }
}

2.2 边的定义

package Graph;

public class NetLink {
	public NetNode headNode;
	public NetNode tailNode;
	public double linkCost;
	public int headNodeID;
	public int tailNodeID;
	public int linkID;
	
	public NetLink(NetNode pTailNode, NetNode pHeadNode, double pLinkCost, int plinkID){
		this.headNode = pHeadNode;
		this.tailNode = pTailNode;
		this.linkCost = pLinkCost;
		this.headNodeID = pHeadNode.NodeID();
		this.tailNodeID = pTailNode.NodeID();
		this.linkID = plinkID;
	}
}

2.3 网络的定义

package Graph;
import java.util.*;

public class Network {
	public HashMap<Integer, NetNode> m_pNodesets;
	
	public Network() {
		m_pNodesets = new HashMap<>();
	}
	
    public Boolean isNodeExists(int iNodeID)
    {
        return m_pNodesets.containsKey(iNodeID);
    }
	
    public NetNode GetNetNode(int iNodeID) throws Exception
    {
        try
        {
            return m_pNodesets.get(iNodeID);
        }
        catch (Exception ex)
        {
        	throw new Exception("Network.GetNetNode() encounter an error." + ex.getStackTrace());
        }
    }
    
    public Boolean isLinkExists(int iTailNodeID, int iHeadNodeID) throws Exception
    {
        if (!this.isNodeExists(iTailNodeID))
        {
            return false;
        }
        NetNode pTailNode = this.GetNetNode(iTailNodeID);
        if (!pTailNode.hasAdjacentLink(iHeadNodeID))
        {
            return false;
        }
        return true;
    }
    
    public NetLink GetNetLink(int iTailNodeID, int iHeadNodeID) throws Exception
    {
        try
        {
            NetNode pTailNode = this.GetNetNode(iTailNodeID);
            return pTailNode.GetAdjacentLink(iHeadNodeID);
        }
        catch (Exception ex)
        {
            throw new Exception("Network.GetNetLink() encounter an error.");
        }
    }
    
    public void AddNode(int iNodeID, NetNode pNetNode) throws Exception
    {
        if (iNodeID < 0)
        {
        	throw new Exception("Network encounter an error." + "nodeID should not be negative: " + iNodeID);
        }

        if (this.isNodeExists(iNodeID))
        {
        	throw new Exception("Network encounter an error." + "input node is alread in the network: " + iNodeID);
        }

        if (pNetNode == null)
        {
        	throw new Exception("Network encounter an error." + "input pNetNode should not be null");
        }
        m_pNodesets.put(iNodeID, pNetNode);
    }

    public void RemoveNode(int iNodeID) throws Exception
    {
        if (m_pNodesets.containsKey(iNodeID))
        {
            m_pNodesets.remove(iNodeID);
        }
        else
        {
        	throw new Exception("Network.RemoveNode() encounter an error." + "Node " + iNodeID + " is not in the network.");
        }
    }
}

2.4 路网加载

package SSP;

import java.io.BufferedReader;
import java.io.FileReader;

import Graph.NetLink;
import Graph.NetNode;
import Graph.Network;

public class NetworkLoader {
	private BufferedReader reader;

	public Network loadNet(String path) throws Exception {
		Network pNetwork = new Network();
		//File inFile = new File(path);
		String inString ="";
		try {
			//reader = new BufferedReader(new FileReader("C:\\Users\\hhhSir\\Desktop\\Network.csv"));
			reader = new BufferedReader(new FileReader(path));
			reader.readLine();
			
			NetNode pTailNode;
			NetNode pHeadNode;
			NetLink pNetLink;
			
			while((inString=reader.readLine())!=null) {
				String item[] = inString.split(",");
				int fromNodeID = new Integer(item[0]);
				int toNodeID = new Integer(item[1]);
				int objectID = new Integer(item[2]);
				double linkCost = new Double(item[3]);
				
				if(!pNetwork.isNodeExists(fromNodeID)) {
					pTailNode = new NetNode(fromNodeID);
					//pNetwork.m_pNodesets.put(fromNodeID, pTailNode);
					pNetwork.AddNode(fromNodeID, pTailNode);
				}
				else {
					pTailNode = pNetwork.GetNetNode(fromNodeID);
				}
				
				if(!pNetwork.isNodeExists(toNodeID)) {
					pHeadNode = new NetNode(toNodeID);
					//pNetwork.m_pNodesets.put(toNodeID, pHeadNode);
					pNetwork.AddNode(toNodeID, pHeadNode);
				}
				else {
					pHeadNode = pNetwork.GetNetNode(toNodeID);
				}
				
				if(!pTailNode.hasAdjacentLink(toNodeID)) {
					pNetLink = new NetLink(pTailNode, pHeadNode, linkCost, objectID);
					pTailNode.AddAdjacentLink(pNetLink);
				}
				else {
					 throw new Exception("pTailNode exists the adjacentLink, the link exists"); 
				}
				
				if(!pHeadNode.PredecessorLinks().containsKey(fromNodeID)) {
					pHeadNode.AddPredecessorLink(pNetLink);
				}
				else {
					throw new Exception("pHeadNode exists the PredecessorLink, the link exists");
				}
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return pNetwork;
	}
}

2.5 解释说明

其中上面用到了一个label类,也是自己定义的,主要是为了计算最短距离溯源用的,先贴个代码,后续有时间写一点最短路径的算法。

package SSP;

import Graph.*;

public class Label {
	public NetNode pAssociatedNode;
	public double iPathCost;  //pathCost
	public NetLink pLink;
	public Label pPrevious;
	
	public Label(NetNode pNode, double pathCost, NetLink link, Label previous) {
		this.pAssociatedNode = pNode;
		this.iPathCost = pathCost;
		this.pLink = link;
		this.pPrevious = previous;
	}
	
	public Label(NetNode pNode) {
		this.pAssociatedNode = pNode;
		this.iPathCost = 0;
		this.pLink = null;
		this.pPrevious = null;
	}
	
	//扩展下一个节点
	public Label ExpandAcylic(NetLink pLink) {
		NetNode pNode = pLink.headNode;
		//判断扩展的节点是不是在路径中
		if(this.isNodeInPath(pNode.NodeID())) {
			return null;
		}
		else {
			//生成新的label
			double newPathCost = this.iPathCost+pLink.linkCost;
			return new Label(pNode, newPathCost, pLink, this);
			//return null;
		}
	}
	
	public Boolean isNodeInPath(int iNodeID) {
		if(iNodeID==pAssociatedNode.NodeID()) {
			return true;
		}
		Label pLabel = this;
		while(pLabel!=null) {
			if(pLabel.pAssociatedNode.NodeID()==iNodeID) {
				return true;
			}
			pLabel = pLabel.pPrevious;
		}
		return false;
	}
}

该demo的用例是一个Network.csv的文件,文件格式为:
在这里插入图片描述
分别是起点、终点、对应边编号、对应边长度四个字段。测试数据如下所示,是厦门市厦门岛路网数据。
路网示意图
链接:https://pan.baidu.com/s/1WgWl-w5xDGSFNnL5iJ_pbA
提取码:py6k

以下是Java实现稀疏矩阵十字链表储存的示例代码: ```java //定义节点类 class OLNode { int row, col; int val; OLNode right, down; } //定义十字链表类 class CrossList { int rows, cols, nums; OLNode[] rowHeads, colHeads; //构造函数 public CrossList(int[][] matrix) { rows = matrix.length; cols = matrix[0].length; rowHeads = new OLNode[rows]; colHeads = new OLNode[cols]; //初始化行列头节点 for (int i = 0; i < rows; i++) { rowHeads[i] = new OLNode(); rowHeads[i].row = i; rowHeads[i].right = rowHeads[i]; } for (int j = 0; j < cols; j++) { colHeads[j] = new OLNode(); colHeads[j].col = j; colHeads[j].down = colHeads[j]; } //遍历矩阵,创建节点并插入十字链表 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (matrix[i][j] != 0) { OLNode node = new OLNode(); node.row = i; node.col = j; node.val = matrix[i][j]; //插入行链表 OLNode p = rowHeads[i]; while (p.right != rowHeads[i] && p.right.col < j) { p = p.right; } node.right = p.right; p.right = node; //插入列链表 p = colHeads[j]; while (p.down != colHeads[j] && p.down.row < i) { p = p.down; } node.down = p.down; p.down = node; nums++; } } } } //打印十字链表 public void print() { for (int i = 0; i < rows; i++) { OLNode p = rowHeads[i].right; for (int j = 0; j < cols; j++) { if (p != rowHeads[i] && p.col == j) { System.out.print(p.val + " "); p = p.right; } else { System.out.print("0 "); } } System.out.println(); } } } //测试代码 public class Main { public static void main(String[] args) { int[][] matrix = {{1, 0, 0, 0}, {0, 2, 0, 0}, {0, 0, 3, 0}, {0, 0, 0, 4}}; CrossList crossList = new CrossList(matrix); crossList.print(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值