一、无向图
1.1无向图的边
public class Edge {
Integer value;
Integer weight;
Edge next;
}
1.2 最小生成树的边
public class MST_Edge implements Comparable<MST_Edge>{
Integer begin; //begin end存放边的两个顶点
Integer end;
Integer weight; //边的权重
/**
* 实现的接口,用于依照权重排序用
* @param o
* @return
*/
@Override
public int compareTo(MST_Edge o) {
return this.weight-o.weight;
}
}
1.3 无向图
public class Graph {
HashMap<Integer,Edge> map= new HashMap<>();
HashMap<Integer,Boolean> visited;
List<MST_Edge> MST_Edges = new ArrayList<>(); //最小生成树的边
int numVertexes; //顶点的个数
int numEdges; //边的个数
public void createGraph(List<Integer> list){
for(int i=0;i<list.size();i+=3){
//一条边的两个点和权重
Integer Vnode1 = list.get(i);
Integer Vnode2 = list.get(i+1);
Integer w = list.get(i+2);
//将边的一点存到另一点的邻接点中
Edge e = new Edge();
e.weight = w;
e.value = Vnode2;
Edge head = map.get(Vnode1); //获取Vnode1点的所有邻接边(获取的值是链表头)
e.next = head;
map.put(Vnode1,e);//重新将该点的邻接边的链表首放入map
e = new Edge();
e.weight = w;
e.value = Vnode1;
head = map.get(Vnode2);
e.next = head;
map.put(Vnode2,e);
MST_Edge me = new MST_Edge();
me.begin = Vnode1;
me.end = Vnode2;
me.weight = w;
MST_Edges.add(me);
}
numVertexes = map.size();
numEdges = list.size()/3;
System.out.println("顶点个数"+numVertexes+"边个数"+numEdges);
}
/**
* 深度优先遍历(非递归)
* @param n 表示从哪个顶点开始深度优先遍历
*/
public void DFS(Integer n){
visited = new HashMap<Integer, Boolean>();
//将所有顶点的访问标志初始化为false
for(Integer i : map.keySet()){
visited.put(i,false);
}
System.out.println(n);
visited.put(n,true);
Stack<Edge> stack = new Stack<>();
if (map.get(n)!=null){//防止图只有一个结点 造成后面的空指针异常
stack.push(map.get(n));
}
while(!stack.isEmpty()){
Edge item = stack.pop();
Integer val = item.value;//要访问的点
if (item.next!=null){ //1、与访问点同在的邻接点链表中还有邻接点先保存
stack.push(item.next);
}
//该点没有被访问过
if(visited.get(val)==false){
System.out.println(val);//2、访问该点
visited.put(val,true); //3、将该点设置为已经访问过
stack.push(map.get(val));//4、再保存访问点的邻接点链表
}
}
}
/**
* 深度优先遍历(递归版本)
* @param n
*/
public void DFS2(Integer n,HashMap<Integer,Boolean> v){
System.out.println(n);
v.put(n,true);
Edge p = map.get(n); //获取当前节点的所有邻接节点
//遍历所有邻接节点,对没有访问的节点进行访问递归
while(p!=null){
if(v.get(p.value)== false){
v.put(p.value,true);
DFS2(p.value,v);
}
p = p.next;
}
}
/**
* 广度优先遍历
* @param n
*/
public void BFS(Integer n){
visited = new HashMap<Integer, Boolean>();
//将所有顶点的访问标志初始化为false
for(Integer i : map.keySet()){
visited.put(i,false);
}
Queue<Integer> q = new LinkedList<>();
q.add(n);
while(!q.isEmpty()){
Integer item = q.remove();
if (visited.get(item) == false){
System.out.println(item);
visited.put(item,true);
}
Edge e = map.get(item);
while(e!=null){
if(visited.get(e.value)==false){
q.add(e.value);
}
e = e.next;
}
}
}
/**
* 用于最小生成树中查找某个点所在连通分量的根节点
* @param parent 键是每个顶点,键值是该顶点的parent
* @param i 要查找该点的连通分量根节点
* @return
*/
public Integer findParent(HashMap<Integer,Integer> parent,int i){
while(parent.get(i) != i){
i = parent.get(i);
}
return i;
}
/**
* 生成最小生成树的边
*/
public void MST(){
//1、将所有边按照权重从小到大排序
Collections.sort(MST_Edges);
//2、将每个顶点看成一个连通分量,并将连通分量的根节点设置为自己
HashMap<Integer,Integer> parent = new HashMap<>();
for(Integer i:map.keySet()){
parent.put(i,i);
}
//用来存储最小生成树的边
List<MST_Edge> Tree_Edges = new LinkedList<>();
//3、遍历存放边的数组
for (int i=0;i<MST_Edges.size();++i){
int m = findParent(parent,MST_Edges.get(i).begin);
int n = findParent(parent,MST_Edges.get(i).end);
//4、判断每条边的两个顶点所在的连通分量的根节点是否相同,如果不同则可以加入到最小生成树中
if (m!=n){
Tree_Edges.add(MST_Edges.get(i));
//5、加入生成树后,要将其中一个顶点的连通分量根节点设置为另一个顶点的连通分量根节点,表示同为一个连通分量
parent.put(m,n);
}
}
//用来统计最小生成树的值
int sum=0;
for(MST_Edge e:Tree_Edges){
sum+= e.weight;
System.out.println(""+e.begin+"---"+e.end+" 权重:"+e.weight);
}
System.out.println("最小生成树的权重和为"+sum);
}
}
1.4 Dijkstra算法
public class Dijkstra {
int max = 65535;
int numVertex = 6;
char Vertexes[] = {'u','v','w','x','y','z'};
int graph[][] = {
/* u v w x y z */
{0,2,5,1,max,max},
{2,0,3,2,max,max},
{5,3,0,3,1,5},
{1,2,3,0,1,max},
{max,max,1,1,0,2},
{max,max,5,max,2,0}
};
/**
* 传入一个顶点,求出它到其他顶点的最短路径
*
* 算法思想:两个集合:S(表示已经找到最短路径的顶点集合) T(表示为未找到最短路径的集合)
* S 初始化为起点 T初始化为全部除起点外的顶点
* ST[]:用来区分S和T集合,值为1表示为S集合(即已经找到顶点与它的最短路径),为0则是T集合
* 数组D[]:表示起点与其他顶点的路径,初始化为顶点与其他顶点的直接相连路径,如果没有直接相连则初始化为最大表示没有边相连
* pre[] :表示起点到某个顶点的最小路径中该顶点的前驱节点
*
* 步骤 1、 遍历除T集合的顶点 ,找到D中与 起点路径最短的 顶点下标 和距离 ,将其添加到S集合(即ST[]中把该顶点对应的位置设为1)
* 2、 更新起点到 T集合中其他顶点 的距离
* 3、 继续步骤1的操作,知道遍历完所有顶点
*
* @param n 表示传入的顶点在数组中的下标
*/
public void Dijsk(int n){
/*存放最短路径(初始化为起点与其他顶点的直接相连的边权重)*/
int D[] = new int[numVertex];
/*区分顶点是S集合还是T集合*/
int ST[] = new int[numVertex];
/*最短路径的前驱节点*/
int pre[] = new int[numVertex];
/*初始化D、ST、pre*/
for(int i=0;i<numVertex;++i){
D[i] = graph[n][i];
ST[i]=0;
pre[i]=n;
}
/*初始化起始顶点(放入S集合)*/
ST[n] =1;
/*存放到某点最短路径的前驱节点*/
HashMap<Integer,Integer> preNode = new HashMap<>();
/*开始求遍历其他顶点求最短路径 需要遍历numVertex-1次(排除掉起点一次)*/
for(int i=0;i<numVertex;++i){
/*if排除自身到自身的最短路径*/
if(i!=n){
/*寻找离起点最近的节点 且 该节点还没添加到啊路径*/
int min = max; //最大数65535 表示没有距离
int k=-1; //存储距离最近顶点的下标
/*找到T集合中离起点最近的顶点*/
for (int j=0;j<numVertex;++j){
/**/
if(ST[j]==0 && D[j]<min){
min = D[j];
k = j;
}
}
/*将T集合中距离起点最近的顶点添加到S集合*/
ST[k] = 1;
/*更新T集合中其他顶点到起点的距离*/
for(int j=0;j<numVertex;++j){
/*防止溢出 max65535 再加会溢出*/
/*起点到k再加k到j的路径和*/
int temp =(graph[k][j]==max ? max:D[k]+graph[k][j] );
/*如果起点 到k再加k到j的路径和 比 原先到 j的路径更短 则更新 */
if(ST[j]==0 && temp<D[j]){
D[j] = temp;
/*更新T集合j的前驱节点*/
pre[j] =k;
}
}
}
}
System.out.println("起点"+Vertexes[n]+"到其他顶点的最短路径:");
for(int item=0;item<numVertex;++item){
System.out.println(""+Vertexes[n]+"-->"+Vertexes[item]+":"+D[item]+" 前驱节点:" + Vertexes[pre[item]]);
}
}
public static void main(String[] args) {
Dijkstra dis = new Dijkstra();
dis.Dijsk(0);
}
}
二、有向图
2.1 有向图顶点
/**
* 有向图的顶点结构
*/
public class VertexNode {
int in; //该顶点的入度
int data; //节点的名称值
TopoEdge firstEdge;
}
2.2 有向图的边(拓扑边)
public class TopoEdge {
int adjvex; //邻接点在 顶点列表的下标值
int weight;
TopoEdge next;
}
2.3 拓扑排序
/**
* 拓扑排序(有向图)
* 拓扑排序注释中有加C的表示是关键路肩算法添加的
*/
public class TopologicalSort {
int numEdge; //边的个数
int numVertex; //顶点个数
VertexNode[] vertexNodes; //顶点数组
int[] etv;//C存放事件(顶点)的最早开始时间(Ealist Time Vertex)
int[] ltv;//C存放事件的最晚开始时间(Latest Time Vertex)
Stack<Integer> s = new Stack();//存放拓扑排序的结果,起点在栈底,终点在栈顶,用来计算事件的最晚开始事件
/**
* 建立拓扑图
* @param numV 图的顶点个数
* @param numE 图的边个数
* @param listEdge 存储边的指向,由前一个指向后一个
* @param listVertex 存储顶点的值
*/
public void creatTopo(int numV,int numE,List<Integer> listVertex,List<Integer> listEdge){
this.numVertex = numV;
this.numEdge = numE;
vertexNodes = new VertexNode[numV];
etv = new int[numV];
/*假设顶点 下标的值 和 顶点的值 相同*/
for(int i=0;i<numV;++i){
vertexNodes[i] = new VertexNode();
vertexNodes[i].data = listVertex.get(i);
vertexNodes[i].in =0; //所有顶点的入度初始化为0
}
for(int i=0;i<listEdge.size();i+=3){
int v1 = listEdge.get(i); //弧尾顶点
int v2 = listEdge.get(i+1);//弧头顶点
int w = listEdge.get(i+2);//有向边的权重
vertexNodes[v2].in += 1; //弧的指向弧头 入度 加一
TopoEdge e = new TopoEdge();
e.adjvex = v2;
e.weight=w;
/*插入弧尾顶点的邻接点链表*/
e.next = vertexNodes[v1].firstEdge;
vertexNodes[v1].firstEdge = e;
}
}
/**
* AOV网(Active on Vertex NetWork):无权有向图(无环),顶点表示活动,弧表示活动之间的优先关系
* 拓扑排序:对有向图构造拓扑序列的过程
*
* 算法思想: 1、首先保存初始图的入度为0的顶点
* 2、将入度为0的顶点访问执行后后,更新这些顶点所在边对应顶点的入度
* 3、再将这些入度为0的顶点保存后重复 步骤2
* 注意:如果执行完后访问的顶点数<全部顶点,说明图中存在环
*
* 结果:输出此图的拓扑排序
*/
public void Topo(){
/*将初始图 入度为0 的顶点存入队列(存的是顶点在顶点列表的下标)*/
Queue<Integer> q = new LinkedList<>();
for(int i=0;i<numVertex;++i){
if(vertexNodes[i].in==0){
q.add(i);
}
}
int counts=0;//记录访问的顶点
/*将队列中所有入度为空的顶点访问*/
while (!q.isEmpty()){
int n = q.remove();
System.out.println(" "+vertexNodes[n].data);
s.push(n);//C存储拓扑排序的节点下标值,用于关键路径计算节点的最迟开始时间
counts+=1;
/*顶点访问后,更新其指向顶点的入度*/
TopoEdge item =vertexNodes[n].firstEdge;
while(item!=null){
int k =item.adjvex; //记录弹出节点指向边的另一个节点
vertexNodes[k].in -=1;
/*更新入度后如果入度为0则放入访问队列*/
if(vertexNodes[k].in ==0){
q.add(k);
}
/*C如果这个点的最早开始时间+边的权重>指向顶点的最早开始时间,则更新指向顶点的最早开始时间*/
if(etv[n]+item.weight>etv[k]){
etv[k] = etv[n]+item.weight;
}
item = item.next;
}
}
if(counts<numVertex){
System.out.println("图中存在环");
}
}
/**
* 关键路径
* 事件:针对顶点 活动:针对边
* 时间最早开始时间:从起点到终点开始推(事件的最早开始时间取决于 (边弧尾顶点的最早开始时间+边的权重)为最大的)
* 时间最晚开始时间:从终点到起点开始推 (事件的最晚开始之间取决于 (边弧头顶点的最晚开始时间-边的权重)为最小的 )
*
* 算法思想:
* 1:先计算出拓扑排序,在计算拓扑排序时,同时计算事件(顶点)的最早开始时间并将排序结果逆序存在栈s中
* 顶点事件的最早开始时间计算: 所有最早开始时间初始化为0;计算拓扑排序时,拿到当前该顶点,
* 遍历该顶点指向的邻接顶点,如果 当前顶点的最早开始时间 + 有向边权重 > 邻接顶点的最早开始时间,
* 则更新邻接节点的最早开始时间
*
* 2: 初始化所有顶点事件的最迟开始时间为终点事件的最早开始事件(即s栈顶元素所在顶点事件的最早开始事件)
*
* 3:将栈中弹出,计算出事件(顶点)的最迟开始时间:遍历弹出顶点的所有邻接点,用邻接点的最迟开始时间 减去 有向边
* 权重的结果跟弹出顶点的最迟开始时间相比,如果结果的时间比弹出顶点的最迟开始时间早,则更新弹出顶点的
* 最迟开始时间
*
* 4:遍历所有顶点,判断顶点跟其邻接点中是否存在 邻接点的最迟开始时间-边(活动的时间)= 该顶点的最迟开始时间,
* 如果存在则该有向边为关键路径
*/
public void CriticalPath(){
ltv = new int[numVertex];
/*将事件顶点的最迟开始事件初始化为终点时间最早开始时间*/
for(int i=0;i<numVertex;++i){
ltv[i] = etv[s.peek()];
}
while(!s.isEmpty()){
int item = s.pop();
//遍历该节点边指向的节点
for(TopoEdge e=vertexNodes[item].firstEdge;e!=null;e=e.next){
int k=e.adjvex; //保存该顶点指向的顶点下标
/*如果 该边起点 指向的边的终点 的最迟开始时间 减去边权重 比边起点的最迟时间还早,则更新改边起点的最迟开始时间*/
if(ltv[k] -e.weight<ltv[item]){
ltv[item] =ltv[k] -e.weight;
}
}
}
/*求关键活动 活动(边)的最早开始时间和最迟开始时间相同即为关键活动路径*/
for(int i=0;i<numVertex;++i){
//遍历该顶点的所有邻接点,计算邻接点的最迟开始时间减去边权重是否等于该点的最迟开始时间
for(TopoEdge e=vertexNodes[i].firstEdge;e!=null;e=e.next){
int k =e.adjvex;
//etv[i]:活动最早开始时间 ltv[k]-e.weight:活动最迟开始时间
if(ltv[k]-e.weight==etv[i]){
System.out.println(""+vertexNodes[i].data+"--->"+vertexNodes[k].data+" 权重:"+e.weight);
}
}
}
}
public static void main(String[] args) {
//拓扑排序测试
System.out.println("拓扑排序测试:");
List<Integer> listVertex = new ArrayList<>();
List<Integer> listEdge = new ArrayList<>();
listVertex.add(1);
listVertex.add(2);
listVertex.add(3);
listVertex.add(4);
listVertex.add(5);
listVertex.add(6);
listVertex.add(7);
listVertex.add(9);
/*边传的是该顶点在数组下标中的值*/
listEdge.add(0);
listEdge.add(2);
listEdge.add(0);
listEdge.add(2);
listEdge.add(4);
listEdge.add(0);
listEdge.add(4);
listEdge.add(6);
listEdge.add(0);
listEdge.add(6);
listEdge.add(7);
listEdge.add(0);
listEdge.add(1);
listEdge.add(3);
listEdge.add(0);
listEdge.add(3);
listEdge.add(4);
listEdge.add(0);
listEdge.add(1);
listEdge.add(5);
listEdge.add(0);
listEdge.add(3);
listEdge.add(5);
listEdge.add(0);
listEdge.add(5);
listEdge.add(7);
listEdge.add(0);
TopologicalSort t = new TopologicalSort();
t.creatTopo(listVertex.size(),listEdge.size()/3,listVertex,listEdge);
t.Topo();
System.out.println("************");
//关键路径测试
System.out.println("关键路径测试:");
List<Integer> listVertex2 = new ArrayList<>();
List<Integer> listEdge2 = new ArrayList<>();
listVertex2.add(0);
listVertex2.add(1);
listVertex2.add(2);
listVertex2.add(3);
listVertex2.add(4);
listVertex2.add(5);
listVertex2.add(6);
listVertex2.add(7);
listVertex2.add(8);
listVertex2.add(9);
listEdge2.add(0);
listEdge2.add(1);
listEdge2.add(3);
listEdge2.add(0);
listEdge2.add(2);
listEdge2.add(4);
listEdge2.add(1);
listEdge2.add(3);
listEdge2.add(5);
listEdge2.add(2);
listEdge2.add(3);
listEdge2.add(8);
listEdge2.add(1);
listEdge2.add(4);
listEdge2.add(6);
listEdge2.add(3);
listEdge2.add(4);
listEdge2.add(3);
listEdge2.add(2);
listEdge2.add(5);
listEdge2.add(7);
listEdge2.add(5);
listEdge2.add(7);
listEdge2.add(6);
listEdge2.add(4);
listEdge2.add(6);
listEdge2.add(9);
listEdge2.add(4);
listEdge2.add(7);
listEdge2.add(4);
listEdge2.add(6);
listEdge2.add(9);
listEdge2.add(2);
listEdge2.add(7);
listEdge2.add(8);
listEdge2.add(5);
listEdge2.add(8);
listEdge2.add(9);
listEdge2.add(3);
TopologicalSort t2 = new TopologicalSort();
t2.creatTopo(listVertex2.size(),listEdge2.size()/3,listVertex2,listEdge2);
t2.Topo();
t2.CriticalPath();
}
}