图
1为什么要有图?
1、线性表局限于一个直接前驱和一个直接后继的关系
2、树也只能有一个直接前驱即父节点
3、当我们需要多对多的关系时,就需要图
2、图的基本介绍
1、图是一种数据和结构,其中节点可以具有多个或零个相邻元素。两个结点之间的连接称为边。结点可以称为顶点
3、图的基本概念
1、顶点(vertex)
2、边(edge)
3、路径
4、无向图:顶点之间的连接没有方向
5、有向图:顶点之间连接有方向
6、带权图:这种边带权值的图也叫网
4、图的表示方式
1、邻接矩阵
1、邻接矩阵:表示图形中顶点之间邻接关系的矩阵,对于n个顶点的图而言,矩阵是的row和col表示的是 1…n 个点(1表示量顶点之间是邻接的)
2、邻接表
1、邻接矩阵需要为每个顶点分配 n 个边的空间,其实很多边是不存在,会造成空间的一定损失
2、邻接表是实现只关系存在的边,不关心不存在的边,因此没有浪费,邻接表由数据+链表组成
5、图的入门
1、要求:实现如下图结构
2、思路分析:存储顶点 String 使用 ArrayList ;保存矩阵 二维数组
3、代码实现
public class Graph {
private List<String> vertexList;//存储顶点的集合
private int[][] edges;//存储图对应的邻接矩阵
private int numOfEdges;//图的边数
public Graph(int n){
this.vertexList=new ArrayList<>();
this.edges=new int[n][n];
}
//添加顶点
public void insertVertex(String vertex){
vertexList.add(vertex);
}
//添加边
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 void showGraph(){
for (int[] edge : edges) {
System.err.println(Arrays.toString(edge));
}
}
//返回边的数目
public int getNumOfEdges(){
return numOfEdges;
}
//返回 v1 v2 的权值
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
//返回下标对应的值
public String getValueByIndex(int i){
return vertexList.get(i);
}
}
6、图的深度优先遍历(Depth First Search)
1、图的深度优先遍历的基本思想
1、深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解:每次访问完当前节点后首先访问当前结点的第一个邻接结点。
2、这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的邻接结点进行横向访问。
3、深度优先遍历是一个递归过程。
2、深度优先遍历算法步骤
1、访问初始结点 v ,并标记 v 已经访问过了
2、查找 v 的第一个邻接结点 w
3、若 w 存在,则继续执行 4 ,如果 w 不存在,则回到第一步,将从 v 的下一个结点继续
4、若 w 未被访问,对 w 进行深度优先遍历递归
5、若 w 被访问过了,查找结点 v 的 w 邻接结点的下一个邻接结点,转到 3
3、代码实现
//得到第一个邻接结点的下标
public int getFirstNeighbor(int index){
for(int i=0;i<vertexList.size();i++){
if(edges[index][i]>0){
return i;
}
}
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;
}
public void dfs(boolean[] isVisted,int i){
System.out.print(getValueByIndex(i)+" ");
isVisted[i]=true;
int w = getFirstNeighbor(i);
while(w!=-1){
if(!isVisted[w]){
dfs(isVisted,w);
}
w=getNextNeighbor(i,w);
}
}
public void dfs(){
//for 循环用来处理独立的结点,就是没有边的结点
for(int i=0;i<vertexList.size();i++){
if(!isVisted[i]){
dfs(isVisted,i);
}
}
}
7、图的广度优先遍历
1、图的广度优先遍历的基本思想
1、图的广度优先搜索(Broad First Search)
2、类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点
2、图的广度优先遍历算法步骤
1、初始化结点 v ,并标记为已访问
2、结点 v 入队
3、当队列非空时,继续执行,否则算法结束
4、出队列,取得对头结点 u
5、查找结点 u 的第一个邻接结点 w
6、若结点 u 的邻接结点 w 不存在,则转到步骤 3 ,否则循环执行以下三个步骤
6.1、若结点 w 尚未被访问,则访问结点 w 并标记为已访问,
6.2、结点 w 入队
6.3、查找结点 u 的继 w 邻接结点后的下一个邻接结点 w ,转到 6
3、代码实现
public void bfs(boolean[] isVisted,int i){
Queue<Integer> queue=new LinkedList<>();
// 1、
System.out.println(getValueByIndex(i)+" ");
isVisted[i]=true;
// 2、
queue.offer(i);
// 3、
while(!queue.isEmpty()){
// 4、
Integer u = queue.poll();
// 5、
int w = getFirstNeighbor(u);
while(w!=-1){
// 6.1、
if(!isVisted[w]){
System.out.println(getValueByIndex(w)+" ");
isVisted[w]=true;
// 6.2、
queue.offer(w);
}
// 6.3、
w=getNextNeighbor(u,w);
}
}
}
public void bfs(){
//for 循环用来处理独立的结点,就是没有边的结点
for(int i=0;i<vertexList.size();i++){
if(!isVisted[i]){
bfs(isVisted,i);
}
}
}