从零开始学习数据结构与算法(14)—— 图
1.为什么要有图?
1.线性表局限于一个直接前驱和一个直接后继的关系
2.树也只有一个直接前驱也就是父节点
3.当我们需要表示多对多的关系时,我们就用到了图
图是一种数据结构,其中节点可以具有零个或多个相邻元素,两个结点之间的连接称为边,节点也可以称为顶点。
图的常用概念:
(1)顶点(节点)
(2)边
(3)路径
(4)无向图(顶点之间连接没有方向)
(5)有向图
(6)带权图
2.图的表示方式
3.图的快速入门案例及代码实现
思路:
(1)使用ArrayList存储顶点String
(2)保存矩阵int[][] edges
4.图的遍历
所谓图的遍历,及时对节点的访问。一个图有那么多节点,如何遍历这些节点,需要特定的策略,一般有两种访问策略:(1)深度优先遍历 (2)广度优先遍历
4.1 图的深度优先搜索(DFS)
(1)深度优先遍历,从初始访问节点出发,初始访问节点可能有多个邻接节点。深度优先遍历的策略就是首先访问第一个邻接节点,然后再以这个被访问的邻接节点作为初始节点,访问他的第一个邻接节点,可以理解每次都在访问完当前节点后首先访问当前节点的第一个邻接节点。
(2)可以看出,这样的访问策略是优先往纵向挖掘深入,而不是对一个节点的所有邻接节点进行横向访问。
(3)显然,深度优先搜索时是一个递归的过程
4.1.1 深度优先遍历算法步骤
(1)访问初始节点v,并标记节点v已访问
(2)查找节点v的第一个邻接节点w
(3)若w存在,则继续执行4,如果w不存在,则回到第1步,将从w的下一个节点继续
(4)若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)
(5)若w已经被访问,查找节点v的w邻接节点的下一个邻接节点,转到步骤3
4.1.2 深度优先遍历代码实现
//得到第一个邻接节点的下标w
/**
*
* @param index
* @return 如果存在就返回对应的下标,否则返回-1
*/
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;
//查找节点i的第一个邻接节点w
int w = getFirstNeighbor(i);
while (w != -1){//邻节点存在
if (!isVisited[w]){
dfs(isVisited,w);
}
//如果w节点已经被访问过
w = getNextNeighbor(i,w);
}
}
//对dfs进行重载,遍历所有的节点,并进行dfs
public void dfs(){
//遍历所有的节点进行dfs
for (int i = 0;i < getNumOfVertex();i++){
if (!isVisited[i]){
dfs(isVisited,i);
}
}
}
4.2 图的广度优先遍历(BFS)
图的广度优先搜索
类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的节点的顺序,以便安这个顺序俩访问这些节点的邻接节点
4.2.1 广度优先遍历算法步骤
(1)访问初始节点v并标记节点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
4.2.1 广度优先遍历代码实现
//对一个节点进行广度优先遍历的方法
private void BFS(boolean[] isVisited,int i){
int u;//表示队列的头节点对应的下标
int w;//邻接节点的下标
//队列,结点访问的顺序
LinkedList queue = new LinkedList();
//访问这个节点
System.out.print(getValueByIndex(i)+"=>");
//标记为已访问
isVisited[i] = true;
//将节点加入队列
queue.addLast(i);
while (!queue.isEmpty()){
//取出队列的头节点下标
u = (Integer)queue.removeFirst();
//得到第一个邻接点的下标
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 < getNumOfVertex();i++){
if (!isVisited[i]){
BFS(isVisited,i);
}
}
}
5.代码汇总
public class GraphDemo {
public static void main(String[] args) {
//测试
int n = 5;//节点的个数
String[] vertexs = {"a", "b", "c", "d", "e"};
//创建图对象
Graph graph = new Graph(n);
//循环添加顶点
for (String value : vertexs) {
graph.insertVertax(value);
}
//添加边
graph.insertEdge(0, 1, 1);//a-b
graph.insertEdge(0, 2, 1);//a-c
graph.insertEdge(1, 2, 1);//b-c
graph.insertEdge(1, 3, 1);//b-d
graph.insertEdge(1, 4, 1);//b-e
//显示邻接矩阵
graph.showGraph();
//测试DFS
// graph.DFS();
//测试BFS
graph.BFS();
}
}
class Graph {
private ArrayList<String> vertexList;//存储顶点集合
private int[][] edges;//存储对应的邻接矩阵
private int numOfEdges;//表示边的数目
//定义数组boolean [] ,记录某个节点是否被访问
private boolean[] isVisited;
public Graph(int n){
//初始化矩阵和vertexList
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
isVisited = new boolean[n];
}
//插入节点
public void insertVertax(String vertex){
vertexList.add(vertex);
}
//添加边
/**
*
* @param v1 表示点的下标
* @param v2 表示点的下标
* @param weight 表示矩阵的值
*/
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
//返回节点的个数
public int getNumOfVertex(){
int size = vertexList.size();
return size;
}
//返回边的个数
public int getNumOfEdges(){
return numOfEdges;
}
//返回节点i下标对应的数据
public String getValueByIndex(int i){
return vertexList.get(i);
}
//返回v1和v2的权值
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
//显示图对应的矩阵
public void showGraph(){
for (int[] link : edges){
System.err.println(Arrays.toString(link));
}
}
//得到第一个邻接节点的下标w
/**
*
* @param index
* @return 如果存在就返回对应的下标,否则返回-1
*/
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;
}
//深度优先遍历算法
private void DFS(boolean[] isVisited,int i){
//首先访问该节点
System.out.print(getValueByIndex(i)+"->");
//将该节点设置为已经访问
isVisited[i] = true;
//查找节点i的第一个邻接节点w
int w = getFirstNeighbor(i);
while (w != -1){//邻节点存在
if (!isVisited[w]){
DFS(isVisited,w);
}
//如果w节点已经被访问过
w = getNextNeighbor(i,w);
}
}
//对dfs进行重载,遍历所有的节点,并进行dfs
public void DFS(){
//遍历所有的节点进行dfs
for (int i = 0;i < getNumOfVertex();i++){
if (!isVisited[i]){
DFS(isVisited,i);
}
}
}
//对一个节点进行广度优先遍历的方法
private void BFS(boolean[] isVisited,int i){
int u;//表示队列的头节点对应的下标
int w;//邻接节点的下标
//队列,结点访问的顺序
LinkedList queue = new LinkedList();
//访问这个节点
System.out.print(getValueByIndex(i)+"=>");
//标记为已访问
isVisited[i] = true;
//将节点加入队列
queue.addLast(i);
while (!queue.isEmpty()){
//取出队列的头节点下标
u = (Integer)queue.removeFirst();
//得到第一个邻接点的下标
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 < getNumOfVertex();i++){
if (!isVisited[i]){
BFS(isVisited,i);
}
}
}
}