图:是由结点集合及结点间的关系集合组成的一种数据结构。
结点和边:图中的顶点称作结点,图中的第i个结点记做vi。
有向图: 在有向图中,结点对<x ,y>是有序的,结点对<x,y>称为从结点x到结点y的一条有向边,因此,<x,y>与<y,x>是两条不同的边。有向图中的结点对<x,y>用一对尖括号括起来,x是有向边的始点,y是有向边的终点,有向图中的边也称作弧。
无向图 :在无向图中,结点对(x,y)是无序的,结点对(x,y)称为与结点x和结点y相关联的一条边。(x,y)等价于<x,y>和
<y,x>。
完全图 :在有n个结点的无向图中,若有n(n-1)/2条边,即任意两个结点之间有且只有一条边,则称此图为无向完全图。在有n个结点的有向图中,若有n(n-1)条边,即任意两个结点之间有且只有方向相反的两条边,则称此图为有向完全图。
邻接结点 :在无向图G中,若(u,v)是E(G)中的一条边,则称u和v互为邻接结点,并称边(u,v)依附于结点u和v。在有向图G中,若<u,v>是E(G)中的一条边,则称结点u邻接到结点v,结点v邻接自结点u,并称边<u,v>和结点u和结点v相关联。
结点的度 :结点v的度是与它相关联的边的条数,记作TD(v)。
路径 :在图G=(V,E)中,若从结点vi出发有一组边使可到达结点vj,则称结点vi到结点vj的结点序列为从结点vi到结点vj的路径
权 :有些图的边附带有数据信息,这些附带的数据信息称为权。第i条边的权用符号wi表示。
路径长度 :对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和。
子图 :设有图G1={V1,E1}和图G2={V2,E2},若V2V1且E2E1,则称图G2是图G1的子图。
连通图和强连通图 :在无向图中,若从结点vi到结点vj有路径,则称结点vi和结点vj是连通的。如果图中任意一对结点都是连通的,则称该图是连通图。在有向图中,若对于任意一对结点vi和结点vj(vi≠vj)都存在路径,则称图G是强连通图。
最小生成树 :一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图联通的最少的边。(n-1)条边。
假设图G=(V,E)有n个结点,即V={v0,v1,…,vn-1},E可用如下形式的矩阵A描述,对于A中的每一个元素aij,满足:
由于矩阵A中的元素aij表示了结点vi和结点vj之间边的关系,或者说,A中的元素aij表示了结点vi和结点vj(0≤j≤n-1)的邻接关系,所以矩阵A称作邻接矩阵。
图的遍历算法设计需要考虑三个问题:
(1)图的特点是没有首尾之分,所以算法的参数要指定访问的第一个结点;
(2)对图的遍历路径有可能构成一个回路,从而造成死循环,所以算法设计要考虑遍历路径可能出现的死循环问题;
(3)一个结点可能和若干个结点都是邻接结点,要使一个结点的所有邻接结点按照某种次序被访问。
(1)访问结点v并标记结点v为已访问;
(2)查找结点v的第一个邻接结点w;
(3)若结点v的邻接结点w存在,则继续执行,否则算法结束;
(4)若结点w尚未被访问则深度优先搜索递归访问结点w;
(5)查找结点v的w邻接结点的下一个邻接结点w,(1)访问初始结点v并标记结点v为已访问;
(2)结点v入队列;
(3)当队列非空时则继续执行,否则算法结束;
(4)出队列取得队头结点u;
(5)查找结点u的第一个邻接结点w;
(6)若结点u的邻接结点w不存在,则转到步骤(3),否则循环执行,
(6.1)若结点w尚未被访问,则访问结点w,并标记结点w为已访问;
(6.2)结点w入队列;
(6.3)查找结点u的w邻接结点后的下一个邻接结点w,转到步骤(6)。
以下图有向图为例:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* @Description: 邻接矩阵类(有向)
* @Author: Ni Fujia
* @CreateDate: 2018/7/14 14:09
**/
public class MyAdjGraphic {
//如果两个节点之间没有边,权值为-1
static final int MAXWEIGHT = -1;
//存放节点的集合
List vertices = new ArrayList();
//邻接矩阵的二维数组
int[][] edegs;
//边的数量
int numOfEdges;
public MyAdjGraphic(int numOfEdges) {
init(numOfEdges);
}
/**
* @param numOfEdges 边的数量
* @Description: 初始化临街矩阵
* @Return: void
* @Author: Ni Fujia
* @CreateDate: 2018/7/14 14:14
*/
private void init(int numOfEdges) {
this.edegs = new int[numOfEdges][numOfEdges];
for (int i = 0; i < numOfEdges; i++) {
for (int j = 0; j < numOfEdges; j++) {
//对角线上的元素
if (i == j) {
this.edegs[i][j] = 0;
}else{
this.edegs[i][j] = MAXWEIGHT;
}
}
}
this.numOfEdges = 0;
}
//返回边的数量
public int getNumOfEdges() {
return this.numOfEdges;
}
//返回节点的数量
public int getVertices() {
return this.vertices.size();
}
//返回节点的值
public Object getValueOfVertices(int index) {
return this.vertices.get(index);
}
//获取某条边的权值
public int getWeightOfVertices(int row, int col) {
if ((row < 0 || row >= vertices.size()) || (col < 0 || col >= vertices.size())) {
throw new IndexOutOfBoundsException("row或者col参数不合法");
}
return this.edegs[row][col];
}
//插入节点
public void insertVertices(Object obj) {
this.vertices.add(obj);
}
//插入带权值的边
public void insertEdges(int row, int col, int weight) {
if ((row < 0 || row >= vertices.size()) || (col < 0 || col >= vertices.size())) {
throw new IndexOutOfBoundsException("row或者col参数不合法");
}
this.edegs[row][col] = weight;
this.numOfEdges++;
}
//删除某条边
public void deleteEdges(int row, int col) throws Exception {
if ((row < 0 || row >= vertices.size()) || (col < 0 || col >= vertices.size())) {
throw new IndexOutOfBoundsException("row或者col参数不合法");
}
//边不存在的情况
if (row == col || this.edegs[row][col] == MAXWEIGHT) {
throw new Exception("边不存在");
}
this.edegs[row][col] = MAXWEIGHT;
this.numOfEdges--;
}
//打印邻接矩阵
public void print() {
for (int i = 0; i < edegs.length; i++) {
for (int j = 0; j < edegs[i].length; j++) {
System.out.print(edegs[i][j] + "\t");
}
System.out.println();
}
}
//取第一个邻接节点
public int getFirstNeighbor(int row) {
if (row < 0 || row >= vertices.size()) {
throw new IndexOutOfBoundsException("vertice参数不合法");
}
for (int col = 0; col < this.vertices.size(); col++) {
if (this.edegs[row][col] > 0) {
return col;
}
}
return -1;
}
//取下一个邻接节点
public int getNextNeighbor(int row,int col){
if ((row < 0 || row >= vertices.size()) || (col < 0 || col >= vertices.size())) {
throw new IndexOutOfBoundsException("row或者col参数不合法");
}
for (int result = col + 1; result < this.vertices.size(); result++) {
if (this.edegs[row][result] > 0) {
return result;
}
}
return -1;
}
/**
* @Description: 连通图的深度优先遍历的真正实现算法
* @param vertice 以vertice为初始节点序号
* @param visited visited数组标记节点是否被访问过,true表示访问过,false表示未访问
* @Return: void
* @Author: Ni Fujia
* @CreateDate: 2018/7/15 10:55
*/
private void depthFirstSearch(int vertice, boolean visited[]) {
//访问该节点
System.out.print(this.getValueOfVertices(vertice)+" ");
//将改节点标记为已访问
visited[vertice] = true;
//取第一个邻接节点
int col = getFirstNeighbor(vertice);
//当邻接节点存在时循环
while (col != -1) {
//如果没有访问过,以col为初始节点递归遍历
if (!visited[col]) {
depthFirstSearch(col, visited);
}
//取下一个邻接节点
col = getNextNeighbor(vertice, col);
}
}
//连通图的深度优先遍历算法
public void depthFirstSearch(){
boolean[] visited = new boolean[this.vertices.size()];
for (int i = 0; i < visited.length; i++) {
visited[i] = false;
}
for (int j = 0; j < visited.length; j++) {
if (!visited[j]) {
depthFirstSearch(j, visited);
}
}
}
/**
* @Description: 广度优先遍历算法的真正实现
* @param vertice 初始节点
* @param visited 数组visited标记节点是否被访问过
* @Return: void
* @Author: Ni Fujia
* @CreateDate: 2018/7/15 11:52
*/
private void broadFirstSearch(int vertice, boolean visited[]) {
int u,w;
//创建顺序队列queue
Queue queue = new LinkedList();
System.out.print(this.getValueOfVertices(vertice) + " ");
visited[vertice] = true;
//结点vertice入队列
queue.add(new Integer(vertice));
while (!queue.isEmpty()) {
//出队列
u = ((Integer) queue.remove()).intValue();
w = getFirstNeighbor(u);
while (w != -1) {
if (!visited[w]) {
System.out.print(this.getValueOfVertices(w)+" ");
visited[w] = true;
queue.add(new Integer(w));
}
//取结点u的邻接结点w的下一个邻接结点
w=this.getNextNeighbor(u, w);
}
}
}
//连通图的深度优先遍历算法
public void broadFirstSearch(){
boolean[] visited = new boolean[this.vertices.size()];
for (int i = 0; i < visited.length; i++) {
visited[i] = false;
}
for (int j = 0; j < visited.length; j++) {
if (!visited[j]) {
broadFirstSearch(j, visited);
}
}
}
}
/**
* @Description: 描述边的权值类
* @Author: Ni Fujia
* @CreateDate: 2018/7/14 14:07
**/
public class Weight {
int row; //横坐标
int col; //纵坐标
int weight; //权值
public Weight(int row, int col, int weight) {
this.row = row;
this.col = col;
this.weight = weight;
}
/**
* @param myAdjGraphic 临界矩阵对象类
* @param vertices 节点对象数组
* @param weights 权值数组
* @param numOfVertices 节点的数量
* @param numOfEdges 边的数量
* @Description: 创建矩阵
* @Return: void
* @Author: Ni Fujia
* @CreateDate: 2018/7/14 14:35
*/
public static void createAdjGraphic(MyAdjGraphic myAdjGraphic, Object vertices[],
Weight weights[], int numOfVertices, int numOfEdges) {
//初始化节点
for (int i = 0; i < numOfVertices; i++) {
myAdjGraphic.insertVertices(vertices[i]);
}
//初始化所有的边
for (int j = 0; j < numOfEdges; j++) {
myAdjGraphic.insertEdges(weights[j].row, weights[j].col, weights[j].weight);
}
}
}
测试:
**
* @Description: 测试图的邻接矩阵
* @Author: Ni Fujia
* @CreateDate: 2018/7/14 14:42
**/
public class Test {
public static void main(String[] args) {
//节点的数量
int numOfVertices = 5;
//边的数量
int numOfEdges = 5;
MyAdjGraphic myAdjGraphic = new MyAdjGraphic(numOfEdges);
//节点数组
Object[] vertices = new Object[]{
new Character('A'),
new Character('B'),
new Character('C'),
new Character('D'),
new Character('E')
};
//权值数组
Weight[] weights = new Weight[]{
new Weight(0,1,10),
new Weight(0,4,20),
new Weight(2,1,40),
new Weight(1,3,30),
new Weight(3,2,50)
};
Weight.createAdjGraphic(myAdjGraphic, vertices, weights, numOfVertices, numOfEdges);
System.out.println("改邻接矩阵如下:");
myAdjGraphic.print();
System.out.println("邻接矩阵的节点数量为:" + myAdjGraphic.getVertices());
System.out.println("邻接矩阵的边的数量为:" + myAdjGraphic.getNumOfEdges());
try {
/*myAdjGraphic.deleteEdges(0, 4);
System.out.println("删除E之后的临界矩阵为:");
myAdjGraphic.print();*/
System.out.println("邻接矩阵的深度优先遍历算法结果是:");
myAdjGraphic.depthFirstSearch();
System.out.println();
System.out.println("邻接矩阵的广度优先遍历算法结果是:");
myAdjGraphic.broadFirstSearch();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果: