关于图的一些特点就不说了,现在我们先展示的是顶点的实现
/**
* Created by 西皮 on 2017/9/15 19:58.
* 图的顶点类
*/
public class MyVertex<VItem> {
private VItem data;//数据
private int inDegree,outDegree;//出入度数
private VStatus status;//状态
private long dTime,fTime;//时间标签
private int parent;//在遍历树中的父节点
private int priority;//在遍历树中的优先级
public MyVertex(VItem data){
this.data = data;
this.inDegree = 0;
this.outDegree = 0;
this.status = VStatus.UNDISCOVERED;
this.dTime = -1;
this.fTime = -1;
this.parent = -1;
this.priority = Integer.MAX_VALUE;
}
public VItem getData() {
return data;
}
public void setData(VItem data) {
this.data = data;
}
public int getInDegree() {
return inDegree;
}
public void setInDegree(int inDegree) {
this.inDegree = inDegree;
}
public int getOutDegree() {
return outDegree;
}
public void setOutDegree(int outDegree) {
this.outDegree = outDegree;
}
public VStatus getStatus() {
return status;
}
public void setStatus(VStatus status) {
this.status = status;
}
public long getdTime() {
return dTime;
}
public void setdTime(long dTime) {
this.dTime = dTime;
}
public long getfTime() {
return fTime;
}
public void setfTime(long fTime) {
this.fTime = fTime;
}
public int getParent() {
return parent;
}
public void setParent(int parent) {
this.parent = parent;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
}
这里的属性,status使用的enum来标识的,三种顶点状态,分别是UNDISCOVERED
,DISCOVERED
,VISITED
,分别用来表示未被发现,已被发现但是还未访问完毕,已经被访问完毕
/**
* Created by 西皮 on 2017/9/16 9:49.
* 定义顶点状态的枚举类
*/
public enum VStatus {
UNDISCOVERED("UNDISCOVERED"),DISCOVERED("DISCOVERED"),VISITED("VISITED");
private final String value;
VStatus(String value){
this.value = value;
}
public String getValue() {
return value;
}
}
接下来展示边
/**
* Created by 西皮 on 2017/9/16 10:09.
* 图的边类
*/
public class MyEdge<EItem> {
private EItem data;//数据
private int weight;//权重
private EType type;//在便利书中所属的类型
public MyEdge(EItem data,int weight){
this.data = data;
this.weight = weight;
this.type = EType.UNDETERMINED;
}
public EItem getData() {
return data;
}
public void setData(EItem data) {
this.data = data;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public EType getType() {
return type;
}
public void setType(EType type) {
this.type = type;
}
}
这里的边同样使用enum来表示边的状态
/**
* Created by 西皮 on 2017/9/16 9:55.
* 定义边状态的枚举类
*/
public enum EType {
UNDETERMINED("UNDETERMINED"),TREE("TREE"),
CROSS("CROSS"),FORWARD("FORWARD"),BACKWARD("BACKWARD");
private final String value;
EType(String value){
this.value = value;
}
public String getValue(){
return value;
}
}
UNDETERMINED
图初始化时,所有边默认是UNDETERMINED,其他的边状态需要结合图的BFS和DFS来看,更简单,这里我们暂且放一边。
接下来,我们来实现一个图
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Created by 西皮 on 2017/9/16 10:13.
* 矩阵图
*/
public class MyGrapMatrix<VItem,EItem> {
private List<MyVertex<VItem>> V = new ArrayList<>();//顶点集
private List<List<MyEdge<EItem>>> E = new ArrayList<>();//边集,使用一个邻接矩阵来表示边
private int n;//顶点数量
private int e;//边的数量
public MyGrapMatrix(){}
public VItem getMyVertex(int i){
return V.get(i).getData();//获得数据
}
public int getMyVertexInDegree(int i){
return V.get(i).getInDegree();//获得顶点入度
}
public int getMyVertexOutDegree(int i){
return V.get(i).getOutDegree();
}
public VStatus getMyVertexStatus(int i){
return V.get(i).getStatus();
}
public int getVertexNum(){
return n;
}
public int getEdgeNum(){
return e;
}
public long getMyVertexDTime(int i){
return V.get(i).getdTime();
}
public long getMyVertexFTime(int i){
return V.get(i).getfTime();
}
public int getMyVertexParent(int i){
return V.get(i).getParent();
}
public int getMyVertexPriority(int i){
return V.get(i).getPriority();
}
/**
* 获得当前节点i的比j小的下一个邻居
* @param i
* @param j
* @return
*/
public int nextNbr(int i,int j){
while (-1 < j && !exisits(i,--j));
return j;
}
/**
* 获得顶点i的首个邻居
* @param i
* @return
*/
public int firstNbr(int i){
return nextNbr(i,V.size());
}
/**
* 判断两个顶点是否有边
* @param i
* @param j
* @return
*/
public boolean exisits(int i,int j){
if((0 <= i) && (i < n) &&
(0 <= j) && (j < n) &&
(E.get(i).get(j) != null))
return true;
return false;
}
/**
* 获得边(i,j)的数据
* @param i
* @param j
* @return
*/
public EItem getMyEdgeData(int i,int j){
return E.get(i).get(j).getData();
}
public EType getMyEdgeStatus(int i,int j){
return E.get(i).get(j).getType();
}
public int getMyEdgeWeight(int i,int j){
return E.get(i).get(j).getWeight();
}
/**
* 插入一条边,边的两个顶点分别是i和j
* @param edgeData
* @param weight
* @param i
* @param j
*/
public void insertEdge(EItem edgeData,int weight,int i,int j){
if (exisits(i,j)) return;//忽略已有的边
E.get(i).set(j,new MyEdge<EItem>(edgeData,weight));
e++;//更新边计数
//更新关联顶点i的出度和j的入度
V.get(i).setOutDegree(V.get(i).getOutDegree()+1);
V.get(j).setInDegree(V.get(j).getInDegree()+1);
}
/**
* 删除边(i,j)
* @param i
* @param j
* @return
*/
public EItem removeEdge(int i,int j){
EItem eBak = getMyEdgeData(i,j);
E.get(i).set(j,null);//删除边(i,j)
e--;//更新边计数
V.get(i).setOutDegree(V.get(i).getOutDegree()-1);
V.get(j).setInDegree(V.get(j).getInDegree()-1);
return eBak;
}
/**
* 顶点插入
* @param vertexData
* @return
*/
public int insertVertex(VItem vertexData){
for (int j = 0;j < n;j++) E.get(j).add(null);
n++;
ArrayList<MyEdge<EItem>> lineEdges = new ArrayList<>(n);
for (int i = 0;i < n;i++){
lineEdges.add(null);
}
E.add(lineEdges);
V.add(new MyVertex<VItem>(vertexData));
return n-1;
}
/**
* 删除顶点及其关联边,返回该顶点信息
* @param i
* @return
* 索引所对应的顶点就改变了
*/
public VItem removeVertex(int i){
for (int j = 0;j < n;j++){
if(exisits(i,j)){
removeEdge(i,j);
}
}
E.remove(i);n--;//删除第i行
VItem vBak = V.get(i).getData();V.remove(i);//备份之后,删除顶点i
for (int j = 0;j < n;j++){//删除所有入边及第i列
E.get(j).remove(i);
V.get(j).setOutDegree(V.get(j).getOutDegree()-1);
}
return vBak;//返回被删除顶点的信息
}
private int BFSclock = 0;
/**
* 广度优先搜索Breadth-First Search
* @param v
*/
public void BFS(int v){
MyQueue<Integer> Q = new MyQueue<>();
V.get(v).setStatus(VStatus.DISCOVERED);
Q.enqueue(v);
while (!Q.isEmpty()){
v = Q.dequeue();
System.out.println(V.get(v).getData());
V.get(v).setdTime(++BFSclock);//取出对首顶点v,并给个时间戳
//考察v的每一个邻居u
for (int u = firstNbr(v);-1 < u; u = nextNbr(v,u)){
if (VStatus.UNDISCOVERED == V.get(u).getStatus()){
//若u尚未被发现
V.get(u).setStatus(VStatus.DISCOVERED);
Q.enqueue(u);//发现该顶点
//将他们之间的边设置为TREE边
E.get(v).get(u).setType(EType.TREE);
//把u在遍历树中的父节点设置成v
V.get(u).setParent(v);
}else {
E.get(v).get(u).setType(EType.CROSS);
}
}
//至此,当前顶点访问完毕
V.get(v).setStatus(VStatus.VISITED);
}
}
/**
* @param s
*/
public void bfs(int s){//s为初始顶点
BFSclock = 0;
int v = s;
do {
if(VStatus.UNDISCOVERED == V.get(v).getStatus())
BFS(v);//即从该顶点出发启动一次BFS
}while (s != (v = (++v % n)));
}
private int DFSclock = 0;
/**
* 深度优先遍历
* @param v
*/
public void DFS(int v){
V.get(v).setdTime(++DFSclock);
V.get(v).setStatus(VStatus.DISCOVERED);//发现当前顶点v
System.out.println(V.get(v).getData());
for (int u = firstNbr(v); -1 < u; u = nextNbr(v,u)){//枚举v的每一邻居u
switch (V.get(u).getStatus()){//并视其状态分别处理
case UNDISCOVERED://u尚未发现,意味着支撑树可在此扩展
E.get(v).get(u).setType(EType.TREE);
V.get(u).setParent(v);
DFS(u);
break;
case DISCOVERED://u已被发现但尚未访问完毕,应属被后代指向的祖先
E.get(v).get(u).setType(EType.BACKWARD);
break;
default://u已访问完毕(VISITED,有向图),则视承袭关系分为前向边或跨边
E.get(v).get(u).setType(V.get(v).getdTime()<V.get(u).getdTime()?EType.FORWARD:EType.CROSS);
break;
}//switch
}
V.get(v).setStatus(VStatus.VISITED);
V.get(v).setfTime(++DFSclock);//至此,当前顶点v方告访问完毕
}
public void dfs(int s){//s为初始顶点
DFSclock = 0;
int v = s;
do {
if(VStatus.UNDISCOVERED == V.get(v).getStatus())
DFS(v);//即从该顶点出发启动一次BFS
}while (s != (v = (++v % n)));
}
}
顶点集使用一个ArrayList,这是一个数组,使用它的索引来标识不同的顶点,而边则使用的是一个二维数组来来表示,这样会使他的空间复杂度为Θ(n^2)
。
这个导致这种结构所使用存储空间和这个图的边数没有关系,所以像这样使用邻接矩阵实现的图更适合稠密图。
在这里我们首先看看这里的exists()
方法
/**
* 判断两个顶点是否有边
* @param i
* @param j
* @return
*/
public boolean exisits(int i,int j){
if((0 <= i) && (i < n) &&
(0 <= j) && (j < n) &&
(E.get(i).get(j) != null))
return true;
return false;
}
我们首先需要确定i和j这两个表示顶点的序号是可用的,然后在去表示边的矩阵中查找从i指向j的这条边是否存在,也就是我们是否把MyEdge的对象存放进这个矩阵中,为null则不存在,否则存在。
接下来,我们来看看firstNbr(int i)和nextNbr(int i)
这两个方法
/**
* 获得当前节点i的比j小的下一个邻居
* @param i
* @param j
* @return
*/
public int nextNbr(int i,int j){
while (-1 < j && !exisits(i,--j));
return j;
}
/**
* 获得顶点i的首个邻居
* @param i
* @return
*/
public int firstNbr(int i){
return nextNbr(i,V.size());
}
调用firstNbr()可以获得i顶点的从尾部开始的第一个邻居,而nextNbr()可以循着这个方向向前再找一个新邻居,这里在遍历中时非常有用的。
接下来是对图的动态操作,分别为对边的添加删除,和对顶点的添加删除,边的添加删除没什么问题,看看代码就够了,这里我们着重看一看顶点的添加删除
/**
* 顶点插入
* @param vertexData
* @return
*/
public int insertVertex(VItem vertexData){
for (int j = 0;j < n;j++) E.get(j).add(null);
n++;
ArrayList<MyEdge<EItem>> lineEdges = new ArrayList<>(n);
for (int i = 0;i < n;i++){
lineEdges.add(null);
}
E.add(lineEdges);
V.add(new MyVertex<VItem>(vertexData));
return n-1;
}
/**
* 删除顶点及其关联边,返回该顶点信息
* @param i
* @return
* 索引所对应的顶点就改变了
*/
public VItem removeVertex(int i){
for (int j = 0;j < n;j++){
if(exisits(i,j)){
removeEdge(i,j);
}
}
E.remove(i);n--;//删除第i行
VItem vBak = V.get(i).getData();V.remove(i);//备份之后,删除顶点i
for (int j = 0;j < n;j++){//删除所有入边及第i列
E.get(j).remove(i);
V.get(j).setOutDegree(V.get(j).getOutDegree()-1);
}
return vBak;//返回被删除顶点的信息
}
顶点添加时,我们首先需要对应的在表示边的矩阵中添加一列和一行,因为这两个V顶点集和E边集是对应的,所以我们看到首先遍历每一行,为每一行添加一个为null的边也就是这两个顶点间的边还不存在,让顶点的数量n++;再接下来,我们为这个矩阵添加一行数据,当然这一行数据存放的也全是空,因为这个新顶点还没有与任何顶点关联。最后我们把这个新顶点放在V集的末尾。
此时,我们可以看到尽管我们对数组做了这么多动态操作,但实质上这些数据全是直接添加在数组的最后一个位置,这个操作的时间复杂度是Ο(1)也就是常数时间。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这个是add(E e)的实际操作,我们可以看到,他首先判断容量是否足够,不够就扩容,够就直接把数据存放在最后,然后size++
那么删除时也是同理,但是删除的时间复杂度就与他的i有关系了,因为i后面的元素需要前移,同时又因为是一个二维数组的,这里时间复杂度应该是Ο(n^2),其他就是类似的操作了,这里就不再说了。
最后测试一下个图是否正确
@Test
public void testGraphBFS(){
MyGrapMatrix<String,Integer> myGrapMatrix = new MyGrapMatrix<>();
myGrapMatrix.insertVertex("S");
myGrapMatrix.insertVertex("A");
myGrapMatrix.insertVertex("D");
myGrapMatrix.insertVertex("E");
myGrapMatrix.insertVertex("C");
myGrapMatrix.insertVertex("B");
myGrapMatrix.insertVertex("F");
myGrapMatrix.insertVertex("G");
myGrapMatrix.insertEdge(1,1,0,1);
myGrapMatrix.insertEdge(1,1,0,4);
myGrapMatrix.insertEdge(1,1,0,2);
myGrapMatrix.insertEdge(1,1,1,0);
myGrapMatrix.insertEdge(1,1,1,4);
myGrapMatrix.insertEdge(1,1,1,3);
myGrapMatrix.insertEdge(1,1,2,0);
myGrapMatrix.insertEdge(1,1,2,5);
myGrapMatrix.insertEdge(1,1,3,1);
myGrapMatrix.insertEdge(1,1,3,6);
myGrapMatrix.insertEdge(1,1,3,7);
myGrapMatrix.insertEdge(1,1,4,0);
myGrapMatrix.insertEdge(1,1,4,1);
myGrapMatrix.insertEdge(1,1,4,5);
myGrapMatrix.insertEdge(1,1,5,2);
myGrapMatrix.insertEdge(1,1,5,4);
myGrapMatrix.insertEdge(1,1,6,3);
myGrapMatrix.insertEdge(1,1,6,7);
myGrapMatrix.insertEdge(1,1,7,3);
myGrapMatrix.insertEdge(1,1,7,5);
myGrapMatrix.insertEdge(1,1,7,6);
System.out.println(myGrapMatrix.getVertexNum());
System.out.println(myGrapMatrix.getEdgeNum());
myGrapMatrix.BFS(0);
}
这里同时测试了树的BFS,确实我们完成树的同时也顺便完成树的DFS和BFS,这些东西,以后再说。