@author QYX
写作时间:2013/0302 最近准备noi比赛,加油!!!
因为近期学习任务太多太紧,所以我主要维护Github,博客园可能会停更几天。----2020年2月9日
图
图(graph)是用线连接在一起的顶点或节点的集合,即两个要素:边和顶点。每一条边连接个两个顶点,用(i,j)表示顶点为 i 和 j 的边。
如果用图示来表示一个图,一般用圆圈表示顶点,线段表示边。有方向的边称为有向边,对应的图成为有向图,没有方向的边称为无向边,对应的图叫无向图。对于无向图,边(i, j)和(j,i)是一样的,称顶点 i 和 j 是邻接的,边(i,j)关联于顶点 i 和 j ;对于有向图,边(i,j)表示由顶点 i 指向顶点 j 的边,即称顶点 i 邻接至顶点 j ,顶点 i 邻接于顶点 j ,边(i,j)关联至顶点 j 而关联于顶点 i 。
对于很多的实际问题,不同顶点之间的边的权值(长度、重量、成本、价值等实际意义)是不一样的,所以这样的图被称为加权图,反之边没有权值的图称为无权图。所以,图分为四种:加权有向图,加权无向图,无权有向图,无权无向图。
图的表现有很多种,邻接表法,临接矩阵等。
图经常是以这种形式出现的[weight,from,to]的n*3维数组出现的,见名知意,三个元素分别为边的权重,从哪儿来,到哪儿去。
如上图所示,由一条边连接在一起的顶点称为相邻顶点,A和B是相邻顶点,A和D是相邻顶点,A和C是相邻顶点......A和E是不相邻顶点。一个顶点的度是其相邻顶点的数量,A和其它三个顶点相连,所以A的度为3,E和其它两个顶点相连,所以E的度为2......路径是一组相邻顶点的连续序列,如上图中包含路径ABEI、路径ACDG、路径ABE、路径ACDH等。简单路径要求路径中不包含有重复的顶点,如果将环的最后一个顶点去掉,它也是一个简单路径。例如路径ADCA是一个环,它不是一个简单路径,如果将路径中的最后一个顶点A去掉,那么它就是一个简单路径。如果图中不存在环,则称该图是无环的。如果图中任何两个顶点间都存在路径,则该图是连通的,如上图就是一个连通图。如果图的边没有方向,则该图是无向图,上图所示为无向图,反之则称为有向图,下图所示为有向图:

在有向图中,如果两个顶点间在双向上都存在路径,则称这两个顶点是强连通的,如上图中C和D是强连通的,而A和B是非强连通的。如果有向图中的任何两个顶点间在双向上都存在路径,则该有向图是强连通的,非强连通的图也称为稀疏图。
此外,图还可以是加权的。前面我们看到的图都是未加权的,下图为一个加权的图:

可以想象一下,前面我们介绍的树和链表也属于图的一种特殊形式。图在计算机科学中的应用十分广泛,例如我们可以搜索图中的一个特定顶点或一条特定的边,或者寻找两个顶点间的路径以及最短路径,检测图中是否存在环等等。
存在多种不同的方式来实现图的数据结构,下面介绍几种常用的方式。
邻接矩阵
在邻接矩阵中,我们用一个二维数组来表示图中顶点之间的连接,如果两个顶点之间存在连接,则这两个顶点对应的二维数组下标的元素的值为1,否则为0。下图是用邻接矩阵方式表示的图:
如果是加权的图,我们可以将邻接矩阵中二维数组里的值1改成对应的加权数。邻接矩阵方式存在一个缺点,如果图是非强连通的,则二维数组中会有很多的0,这表示我们使用了很多的存储空间来表示根本不存在的边。另一个缺点就是当图的顶点发生改变时,对于二维数组的修改会变得不太灵活。
邻接表
图的另外一种实现方式是邻接表,它是对邻接矩阵的一种改进。邻接表由图中每个顶点的相邻顶点列表所组成。如下图所示,我们可以用数组、链表、字典或散列表来表示邻接表。

关联矩阵
我们还可以用关联矩阵来表示图。在关联矩阵中,矩阵的行表示顶点,列表示边。关联矩阵通常用于边的数量比顶点多的情况下,以节省存储空间。如下图所示为关联矩阵方式表示的图:

