数据结构(图)

一、无向图

1.1无向图基本属性

术语介绍:

相邻顶点:当两个顶点通过一条边相连时,即此两顶点为相邻顶点

度: 一个顶点有多少条边

子图:一幅图所有边的子集组成的图

路径:边+顶点(连续的)

环:起点与终点一致的路径

连通图:图中任意一个顶点都存在一条路径能够到达另一个顶点,那么这幅图叫做连通图

连通子图:顾名思义

1.2 图的存储结构

图的核心两个点就是 顶点与边

常见的图的存储结构有邻接矩阵和邻接表

1.2.1 邻接矩阵

使用一个V*V的二维数组int[V][V] adj,把索引的值看做是顶点,如adj[m][n],即表示图中m顶点与n顶点之间边的联系,如果m与n之间有边联系,对于无向图,可将adj[m][n]=adj[n][m]=边的权值,有向图则adj[m][n]=顶点m到n边的权值,adj[n][m]=顶点n到m边的权值。见下图

1.2.2 邻接表

使用一个大小为V的数组Queue[V] adj,把索引看作是顶点,在每个索引处adj[v]上存储一个队列,该队列存储的是所有与该顶点相邻的其他顶点。相比于邻接矩阵,邻接表的空间复杂度低。见下图

 图以及邻接表的代码实现类如下所示,包含获取图的顶点数,边数,在图中添加边,获取某顶点的所有相邻顶点等方法

public class Graph {
    //顶点数目
    private final int V;
    //边的数目
    private int E;
    //邻接表
    private Queue<Integer>[]adj;//
    public Graph(int v){
        this.V=v;
        this.E=0;
        this.adj=new Queue[v];

//        for (int i = 0; i <adj.length ; i++) {
//            ad
//
//        }
    }
    //获取顶点的数目
    public int V(){
        return V;
    }
    //获取边的数目
    public int E(){
        return E;
    }
    //向图中添加一条边v-w,即在队列中添加相应的顶点即可
    public void addEdge(int v,int w){
        adj[v].offer(w);
        adj[w].offer(v);
        E++;
    }
    //获取和顶点V相连的所有顶点
    public Queue<Integer> adj(int v){
        return adj[v];
    }

}

邻接表深度优先搜索,根据指定顶点找出所有有关联的点

package 图;

public class DepthFirstSearch {
    //索引代表顶点,值表示当前顶点是否已被搜索
    private boolean[]marked;
    //记录有多少顶点与s顶点相通
    private int count;
    //构造深度优先搜索对象,使用深度优先搜索找出G图中s顶点所有的相邻顶点
    public DepthFirstSearch(Graph G,int s){
        //初始化marked数组,长度即为图中顶点的数量
        this.marked=new boolean[G.V()];
        //初始化跟顶点s相通的顶点的数量
        this.count=0;
        dfs(G,s);
    }
    public  int count(){
        return count;
    }
    public void dfs(Graph G,int v){
        //标识已搜索
        marked[v]=true;
        for (Integer i:G.adj(v)){
            if(marked[i]==false) {
                dfs(G, i);
            }
        }
        count++;
    }

}

邻接表的广度优先搜索 可以联想到二叉树的层序遍历

package 图;

import java.util.LinkedList;
import java.util.Queue;

public class BreadthFirstSearch {
    //索引代表顶点,值代表是否被搜索过
    private boolean[]marked;
    //记录有多少顶点与s顶点相通
    private int count;
    //用来存储带搜索邻接表的点
    private Queue<Integer> waitsearch;
    //构造广度优先搜索对象,使用广度优先搜索找出G图中s顶点的所有相邻顶点
    public BreadthFirstSearch(Graph G,int s){
        this.marked=new boolean[G.V()];
        this.count=0;
        this.waitsearch=new LinkedList<>();
        bfs(G,s);
    }
    private void bfs(Graph G,int v){
        //把当前顶点标记为已搜索
        marked[v]=true;
        //让顶点v进入队列,待搜索
        waitsearch.add(v);
        //通过循环,如果队列不为空,则从队列中弹出一个待搜索的顶点进行搜索
while (!waitsearch.isEmpty()){
    //弹出待搜索邻点
    Integer id=waitsearch.poll();
    for (Integer i:G.adj(id)){
        if(marked[i]==false){
            bfs(G,i);
        }
    }
}
count++;

    }
    public boolean panduan(int w){
        return marked[w];
    }
    public int count(){
        return count;
    }

}

1.3路径查找 

输入起点和终点,找出起点和终点之间存在的路径,按顺序输出顶点

package 图;

import java.util.Stack;

public class DepthFirstPaths {
    //索引代表顶点,值代表当前顶点已经被搜索
    private boolean[]marked;
    //起点
    private int s;
    //索引代表顶点,值代表从起点s到当前顶点的最后一个顶点
    private int[]edgeTo;
    //构造深度优先搜索对象,使用深度优先搜索找出G图中起点为s的所有路径
    public DepthFirstPaths(Graph G,int s){
        //初始化marked数组
        this.marked=new boolean[G.V()];
        //初始化起点
        this.s=s;
        this.edgeTo=new int[G.V()];
        dfs(G,s);

    }
    private void dfs(Graph G,int v){
        //把v标识成已搜索
        marked[v]=true;
        //遍历顶点的邻接表,拿到每一个相邻的顶点,继续递归搜索
        for (int i:G.adj(v)){
            //如果顶点未被搜索,则递归进行搜索
            if(!marked[i]){
                edgeTo[i]=v;
                dfs(G,i);
            }
        }
    }
    //判断两个路径是否相通,通则为true
    public boolean hasPathTo(int v){
        return marked[v];
    }
    //找出从起点s到顶点v的路径
    public Stack<Integer> pathTo(int v){
        if(!hasPathTo(v)){
            return null;
        }
        //创建对象,保存路径中的所有顶点
        Stack<Integer>path=new Stack<>();
        for (int i = v; i !=s ; i=edgeTo[i]) {
            path.push(i);

        }
        path.push(s);
        return path;
    }

}

 二、有向图

