图的基本概念
1.图是一种数据结构,其中结点可以具有零个或者多个相邻的元素,两个结点之间的连接称为边。结点可以称为顶点。
2.图的两种表示方式:一个是二维数组来表示(邻接矩阵);另一种即链表表示(邻接表)。
邻接矩阵:1表示两个顶点之间是连通的;0表示两个顶点之间不可以连通。(自己规定的)
缺点:需要为每一个顶点分配n个边的空间,二很多边是不存在的,造成空间的浪费。
邻接表:只关心存在的边,因此没有空间的浪费,邻接表是由数组+链表来实现的。
图的邻接矩阵的创建
思想:使用集合来存储图的顶点,使用二维数组来存储图的边
代码实现:
import java.util.ArrayList;
import java.util.Arrays;
/*
* 图的创建
*/
public class Graph {
private ArrayList<String> vertexList;//首先有一个集合用于存储所有的顶点
private int[][] edges;//有一个二维数组用于存储图对应的邻接矩阵
private int numOfEdges;//用于表示边的数目
public Graph() {
}
public Graph(int n) {
//初始化矩阵和vertexList
edges = new int[n][n];
vertexList = new ArrayList<>();
numOfEdges = 0;
}
//插入顶点:
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
//插入边:
/**
*
* @param v1 v2:插入边的两端顶点分别是第几个顶点
* @param weight 邻接矩阵中为 0,还是1
*/
public void insertEdge(int v1,int v2,int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges ++;
}
/**
* 图的常用方法:
* 1.返回结点的个数
* 2.返回边的个数
* 3.返回下标为i的结点的数据
* 4.返回v1,v2的权值
* 5.显示图对应的矩阵
*/
//1.返回图中结点的个数
public int getNumOfVertex() {
return vertexList.size();
}
//2.返回边的个数
public int getNumOfEdges() {
return numOfEdges;
}
//3.返回下标为i的结点的数据
public String getValueByIndex(int i) {
return vertexList.get(i);
}
//4.返回v1,v2的权值(v1,v2表示第v1个顶点和第v2个顶点【从0开始算】)
public int getWeight(int v1,int v2) {
return edges[v1][v2];
}
//5.显示图对应的矩阵(显示二维数组)
public void showGraph() {
for(int[] link : edges)
System.out.println(Arrays.toString(link));
}
public static void main(String[] args) {
//测试创建一个图的邻接矩阵
Graph graph = new Graph(5);
String VertexValue[] = {"sun","flower","kiki","mimi","cici"};
//添加顶点
for(String v : VertexValue) {
graph.insertVertex(v);
}
//添加边:sun-flower;sun-kiki;flower-kiki;flower-mimi;flower-cici
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();
}
}
创建结果:
图的遍历
图遍历的基本概念
图的遍历,即就是对于结点的访问。一个图有多个结点,如何去访问这些节点,需要特定的策略。一般有两种访问策略:(1)DFS (2)BFS
图的深度优先遍历(DFS)
基本思想:“纵向遍历,一条道走到尽头”:从第一个结点开始访问,初始结点可能会有多个邻接结点,而DFS的策略就是要首先访问第一个邻接结点,然后再将这个结点作为初始结点,访问它的第一个邻接结点。可以理解为:每次在访问完当前结点后首先访问当前结点的第一个邻接结点,是一个递归的过程。
代码实现:
//得到当前结点的第一个邻接结点的下标:如果存在就返回对应的下标,如果不存在就返回对应的下标
public int getFirstNeighbor(int index) {
for(int j = 0; j < vertexList.size();j++) {
if(edges[index][j] > 0) {
return j;
}
}
return -1;
}
//根据前一个邻接结点的下标来获取下一个邻接结点
public int getNextNeighbor(int v1,int v2) {
for(int j = v2 + 1;j < vertexList.size();j++) {
if(edges[v1][j] > 0) {
return j;
}
}
return -1;
}
//深度优先遍历算法:
public void dfs(boolean[] isVisited,int i) {
//首先我们访问当前结点
System.out.println(getValueByIndex(i) + "-->");
//将当前结点设置为已经访问
isVisited[i] = true;
//查找当前结点的第一个邻接结点w
int w = getFirstNeighbor(i);
while(w != -1) {
if(!isVisited[w]) { //说明没有被访问过
dfs(isVisited,w);
}
//如果w结点已经被访问过了,就访问当前结点v的下一个节点
w = getNextNeighbor(i,w);
}
//如果当前结点没有邻接结点,即w不存在,则从V的下一个节点继续(此时的下一个,指的是下标编号)
}
//如果当前结点没有邻接结点,即w不存在,则从V的下一个节点继续(此时的下一个,指的是下标编号)
//对dfs进行一个重载,遍历我们所有的结点,并进行dfs
public void dfs() {
for(int i = 0;i < vertexList.size();i++) {
if(!isVisited[i]) {
dfs(isVisited,i);
}
}
}
图的广度优先遍历(BFS)
基本思想:“横向遍历,扫射遍历”:类似一个分层搜索的过程。他需要用一个队列来保持访问过的结点的顺序,以便按照这些顺序来访问这些结点的邻接结点。
BFS的算法步骤:
1.访问初始结点v,并标记该结点已经被访问;
2.将当前的v结点入队列;
3.当队列非空时,继续执行;否则,算法结束(指的是对A的算法结束【以A开始的访问已经全部结束】);
4.出队列,取得头结点u【A出队列,得到第一个结点】;
5.查找结点u的第一个邻接结点w;
6.如果结点u的邻接结点w不存在,则转至步骤3;否则就执行以下步骤:
1)若结点w没有被访问,则访问结点w并标记为已访问;
2)结点w入队列
3)查找结点u的继w邻接结点后的下一个邻接结点w,转至步骤6。
代码实现:
/**
* 广度优先遍历(BFS):
*/
public void bfs(boolean[] isVisited,int i) {
int u;//表示队列的头结点对应的下标
int w;//表示邻接结点的下标
LinkedList<Integer> queue = new LinkedList<>();//队列,记录结点访问的顺序
//访问当前结点
System.out.println(getValueByIndex(i) + "-->");
//标记当前结点已经访问
isVisited[i] = true;
//当前结点入队列
queue.addLast(i);
while (!queue.isEmpty()) {
//取出队列的头结点下标
u = queue.removeFirst();
//得到u结点的第一个邻接结点
w = getFirstNeighbor(u);
while (w != -1) { //存在
//判断是否访问过
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "-->");
isVisited[w] = true;
queue.addLast(w);
}
//如果访问过,则找u结点的下一个邻接结点
w = getNextNeighbor(u,w);
}
}
}
//遍历所有的结点,都要进行广度优先搜索
public void bfs() {
for(int i = 0;i < vertexList.size();i++) {
if(!isVisited[i]) {
bfs(isVisited,i);
}
}
}
图的代码实现
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 Graph() {
}
public Graph(int n) {
//初始化矩阵和vertexList
edges = new int[n][n];
vertexList = new ArrayList<>();
numOfEdges = 0;
isVisited = new boolean[n];
}
/**
* 广度优先遍历(BFS):
*/
public void bfs(boolean[] isVisited,int i) {
int u;//表示队列的头结点对应的下标
int w;//表示邻接结点的下标
LinkedList<Integer> queue = new LinkedList<>();//队列,记录结点访问的顺序
//访问当前结点
System.out.print(getValueByIndex(i) + "-->");
//标记当前结点已经访问
isVisited[i] = true;
//当前结点入队列
queue.addLast(i);
while (!queue.isEmpty()) {
//取出队列的头结点下标
u = queue.removeFirst();
//得到u结点的第一个邻接结点
w = getFirstNeighbor(u);
while (w != -1) { //存在
//判断是否访问过
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "-->");
isVisited[w] = true;
queue.addLast(w);
}
//如果访问过,则找u结点的下一个邻接结点
w = getNextNeighbor(u,w);
}
}
}
//遍历所有的结点,都要进行广度优先搜索
public void bfs() {
for(int i = 0;i < vertexList.size();i++) {
if(!isVisited[i]) {
bfs(isVisited,i);
}
}
}
/**
* 深度优先遍历(DFS):
*/
//得到当前结点的第一个邻接结点的下标:如果存在就返回对应的下标,如果不存在就返回对应的下标
public int getFirstNeighbor(int index) {
for(int j = 0; j < vertexList.size();j++) {
if(edges[index][j] > 0) {
return j;
}
}
return -1;
}
//根据前一个邻接结点的下标来获取下一个邻接结点
public int getNextNeighbor(int v1,int v2) {
for(int j = v2 + 1;j < vertexList.size();j++) {
if(edges[v1][j] > 0) {
return j;
}
}
return -1;
}
//深度优先遍历算法:
public void dfs(boolean[] isVisited,int i) {
//首先我们访问当前结点
System.out.print(getValueByIndex(i) + "-->");
//将当前结点设置为已经访问
isVisited[i] = true;
//查找当前结点的第一个邻接结点w
int w = getFirstNeighbor(i);
while(w != -1) {
if(!isVisited[w]) { //说明没有被访问过
dfs(isVisited,w);
}
//如果w结点已经被访问过了,就访问当前结点v的下一个节点
w = getNextNeighbor(i,w);
}
//如果当前结点没有邻接结点,即w不存在,则从V的下一个节点继续(此时的下一个,指的是下标编号)
}
//如果当前结点没有邻接结点,即w不存在,则从V的下一个节点继续(此时的下一个,指的是下标编号)
//对dfs进行一个重载,遍历我们所有的结点,并进行dfs
public void dfs() {
for(int i = 0;i < vertexList.size();i++) {
if(!isVisited[i]) {
dfs(isVisited,i);
}
}
}
//插入顶点:
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
//插入边:
/**
*
* @param v1 v2:插入边的两端顶点分别是第几个顶点
* @param weight 邻接矩阵中为 0,还是1
*/
public void insertEdge(int v1,int v2,int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges ++;
}
/**
* 图的常用方法:
* 1.返回结点的个数
* 2.返回边的个数
* 3.返回下标为i的结点的数据
* 4.返回v1,v2的权值
* 5.显示图对应的矩阵
*/
//1.返回图中结点的个数
public int getNumOfVertex() {
return vertexList.size();
}
//2.返回边的个数
public int getNumOfEdges() {
return numOfEdges;
}
//3.返回下标为i的结点的数据
public String getValueByIndex(int i) {
return vertexList.get(i);
}
//4.返回v1,v2的权值(v1,v2表示第v1个顶点和第v2个顶点【从0开始算】)
public int getWeight(int v1,int v2) {
return edges[v1][v2];
}
//5.显示图对应的矩阵(显示二维数组)
public void showGraph() {
for(int[] link : edges)
System.out.println(Arrays.toString(link));
}
public static void main(String[] args) {
//测试创建一个图的邻接矩阵
Graph graph = new Graph(5);
String VertexValue[] = {"A","B","C","D","E"};
//添加顶点
for(String v : VertexValue) {
graph.insertVertex(v);
}
//添加边:A-B;A-C;B-C;B-D;B-E
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();
// //DFS
// System.out.println("深度优先遍历图:");
// graph.dfs();
//BFS
System.out.println("广度优先遍历图:");
graph.bfs();
}
}