深度优先搜索#
深度优先搜索,我们以无向图为例。
图的深度优先搜索(Depth First Search),和树的先序遍历比较类似。
它的思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
显然,深度优先搜索是一个递归的过程。
广度优先搜索#
广度优先搜索,我们以有向图为例。
广度优先搜索算法(Breadth First Search),又称为”宽度优先搜索”或”横向优先搜索”,简称BFS。
它的思想是:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。
换句话说,广度优先搜索遍历图的过程是以v为起点,由近至远,依次访问和v有路径相通且路径长度为1,2…的顶点。
package com.qyx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
//使用邻接矩阵来实现图
public class Graph {
private ArrayList<String> vertexList; //存储顶点的集合
private int[][] edges;//存储图对应的邻接矩阵
private int numOfEdges;//表示边的数目
private boolean isVisited[];
public static void main(String[] args) {
int n=5;//节点的个数
String vertex[]={"A","B","C","D","E"};
//创建图对象
Graph graph=new Graph(n);
for (String s:vertex)
{
graph.vertexList.add(s);
}
//添加边
graph.insertEdge(0,1,1);
graph.insertEdge(0,2,1);
graph.insertEdge(1,2,1);
graph.insertEdge(1,3,1);
graph.insertEdge(1,4,1);
//显示邻接矩阵
graph.showGraph();
//graph.dfs();
graph.bfs();
}
//构造器
public Graph(int n)
{
//初始化邻接矩阵和vertexList
edges=new int[n][n];
vertexList=new ArrayList<String>();
numOfEdges=0;
isVisited=new boolean[5];
}
//插入顶点
public void insertVertex(String vertex)
{
vertexList.add(vertex);
}
/**
*
* @param v1 表示点的下标 即是第几个顶点
* @param v2 第二个顶点对应的下标
* @param weight 表示是否连接 0不相连 1相连
*/
//添加边
public void insertEdge(int v1,int v2,int weight)
{
edges[v1][v2]=weight;
edges[v2][v1]=weight;
numOfEdges++;
}
//图中常用的方法
//返回节点的个数
public int getNumOfVertex()
{
return vertexList.size();
}
//得到边的数目
public int getNumOfEdges()
{
return numOfEdges;
}
//返回节点i对应的值 0-A 1-B 2-C
public String getValue(int index)
{
return vertexList.get(index);
}
//返回v1和v2的权值
public int getWeight(int v1,int v2)
{
return edges[v1][v2];
}
//显示图对应的邻接矩阵
public void showGraph()
{
for (int[] arrs:edges)
{
System.out.println(Arrays.toString(arrs));
}
}
/**
*
* @param index
* @return 如果存在返回对应的下标,否则返回-1
*/
//得到第一个邻接节点的下标
public int getFirstNeighbor(int index)
{
for (int j=index;j<vertexList.size();j++)
{
if (edges[index][j]>0)
{
return j;
}
}
return -1;
}
//根据前一个邻接节点的下标来获取下一个邻接节点
public int getNextNeighbor(int v1,int v2) {
for (int i = v2 + 1; i < vertexList.size(); i++) {
if (edges[v1][i] > 0) {
return i;
}
}
return -1;
}
//深度优先遍历算法
//i 第一次就是0
private void dfs ( boolean[] isVisited, int i)
{
//首先我们访问该节点
System.out.print(getValue(i) + "->");
//将该邻接节点设置为已访问
isVisited[i] = true;
int w = getFirstNeighbor(i);//查找节点w的第一个邻接节点w
while (w != -1) {
if (!isVisited[w]) {
dfs(isVisited,w);
}
//如果已经被访问过
w=getNextNeighbor(i,w);
}
}
//对dfs进行重载,遍历所有的节点并进行dfs
public void dfs()
{
//遍历所有的节点进行dfs
for (int i=0;i<getNumOfVertex();i++)
{
if (!isVisited[i])
{
dfs(isVisited,i);
}
}
}
//对一个节点进行深度优先遍历的方法
public void bfs(boolean[] isVisited,int i)
{
int u;//表示队列的头结点对应下标
int w;//邻接节点
//队列,记录节点访问的顺序
LinkedList queue = new LinkedList();
//访问节点
System.out.print(getValue(i)+"->");
//标记为已访问
isVisited[i]=true;
//将节点加入队列
queue.addLast(i);
while (!queue.isEmpty())
{
//取出队列的头结点下标
u =(Integer)queue.removeFirst();
//得到第一个邻接节点的下标w
w =getFirstNeighbor(u);
while (w!=-1)
{
//找到
//是否访问
if (!isVisited[w])
{
System.out.print(getValue(w)+"->");
//入队
queue.addLast(w);
isVisited[w]=true;
}
//找w后面的下一个邻接节点
w=getNextNeighbor(u,w); //体现出广度优先
}
}
}
//遍历所有的节点都进行广度优先搜索
public void bfs()
{
for (int i=0;i<getNumOfVertex();i++)
{
if (!isVisited[i])
{
bfs(isVisited,i);
}
}
}
}
2020年2月9日对原有博客进行了修改,参考博客:
https://www.cnblogs.com/jaxu/p/11338294.html
https://www.cnblogs.com/DarrenChan/p/9547869.html
感谢帮助!
来源:https://www.cnblogs.com/qyx66/p/12289482.html