有向图与无向图最大的差别便是它的边有了方向

2.1有向图基本属性

出度:某个顶点指出边的个数

入度:某个顶点指入边的个数

有向路径:一系列顶点连接着一系列有向边形成的通路

有向环:至少含有一条边,起点和终点是同一个顶点的有向路径

两个顶点之间存在双向边,它们也算一个有向环

2.2 有向图实现类

package 图;

import java.util.LinkedList;
import java.util.Queue;

public class Digraph {
    //顶点数目
    private final int v;
    //边的数目
    private int E;
    //邻接表
    private Queue<Integer>[]adj;
    public Digraph(int v){
        this.v=v;
        this.E=0;
        this.adj=new Queue[v];
        for (int i = 0; i <adj.length ; i++) {
            adj[i]=new LinkedList<>();
        }
    }
    //获取顶点数目
    public int V(){
        return v;
    }
    //获取边的数目
    public int E(){
        return E;
    }
    //向有向图中加一条边v->w
    public void addEdge(int v,int w){
        //只需要让顶点w出现在顶点v的邻接表中(边是有方向的)
        adj[v].add(w);
        E++;
    }
    //获取顶点v所指出的所有顶点
    public Queue<Integer> adj(int v){
        return adj[v];
    }
    //该图的反向图
    private Digraph reverse(){
        Digraph r=new Digraph(v);
        for (int i = 0; i <v ; i++) {
            for (int j:adj(i)){
                r.addEdge(i,j);
            }
            
        }
        return r;
    }
    
    
}

2.3 有向图拓扑排序

拓扑排序即按时间序列进行排序,先做谁再做谁

首先得检测图中是否有环,如果有环就无法进行拓扑排序

检测有向图中是否存在环,主要是利用标记数组,标记路径中是否存在回头路,如果存在回头路,则表示图中有环。

package 图;

public class DirectedCycle {
    private boolean[]marked;
    //记录图中是否有环
    private boolean hasCycle;
    //索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的路径上(不走回头路)
    private boolean[] onStack;

    //创建一个检测对象,检测图G是否有环
    public DirectedCycle(Digraph G){
        //初始化marked数组
        this.marked=new boolean[G.V()];
        //初始化hasCycle
        this.hasCycle=false;
        //初始化onStack数组
        this.onStack=new boolean[G.V()];
        //找到图中的每一个顶点,让每一个顶点作为路口,调用一次dfs搜索
        for(int v=0;v<G.V();v++){
            if(!marked[v]){
                dfs(G,v);
            }
        }
    }
    public void dfs(Digraph G,int v){
        //当前顶点标记已搜索
        marked[v]=true;
        //把当前顶点进栈
        onStack[v]=true;
        //进行深度搜索
        for(Integer w:G.adj(v)){
            if(!marked[w]){
                dfs(G,w);
            }
            //判断当前顶点是否已在栈中,如已在(即走了回头路),那说明有环
            if(!onStack[w]){
                hasCycle=true;//表明有环
                return;
            }
        }
        //将当前顶点出栈,重新置位
        onStack[v]=false;
    }
    //判断当年有向图G中是否有环
    public boolean hanCycle(){
        return hasCycle;
    }

}

拓扑排序,即输出相通的顶点序列,结果不唯一 

package 图;

import java.util.Stack;

/**
 * 拓扑排序
 */
public class TopoLogical {
    //顶点的拓扑
    private Stack<Integer>order;
    //构造拓扑排序对象
    public TopoLogical(Digraph G){
        //创建一个检测有向环的对象
        DirectedCycle cycle=new DirectedCycle(G);
        //判断G图中有无环,如果没有环,则进行顶点排序;创建一个顶点排序对象
        if(!cycle.hanCycle()){
            DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
            order=depthFirstOrder.reversePost();
        }
    }
    //判断图G是否有环
    private boolean isCycle(){
        return order==null;
    }
    //获取拓扑排序的所有顶点
    public Stack<Integer> order(){
        return order;
    }
}

3.有向图拓扑排序练习 

经典题目课程表,因为两个课程上的先后存在依赖关系,采用拓扑排序极为适合

 附上代码,核心便是创建邻接表,创建入度数组(存放节点入度数量)

class Solution {
  public static boolean canFinish(int numCourses, int[][] prerequisites) {
        if (numCourses <= 0) {
            return false;
        }
        int count=0;
        Queue<Integer>queue=new LinkedList<>();
      //初始化入度数组 
        int degree_count[] = new int[numCourses];
        //初始化邻接表
        HashSet<Integer>[] arr = new HashSet[numCourses];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = new HashSet<Integer>();
        }
        //遍历课程,确定最终的邻接表和入度数组
        for (int temp[] : prerequisites) {
            //入度+1
               degree_count[temp[0]]++;
               arr[temp[1]].add(temp[0]);
        }
         //统计入度为0的节点放入队列
        for (int i = 0; i <degree_count.length ; i++) {
            if(degree_count[i]==0){
                queue.add(i);
            }
        }
//将入度为0的节点排出,同时它的相邻节点入度数量减1,再判断其入度是否为0,为0,则放入队列
        while (!queue.isEmpty()){

            int newone=queue.poll();
       //每排出一个节点,count便+1
            count++;
            for (Integer i:arr[newone]){
                degree_count[i]-=1;
                if(degree_count[i]==0){
                    queue.add(i);
                }
            }
        }
  如果排出的节点数量少于总的节点数,则证明存在环
        return count==numCourses;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值