數據結構:圖的常用操作(鄰接矩陣表示)

首先來看一下圖的存儲結構

       在這裏,明確一下圖和網的區別:圖節點的關係爲否聯通 網節點之間的連通路徑具有長度
       在圖鄰接矩陣的表示中,非聯通節點的值爲0,連通節點的值爲1
       如果對於網而言,一般節點自身值爲0,連通節點之間爲距離,不連通節點的距離設置爲不可能出現的最大值

下面的鄰接矩陣表示的是網,因爲後面學習最小生成樹算法和最短路徑算法中用的到

class AdjMatrix
{
	int[][] adjMatrix= {{0,9,4,Integer.MAX_VALUE,6},
			{9,0,3,3,Integer.MAX_VALUE},
			{4,3,0,5,Integer.MAX_VALUE},
			{Integer.MAX_VALUE,3,5,0,1},
			{6,3,Integer.MAX_VALUE,1,0}};//鄰接矩陣
	int nodeNum=5;//圖中節點個數
	int[] data= {0,1,2,3,4};//表示鄰接矩陣各位置的值 可以爲任意類型
	public AdjMatrix(){}//默認構造函數 使用默認值
	//分配內存,期待後期賦值
	public AdjMatrix(int nodeNum)
	{
		this.nodeNum=nodeNum;
		adjMatrix=new int[nodeNum][nodeNum];
		data=new int[nodeNum];
	}
}

關於用鄰接矩陣表示的圖有以下基本操作:

鄰接矩陣的遍歷

深度優先遍歷
遞歸實現

/******************
	 * 深度優先遍歷用鄰接矩陣表示的圖
	 * @param matrix 圖結構
	 */
	public static void DFSTraversal(AdjMatrix graph)
	{
		//非空判斷
		if(graph==null||graph.data.length==0)
		{
			return;
		}
		int nodeNum=graph.data.length;//節點個數
		//建立boolean數組表示節點是否訪問
		boolean[] visited=new boolean[nodeNum];
		for(int i=0;i<nodeNum;i++)
		{
			if(!visited[i])//檢測節點是否訪問過
			{
				DFS(graph,i,visited);
			}
		}
	}
	/**********************
	 * 訪問圖中的一個節點
	 * @param matrix 用來表示圖的鄰接矩陣
	 * @param index 訪問節點的位置
	 * @param visited
	 */
	private static void DFS(AdjMatrix graph,int index,boolean[] visited)
	{
		//訪問i節點 並設置訪問標識
		System.out.println(graph.data[index]);
		visited[index]=true;
		for(int i=0;i<graph.data.length;i++)
		{
			if(graph.adjMatrix[index][i]!=0&&graph.adjMatrix[index][i]!=Integer.MAX_VALUE&&!visited[i])
			{
				DFS(graph,i,visited);
			}
		}
	}
	/*********************
	

廣度優先遍歷
借用隊列實現

	/************************
	 * 廣度優先遍歷圖
	 * @param graph 需要進行遍歷的圖結構
	 */
	public static void BFSTraversal(AdjMatrix graph)
	{
		//非空判斷
		if(graph==null||graph.data.length==0)
		{
			return;
		}
		//記錄節點個數
		int nodeNum=graph.data.length;
		//使用數組模擬隊列
		int[] queue=new int[nodeNum];
		int queueHead=0;//隊列頭節點位置
		int queueNext=0;//隊列尾節點位置
		//記錄節點是否被讀取過
		boolean[] visited=new boolean[nodeNum];
		//保證所有的節點都被訪問過  此循環用於處理非聯通圖的情況
		for(int i=0;i<nodeNum;i++)
		{
			if(!visited[i])
			{
				visited[i]=true;
				//訪問該節點位置
				System.out.println(graph.data[i]);
				queue[queueNext++]=i;//將節點加入隊列中
				//當隊列不爲空時      此循環可以將與0位置聯通的節點全部遍歷
				while(queueHead!=queueNext)
				{
					int tempIndex=queue[queueHead++];//彈出一個位置
					//查看該節點可達的節點
					for(int j=0;j<nodeNum;j++)
					{
						if(graph.adjMatrix[i][j]!=0&&graph.adjMatrix[i][j]!=Integer.MAX_VALUE&&!visited[j])
						{
							visited[j]=true;//訪問該位置
							System.out.println(graph.data[j]);
							queue[queueNext++]=j;//節點入隊
						}
					}
				}
			}
		}
	}
	

最小生成樹求解

