一、为什么要有图
- 线性表局限于一个直接前驱和一个直接后继的关系
- 树也只能有一个直接前驱也就是父节点
- 当我们需要表示多对多的关系时,就需要用到图这种数据结构
基本介绍
- 图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边(edge); 结点也可以称为顶点(vertex)。
- 路径: 比如从 D -> C 的路径有
- D->B->C
- D->A->B->C
-
1.无向图: 顶点之间的连接没有方向,比如A-B,
即可以是 A-> B 也可以 B->A .

-
2.有向图: 顶点之间的连接有方向,比如A-B,
只能是 A-> B 不能是 B->A .

- 3.带权图:这种边带权值的图也叫网

二、图的表示方式
图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接表)。
- 1.邻接矩阵表示:邻接矩阵是表示图形中顶点之间相邻关系的矩阵

- 2.邻接表表示:邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在,会造成空间的一定损失.而邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成。

-
代码实现邻接矩阵:
以这个图为例
public class ArrayQueue {
private ArrayList<String> vertexList; //存储顶点集合
private int[][] edgs; //存储对应邻接矩阵
private int numOfEdgs; //表示边的数目
private boolean[] isVisited; //记录某个节点是否被访问
public static void main(String[] args) {
int n = 5;
String VertexValue[] = {"A","B","C","D","E"};
ArrayQueue a = new ArrayQueue(n);
for(String val : VertexValue) {
a.insertVertex(val);
}
//A-B A-C B-C B-D B-E
a.insertEdge(0, 1, 1);
a.insertEdge(0, 2, 1);
a.insertEdge(1, 2, 1);
a.insertEdge(1, 3, 1);
a.insertEdge(1, 4, 1);
a.print();
a.dfs();
}
//构造器,初始化实例
public ArrayQueue(int n) {
edgs = new int[n][n];
vertexList = new ArrayList<>(n);
numOfEdgs = 0;
isVisited = new boolean[n];
}
//插入节点
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
//添加边
public void insertEdge(int v1,int v2,int weight) {
edgs[v1][v2] = weight;
edgs[v2][v1] = weight;
numOfEdgs++;
}
//返回节点个数
public int getNumOfVertex() {
return vertexList.size();
}
//得到边的数目
public int getNumOfEdgs() {
return numOfEdgs;
}
//返回i下表对应的数据
public String getValueByIndex(int i) {
return vertexList.get(i);
}
//返回v1,v2的权值
public int getWeight(int v1,int v2) {
return edgs[v1][v2];
}
//打印图
public void print() {
for(int[] a : edgs) {
for(int i = 0;i < a.length;i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
}
}
- 打印结果如图:

三、图的遍历方式
- 所谓图的遍历,即是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要特定策略,一般有两种访问策略: (1)深度优先遍历 (2)广度优先遍历
1、图的深度优先遍历 DFS (Depth First Search)
- 二叉树的前序、中序、后序遍历,本质上也可以认为是深度优先遍历。
- 假设你去参观景点,把每个节点看作为一个景点,那么怎么规划参观路线呢?

- DFS就相当于是一头扎到底的玩法。我们选择一条支路,尽可能不断地深入,如果遇到死路就往回退,回退过程中如果遇到没探索过的支路,就进入该支路继续深入。(这里看出来DFS肯定要用到递归回溯)
- 在图中,我们首先选择景点1的这条路,继续深入到景点4、景点5、景点3、景点6,发现后面没有其他景点了(景点旁边的数字代表探索次序):

- 于是,我们退回到景点1,然后探索景点7,景点8,又走到了死胡同。于是,退回到景点7,探索景点10:

- 按照这个思路,我们再退回到景点1,探索景点9,最后再退回到景点0,后续依次探索景点2,终于玩遍了整个游乐场:

- 代码实现
//得到下一个邻接节点的下标
public int getFirstNeighbor(int index) {
for(int j = 0;j < vertexList.size();j++) {
if(edgs[index][j] > 0) {
return j;
}
}
return -1;
}
//获取下一位
public int getNextNeighbor(int v1,int v2) {
for(int j = v2 + 1;j < vertexList.size();j++) {
if(edgs[v1][j] > 0) {
return j;
}
}
return -1;
}
//深度优先遍历算法
public void dfs(boolean[] isVisited,int i) {
//首先访问该节点
System.out.print(getValueByIndex(i) + "->");
isVisited[i] = true; //设置节点为已访问
int w = getFirstNeighbor(i);
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);
}
}
}
2、图的广度优先遍历 BFS (Broad First Search)
二叉树的层次遍历,本质上也可以认为是深度优先遍历。
- 在图中,我们首先探索景点0的相邻景点1、2、3、4

- 接着,我们探索与景点0相隔一层的景点7、9、5、6:

- 最后,我们探索与景点0相隔两层的景点8、10:

- 代码实现:
//广度优先遍历算法
public void bfs(boolean[] isVisited,int i) {
int u = 0;
int w = 0;
LinkedList q = new LinkedList(); //用链表模拟队列
System.out.print(getValueByIndex(i) + "=>");
isVisited[i] = true;
q.addLast(i);
while(!q.isEmpty()) {
u = (Integer)q.removeFirst();
w = getFirstNeighbor(u);
while(w != -1) {
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
isVisited[w] = true;
q.addLast(w);
}
w = getNextNeighbor(u,w);
}
}
}
五、图的深度优先VS 广度优先

- 深度优先遍历顺序为 1->2->4->8->5->3->6->7
- 广度优先算法的遍历顺序为:1->2->3->4->5->6->7->8
网友评论