1.4 有向图
有向图与无向图类似,只是边有了方向而已
基本概念:
- 出度:由某个顶点指出的边的个数称为该顶点的出度
- 入度:指向某个顶点的边的个数称为该顶点的入度
- 有向路径:由一系列顶点组成,对于其中的每个顶点都存在一条有向边,从该顶点指向下一个顶点
- 有向环:起点和终点相同的有向图
手搓有向图:
package GraphTest;
import LinearTest.QueueTest;
public class DiGraph {
private final int V;
private int E;
private QueueTest<Integer>[] adj;
public DiGraph(int V){
this.V = V;
this.E = E;
//创建邻接表数组
this.adj = new QueueTest[V];
//创建邻接表
for (int i = 0;i<adj.length;i++){
adj[i] = new QueueTest<>();
}
}
public int V(){
return V;
}
public int E(){
return E;
}
//增加边v->w
public void addEdge(int v,int w){
adj[v].enqueue(w);
E++;
}
//获取由定点v所指出的所有定点
public QueueTest<Integer> adj(int v){
return adj[v];
}
//该有向图的反转图
public DiGraph reverse(){
//创建新的有向图对象
DiGraph r = new DiGraph(V);
//将由i->w的边改变为由w->i的边,就可以将图反向
for(int i = 0;i<V;i++){
for (Integer w : adj[i]) {
addEdge(w,i);
}
}
return r;
}
}
1.5 拓扑排序
给定一副有向图,将所有的顶点排序,使得所有有向边均从排在前面的元素指向排在后面的元素,此时就可以明确表示出每个顶点的优先级
检测有向图中的环:
要使用拓扑排序实现就要保证图中没有环的存在
代码如下:
package GraphTest;
public class DirectedCycle {
//标记定点是否已被访问
private boolean[] marked;
//记录图中是否有环
private boolean hasCycle;
//索引代表顶点,使用栈的思想,
// 记录当前顶点有没有已经处于正在搜索的有向路径上
private boolean[] onStack;
//创建一个检测环对象,检测图G中是否有环
public DirectedCycle(DiGraph G){
//创建一个标记是否访问的数组
marked = new boolean[G.V()];
//创建一个搜索栈数组
onStack = new boolean[G.V()];
//默认图中无环
hasCycle = false;
//遍历图中每一个顶点,判断是否有环
for(int v=0;v<G.V();v++){
//如果有环直接跳出循环,不能进行拓扑排序
if(hasCycle){
break;
}
//判断当前顶点是否已被访问
if(!marked[v]){
//如果没有被访问则调用dfs算法判断是否有环
dfs(G,v);
}
}
}
private void dfs(DiGraph G,int v){
//把当前结点标记为已搜索
marked[v] = true;
//将当前结点放入搜索栈中
onStack[v] = true;
//获取当前结点的邻接表
for (Integer w : G.adj(v)) {
//判断邻接表中w顶点是否被访问
if(!marked[w]){
dfs(G,w);
}
//如果当前遍历结点已经在搜索路径的栈中,则返回有环即可
if(onStack[w]){
hasCycle = true;
//当前顶点搜索完毕,让当前顶点出栈
onStack[w] = false;
return ;
}
}
}
//返回当前图中是否存在环
public boolean hasCycle(){
return hasCycle;
}
public static void main(String[] args) {
DiGraph G = new DiGraph(5);
G.addEdge(3,0);
G.addEdge(0,2);
G.addEdge(2,1);
G.addEdge(1,0);
G.addEdge(1,4);
DirectedCycle directedCycle = new DirectedCycle(G);
System.out.println(directedCycle.hasCycle());
}
}
顶点排序:
利用深度优先搜索搜索路径把终点放入栈中,然后返回上一节点如果该结点没有子节点也放入栈中,利用栈后进先出的特点就可以得到一个从起点到终点的结点顺序
package GraphTest;
import java.util.Stack;
public class DepthFirstOrder {
//创建标记顶点是否被搜索过的数组
private boolean[] marked;
//声明搜索栈,储存搜索的顶点序列
private Stack<Integer> reversePost;
//创建一个检测环对象,判断图G中是否有环
public DepthFirstOrder(DiGraph G){
marked = new boolean[G.V()];
//创建一个搜索栈
reversePost = new Stack<Integer>();
//遍历搜索图中每一个顶点
for (int v=0;v<G.V();v++){
if(!marked[v]){
dfs(G,v);
}
}
}
private void dfs(DiGraph G,int v){
marked[v] = true;
for (Integer w : G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
reversePost.push(v);
}
public Stack<Integer> reversePost(){
return reversePost;
}
public static void main(String[] args) {
DiGraph G = new DiGraph(6);
G.addEdge(0,2);
G.addEdge(0,3);
G.addEdge(2,4);
G.addEdge(3,4);
G.addEdge(4,5);
G.addEdge(1,3);
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
Stack<Integer> integers = depthFirstOrder.reversePost();
for (Integer integer : integers) {
System.out.println(integer);
}
}
}
拓扑排序:
先检测是否有环、之后顶点排序就是拓扑排序
package GraphTest;
import java.util.Stack;
public class TopoLogical {
//顶点的拓扑排序结果集
private Stack<Integer> order;
//构造拓扑排序对象
public TopoLogical(DiGraph G){
//判断是否有环
DirectedCycle dCycle = new DirectedCycle(G);
//如果没环进行顶点排序
if(!dCycle.hasCycle()){
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
order = depthFirstOrder.reversePost();
}
}
//判断是否有环
private boolean isCycle(){
return order==null;
}
//获取拓扑排序的所有顶点
public Stack<Integer> order(){
return order;
}
public static void main(String[] args) {
DiGraph G = new DiGraph(6);
G.addEdge(0,2);
G.addEdge(0,3);
G.addEdge(2,4);
G.addEdge(3,4);
G.addEdge(4,5);
G.addEdge(1,3);
TopoLogical topoLogical = new TopoLogical(G);
Stack<Integer> order = topoLogical.order();
for (Integer w : order) {
System.out.println(w+" ");
}
}
}
1.6 加权无向图
为每一条边关联一个权重值,这样可以根据权重获得最优解
加权无向图边的表示:
package GraphTest;
/**
* 有向边的表示类
*/
public class Edge implements Comparable<Edge> {
//顶点一
private final int v;
//顶点二
private final int w;
//当前边的权重
private final double wight;
public Edge(int v,int w,double wight){
this.v = v;
this.w = w;
this.wight = wight;
}
//获取边的权重
public double wight(){
return wight;
}
//获取边上的一个点
public int either(){
return v;
}
//获取边上除了顶点vertex外的另外一个顶点
public int other(int vertex){
if(vertex==v){
return w;
}else{
return v;
}
}
@Override
public int compareTo(Edge that) {
int cmp;
//当前边的权重大于that边
if(this.wight()>that.wight()){
cmp = 1;
}else if(this.wight()<that.wight()){
cmp = -1;
}else{
cmp = 0;
}
return cmp;
}
}
加权无向图:
package GraphTest;
import LinearTest.QueueTest;
public class EdgeWeightedGraph {
//顶点总数
private final int V;
//边的总数
private int E;
//邻接表,因为是加权无向图,泛型不是Integer
private QueueTest<Edge>[] adj;
public EdgeWeightedGraph(int V){
this.V = V;
this.E = 0;
this.adj = new QueueTest[V];
//为每个顶点初始化一个邻接表
for(int i=0;i<adj.length;i++){
adj[i] = new QueueTest<Edge>();
}
}
//获取图中顶点的数量
public int V(){
return V;
}
//获取图中边的数量
public int E(){
return E;
}
//向加权无向图中添加一条边e
public void addEdge(Edge e){
int w = e.either();
int v = e.other(w);
//因为是无向图,所有边应该出现在两个顶点的邻接表中
adj[w].enqueue(e);
adj[v].enqueue(e);
E++;
}
//获取和顶点v关联的所有边
public QueueTest<Edge> adj(int v){
return adj[v];
}
//获取加权无向图的所有边
public QueueTest<Edge> edges(){
//创建一个队列存储所有边
QueueTest<Edge> allEdge = new QueueTest<>();
/*因为是无向图,所以如果每个边都加入队列会出现重复
因为顶点是有小到大,所以每次判断一下,小顶点放入,
大顶点边不放入,就可以避免重复
*/
//获取所有顶点
for(int v=0;v<V;v++){
//获取所有顶点的邻接表
for (Edge w : adj[v]) {
//获取w边的除了v顶点的另一个顶点
if(w.other(v)<v){
allEdge.enqueue(w);
}
}
}
return allEdge;
}
}