图是由有穷非空集合的顶点和顶点之间的边组成的集合。通常表示为G(V,E),其中G表示一个图,V是G中顶点的集合,E是图G中边的集合。
在线性结构中,每个元素都只有一个直接前驱和直接后继,主要用来表示一对一的数据结构;在数形结构中,数据之间有着明显的父子关系,,每个数据和其子节点的多个数据相关,主要用来表示一对多的数据结构;在图形结构中,数据之间具有任意关系,图中任意两个数据元素之间都可能相关,可用来表示多对多的数据结构。图根据边的属性可分为无向图和有向图。
无向、有向图、:若从一个顶点到另一个顶点的边没有方向就是无向图。反之就是有向图。
图的存储结构:邻接矩阵:
图的邻接矩阵的存储方式是基于两个数组来表示图的数据结构并存储图中的数据。一个一维数组存储图中的顶点信息,一个二维数组(叫做邻接矩阵)存储图中的边或弧的信息。设图G中有N个顶点,则邻接矩阵是一个n*n的方阵。
例如:右图所示的无向图G1和有向图G2,对应的邻接矩阵分别为A1和A2。
从邻接矩阵A1和A2中不难看出,无向图的邻接矩阵是对称阵,因此一般可以采用压缩存储;有向图的邻接矩阵一般不对称。用邻接矩阵存储图,所需要的存储空间只与顶点数有关。
对于网G,假设wij代表边(vi,vj)或<vi,vj>上的权值,则图G的邻接矩阵A定义如下:
其中,0≤i,j≤n-1。
例如:右图所示的网G3和G4,对应的邻接矩阵分别为A3和A4:
用邻接矩阵表示图,很容易判断任意两个顶点之间是否有边,同样很容易求出各个顶点的度。对于无向图,邻接矩阵的第i行或第i列的非零元素的个数正好是第i个顶点vi的度;对于有向图,邻接矩阵的第i行的非零元素的个数正好是第i个顶点vi的出度,第i列的非零元素的个数正好是第i个顶点v的入度。
图的深度优先搜索与广度优先搜索
深度优先搜索:类似于树的先根遍历。
广度优先搜索:就是一层一层的搜索。
package 图;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @version v1.0
* @Package 图
* @auther LaurenceLau
* @date 2020/4/16,1:24
* @description
*/
public class GraphNode {
private Object data;
private List<GraphNode> nexts=new ArrayList<>();//有向
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
//判断两个节点是否相等,重equels和hashcode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GraphNode graphNode = (GraphNode) o;
return Objects.equals(data, graphNode.data) &&
Objects.equals(nexts, graphNode.nexts);
}
@Override
public int hashCode() {
return Objects.hash(data, nexts);
}
public List<GraphNode> getNexts() {
return nexts;
}
public void setNexts(List<GraphNode> nexts) {
this.nexts = nexts;
}
public GraphNode(Object data) {
this.data = data;
this.nexts = nexts;
}
}
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* @version v1.0
* @Package 图
* @auther LaurenceLau
* @date 2020/4/16,1:35
* @description
*/
public class test {
/*
* 深度优先遍历
* @root 图的起始节点
*@ visiteds 已访问过的节点集合
* */
//深度遍历要用栈,广度遍历用队列
public static void dft(GraphNode root,ArrayList<GraphNode> visiteds){
System.out.println(root.getData());//打印出节点数据
visiteds.add(root);//将访问过的节点加入visiteds
for(int i=0;i<root.getNexts().size();i++){
GraphNode t=root.getNexts().get(i);
if(!visiteds.contains(t)){
dft(t,visiteds);//函数自身有一个栈
}
}
}
//广度优先遍历
public static void bft(GraphNode root,ArrayList<GraphNode> visiteds){
Queue<GraphNode> queue =new LinkedList<>();
queue.offer(root);
visiteds.add(root);
while(!queue.isEmpty()){
GraphNode poll=queue.poll();
System.out.println(poll.getData());
visiteds.add(poll);
for(int i=0;i<poll.getNexts().size();i++){
GraphNode node = poll.getNexts().get(i);
if(!visiteds.contains(node)){
queue.offer(node);
visiteds.add(node);
}
}
}
}
public static void main(String[] args) {
GraphNode node1=new GraphNode(0);
GraphNode node2=new GraphNode(1);
GraphNode node3=new GraphNode(2);
GraphNode node4=new GraphNode(3);
GraphNode node5=new GraphNode(4);
GraphNode node6=new GraphNode(5);
GraphNode node7=new GraphNode(6);
GraphNode node8=new GraphNode(7);
//单向关联
node1.getNexts().add(node2);
node1.getNexts().add(node3);
node3.getNexts().add(node7);
node2.getNexts().add(node4);
node2.getNexts().add(node5);
node4.getNexts().add(node6);
node6.getNexts().add(node5);
node5.getNexts().add(node8);
node1.getNexts().size();//出度
List<GraphNode> graphNodeList=new ArrayList<>();
graphNodeList.add(node1);
graphNodeList.add(node2);
graphNodeList.add(node3);
graphNodeList.add(node4);
graphNodeList.add(node5);
graphNodeList.add(node6);
graphNodeList.add(node7);
graphNodeList.add(node8);
System.out.println("深度优先遍历,递归");
dft(node1,new ArrayList<>());
System.out.println("广度优先遍历");
bft(node1,new ArrayList<>());
}
}