prim算法
* 普里姆算法的思路:劃定節點集和邊集,
* 節點集和邊集由一個數組lowcost來表示,下標表示節點的位置,
* 值爲零表示此節點在節點集中,不爲零則表示節點集中的節點到此節點的最短距離
* 初始化時將開始節點加入節點集中,並將邊集置爲節點集中的節點到非節點集中節點的權值
* 每次從邊集中選出一條最短的邊,則該邊一個節點在節點集中,另一個節點不在,將不在節點集中的節點加入節點集中
* 並設置節點集中的各節點之間的距離爲0
* 另設置adjvex數組,其下標表示節點在圖中的位置,值表示lowest數組相應位置的路徑長度值是由哪個節點指向此節點的
* prim算法本質上是一種動態規劃算法**

	/* 根據圖的鄰接矩陣獲取最小生成樹
	 * @param matrix 圖的鄰接矩陣
	 */
	public static void getMinCreateTreePrim(int[][] matrix)
	{
		if(matrix==null||matrix.length==0||matrix[0].length==0)
		{
			return ;
		}
		//圖中的節點數量
		int nodeNum=matrix.length;
		int[] adjvex=new int[nodeNum];
		int[] lowcost=new int[nodeNum];//從節點集中的節點到其他節點的最短距離 爲零表示節點處於節點集中
		//假設從0節點開始訪問
		lowcost[0]=0;//0位置節點加入節點集中
		//初始化 path中已經都初始化爲0 
		for(int i=0;i<nodeNum;i++)
		{
			//值爲零表示此節點在節點集中,不爲零則表示節點集中的節點到此節點的最短距離
			lowcost[i]=matrix[0][i];
			adjvex[i]=0;
		}
		//n個節點的最小生成樹具有n-1條邊 每次循環確定一條邊
		for(int i=1;i<nodeNum;i++)
		{
			int minPath=Integer.MAX_VALUE;//本次循環最短邊的長度
			int minPathIndex=0;//本次循環最短邊是由哪條邊發出的
			//遍歷lowest數組 查找最短邊
			for(int j=0;j<nodeNum;j++)
			{
				if((lowcost[j]!=0)&&(lowcost[j]<minPath))
				{
					minPath=lowcost[j];//最短長度
					minPathIndex=j;
				}
			}
			//將minPathIndex節點加入節點集中
		//	System.out.println(adjvex[minPathIndex]+" "+minPathIndex);
			lowcost[minPathIndex]=0;
			//更新lowest數組值
			for(int j=0;j<nodeNum;j++)
			{
				//如果新加入的節點集的節點到這個節點的距離小於之前節點集中節點到此節點的最小距離 
				if(lowcost[j]>matrix[minPathIndex][j])
				{
					//修改從節點集到此節點的最小距離
					lowcost[j]=matrix[minPathIndex][j];
					adjvex[j]=minPathIndex;//修改指向此節點的節點位置
				}
			}
		}
	}

最短路徑求解

Dijkstra算法
比較迪傑斯特拉算法和普里姆算法,你回發現有很多相似的地方
如果將起點視爲終點,考慮其他節點到起點的距離,算法可能會更好理解。
循環中,首先找到了距離起點最近的一個點,然後更新除起點到起點和距離最近的點到起點的距離之外的其他距離,如果其他節點經過最近的點中轉後到起點的距離小於直接到起點的距離,那麼更新此值,並記錄經過哪個節點中轉。

代碼中,只返回了最短路徑的長度,如果你想獲得最短路徑,可以使用preVex數組,它的值記錄着每個節點的前驅節點,這個路徑即爲end–>preVex[end]–>preVex[preVex[end]]–>…–>start。

/***********************
	 * 迪傑斯特拉算法求解最短路徑 
	 * @param matrix 鄰接矩陣
	 * @param start 開始節點位置
	 * @param end 結束節點的位置
	 * @return 從start到end的節點最短路徑長度
	 */
	public static int getMinPathDijkstra(int[][] matrix,int start,int end)
	{
		if(matrix==null||matrix.length==0||matrix[0].length==0)
		{
			return -1;
		}
		//起點和終點位於同一位置
		if(start==end)
		{
			return 0;
		}
		//記錄圖節點個數
		int nodeNum=matrix.length;
		//前驅節點的下標
		int[] preVex=new int[nodeNum];
		//從開始節點到其它節點的最短距離
		int[] shortPath=new int[nodeNum];
		//已經確定過的節點 原點到該節點的最短路徑已經確定
		boolean[] sureNode=new boolean[nodeNum];
		
		//初始化階段
		sureNode[start]=true;//開始節點已經訪問過
		for(int i=0;i<nodeNum;i++)
		{
			//當前節點到其它節點的距離爲
			shortPath[i]=matrix[start][i];
			//其它節點的前驅節點都爲start
			preVex[i]=start;
		}
		//開始循環確定節點 每次確定一個節點
		for(int i=1;i<nodeNum;i++)
		{
			int minPath=Integer.MAX_VALUE;//當前節點到其它節點的最短距離
			int minPathIndex=0;//所謂其它節點的位置
			//此處的循環爲了查找當前節點集中 兩節點之間的最小值 用於確定一個節點
			for(int j=0;j<nodeNum;j++)
			{
				if(!sureNode[j]&&shortPath[j]<minPath)
				{
					minPath=shortPath[j];
					minPathIndex=j;
				}
			}
			//確定一個節點 
			sureNode[minPathIndex]=true;
			//更新其它節點到開始節點的最短距離
			for(int j=0;j<nodeNum;j++)
			{
				if(!sureNode[j]&&matrix[minPathIndex][i]!=Integer.MAX_VALUE&&shortPath[j]>minPath+matrix[minPathIndex][j])
				{
					shortPath[j]=minPath+matrix[minPathIndex][j];
					preVex[j]=minPathIndex;
				}
			}
		}
		return shortPath[end];
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章